176 lines
8.9 KiB
JavaScript

var gl, program, bufferPosition, bufferNormal, bufferColor, vertexCount = 0;
function createShader(glContext, type, source) {
var shader = glContext.createShader(type);
glContext.shaderSource(shader, source);
glContext.compileShader(shader);
if (!glContext.getShaderParameter(shader, glContext.COMPILE_STATUS))
throw new Error(glContext.getShaderInfoLog(shader));
return shader;
}
function initGL() {
gl = ui.glCanvas.getContext('webgl', {
antialias: true,
alpha: true
});
if (!gl)
throw new Error('当前浏览器不支持 WebGL 预览,但仍可导出 STL');
var vertex = createShader(gl, gl.VERTEX_SHADER, `
attribute vec3 aPosition;
attribute vec3 aNormal;
attribute vec3 aColor;
uniform mat4 uMVP;
uniform mat4 uModel;
varying vec3 vNormal;
varying vec3 vColor;
varying vec3 vPos;
void main() {
vec4 world = uModel * vec4(aPosition, 1.0);
gl_Position = uMVP * vec4(aPosition, 1.0);
vNormal = normalize(mat3(uModel) * aNormal);
vColor = aColor;
vPos = world.xyz;
}
`);
var fragment = createShader(gl, gl.FRAGMENT_SHADER, `
precision mediump float;
varying vec3 vNormal;
varying vec3 vColor;
varying vec3 vPos;
void main() {
vec3 n = normalize(vNormal);
vec3 lightA = normalize(vec3(-0.45, -0.35, 0.82));
vec3 lightB = normalize(vec3(0.75, 0.25, 0.44));
float diffuse = max(dot(n, lightA), 0.0) * 0.72 + max(dot(n, lightB), 0.0) * 0.18;
float rim = pow(1.0 - abs(n.z), 2.0) * 0.18;
vec3 color = vColor * (0.42 + diffuse) + rim * vec3(0.75, 0.92, 0.90);
gl_FragColor = vec4(color, 1.0);
}
`);
program = gl.createProgram();
gl.attachShader(program, vertex);
gl.attachShader(program, fragment);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS))
throw new Error(gl.getProgramInfoLog(program));
bufferPosition = gl.createBuffer();
bufferNormal = gl.createBuffer();
bufferColor = gl.createBuffer();
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.BACK);
requestAnimationFrame(renderGL);
}
function updateMeshBuffers() {
if (!gl || !state.reliefPositions || !state.framePositions)
return;
var include = ui.includeFrame.checked;
var frameLength = include ? state.framePositions.length : 0;
var totalLength = frameLength + state.reliefPositions.length;
var positions = new Float32Array(totalLength);
var normals = new Float32Array(totalLength);
var colors = new Float32Array(totalLength);
var offset = 0;
if (include) {
positions.set(state.framePositions, 0);
normals.set(state.frameNormals, 0);
for (let i = 0; i < frameLength; i += 3) {
colors[i] = .14;
colors[i + 1] = .29;
colors[i + 2] = .31;
}
offset = frameLength;
}
positions.set(state.reliefPositions, offset);
normals.set(state.reliefNormals, offset);
var minT = Number(ui.minThickness.value);
var maxT = Number(ui.maxThickness.value);
var thicknessRange = Math.max(.01, maxT - minT);
for (let i = offset; i < totalLength; i += 3) {
var thickness = clamp(positions[i + 2] - FAN.baseZ, minT, maxT);
var darkness = (thickness - minT) / thicknessRange;
colors[i] = .91 + (.31 - .91) * darkness;
colors[i + 1] = .97 + (.56 - .97) * darkness;
colors[i + 2] = .91 + (.54 - .91) * darkness;
}
vertexCount = totalLength / 3;
gl.bindBuffer(gl.ARRAY_BUFFER, bufferPosition);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, bufferNormal);
gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, bufferColor);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
}
function mat4Multiply(a, b) {
var out = new Float32Array(16);
for (let col = 0; col < 4; col++) {
for (let row = 0; row < 4; row++) {
out[col * 4 + row] = a[row] * b[col * 4] + a[4 + row] * b[col * 4 + 1] + a[8 + row] * b[col * 4 + 2] + a[12 + row] * b[col * 4 + 3];
}
}
return out;
}
function perspective(fov, aspect, near, far) {
var f = 1 / Math.tan(fov / 2);
var nf = 1 / (near - far);
return new Float32Array([f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (far + near) * nf, -1, 0, 0, 2 * far * near * nf, 0]);
}
function translation(x, y, z) {
return new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1]);
}
function rotationX(a) {
var c = Math.cos(a)
, s = Math.sin(a);
return new Float32Array([1, 0, 0, 0, 0, c, s, 0, 0, -s, c, 0, 0, 0, 0, 1]);
}
function rotationZ(a) {
var c = Math.cos(a)
, s = Math.sin(a);
return new Float32Array([c, s, 0, 0, -s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
}
function renderGL() {
requestAnimationFrame(renderGL);
if (!gl || !program || !vertexCount)
return;
var dpr = Math.min(window.devicePixelRatio || 1, 2);
var width = Math.max(1, Math.floor(ui.glCanvas.clientWidth * dpr));
var height = Math.max(1, Math.floor(ui.glCanvas.clientHeight * dpr));
if (ui.glCanvas.width !== width || ui.glCanvas.height !== height) {
ui.glCanvas.width = width;
ui.glCanvas.height = height;
}
gl.viewport(0, 0, width, height);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.useProgram(program);
var center = translation(90, 0, -4.5);
var rotation = mat4Multiply(rotationX(state.camera.pitch), rotationZ(state.camera.yaw));
var model = mat4Multiply(rotation, center);
var view = translation(0, 0, -state.camera.zoom);
var proj = perspective(deg(36), width / height, 1, 1600);
var mvp = mat4Multiply(proj, mat4Multiply(view, model));
gl.uniformMatrix4fv(gl.getUniformLocation(program, 'uMVP'), false, mvp);
gl.uniformMatrix4fv(gl.getUniformLocation(program, 'uModel'), false, model);
var positionLoc = gl.getAttribLocation(program, 'aPosition');
gl.bindBuffer(gl.ARRAY_BUFFER, bufferPosition);
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 0, 0);
var normalLoc = gl.getAttribLocation(program, 'aNormal');
gl.bindBuffer(gl.ARRAY_BUFFER, bufferNormal);
gl.enableVertexAttribArray(normalLoc);
gl.vertexAttribPointer(normalLoc, 3, gl.FLOAT, false, 0, 0);
var colorLoc = gl.getAttribLocation(program, 'aColor');
gl.bindBuffer(gl.ARRAY_BUFFER, bufferColor);
gl.enableVertexAttribArray(colorLoc);
gl.vertexAttribPointer(colorLoc, 3, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, vertexCount);
}