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); }