将变换矩阵从着色器迁移至JavaScript应用的技术疑问
Got it, let's walk through this step by step—shifting those rotation, translation, and scale matrices from your shader to JS is straightforward, and it’ll make adding control buttons a breeze. Here’s how to pull it off:
1. First: Ditch Shader-Based Matrix Calculations
First, modify your vertex shader to accept precomputed matrices from JavaScript instead of calculating them internally.
Example Modified Vertex Shader
If your original shader looked like this (with hardcoded matrix math):
attribute vec3 position; uniform float rotationY; void main() { // Rotation matrix calculated directly in GLSL mat4 rotate = mat4( cos(rotationY), 0, sin(rotationY), 0, 0, 1, 0, 0, -sin(rotationY), 0, cos(rotationY), 0, 0, 0, 0, 1 ); gl_Position = projectionMatrix * viewMatrix * rotate * vec4(position, 1.0); }
Update it to receive a combined modelMatrix (plus view/projection if you’re using those) as a uniform:
attribute vec3 position; uniform mat4 modelMatrix; // This will come from JS uniform mat4 viewMatrix; uniform mat4 projectionMatrix; void main() { gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0); }
2. Build Matrix Utility Functions in JavaScript
WebGL doesn’t have built-in matrix helpers like GLSL, so you’ll need simple functions to create and combine transformation matrices. All these use column-major order (matching GLSL’s mat4 storage).
Add these to your JS code:
// Helper to convert degrees to radians (since JS uses radians for trig functions) Math.radians = (degrees) => degrees * Math.PI / 180; // Create a 4x4 translation matrix function createTranslationMatrix(x, y, z) { return [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1 ]; } // Create a 4x4 rotation matrix around the X axis function createRotationXMatrix(angleRadians) { const cos = Math.cos(angleRadians); const sin = Math.sin(angleRadians); return [ 1, 0, 0, 0, 0, cos, -sin, 0, 0, sin, cos, 0, 0, 0, 0, 1 ]; } // Create a 4x4 rotation matrix around the Y axis function createRotationYMatrix(angleRadians) { const cos = Math.cos(angleRadians); const sin = Math.sin(angleRadians); return [ cos, 0, sin, 0, 0, 1, 0, 0, -sin, 0, cos, 0, 0, 0, 0, 1 ]; } // Create a 4x4 scale matrix function createScaleMatrix(x, y, z) { return [ x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1 ]; } // Multiply two 4x4 matrices (order matters: a * b) function multiplyMatrices(a, b) { const result = new Array(16).fill(0); for (let row = 0; row < 4; row++) { for (let col = 0; col < 4; col++) { result[row * 4 + col] = a[row * 4 + 0] * b[0 * 4 + col] + a[row * 4 + 1] * b[1 * 4 + col] + a[row * 4 + 2] * b[2 * 4 + col] + a[row * 4 + 3] * b[3 * 4 + col]; } } return result; }
3. Combine Matrices and Pass to the Shader
Now, in your rendering logic (or button click handlers), calculate your combined transformation matrix and send it to the shader.
Step 3.1: Get Uniform Locations
First, grab the location of the modelMatrix uniform when initializing your WebGL program:
const gl = document.getElementById('canvas').getContext('webgl'); const program = initShaderProgram(gl, vertexShaderSource, fragmentShaderSource); // Your existing shader setup // Get uniform location const modelMatrixLoc = gl.getUniformLocation(program, 'modelMatrix'); // Also get view/projection locations if you’re using them const viewMatrixLoc = gl.getUniformLocation(program, 'viewMatrix'); const projectionMatrixLoc = gl.getUniformLocation(program, 'projectionMatrix');
Step 3.2: Calculate and Pass the Matrix
Define variables to track your cube’s state (rotation angles, position, scale), then compute the matrix in your render loop:
// Track cube state let rotationY = 0; const cubeScale = 0.5; const cubePosition = [0, 0, -2]; // Push cube back so it’s visible function render() { // 1. Clear the canvas gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // 2. Calculate transformation matrices const scaleMat = createScaleMatrix(cubeScale, cubeScale, cubeScale); const rotateMat = createRotationYMatrix(Math.radians(rotationY)); const translateMat = createTranslationMatrix(...cubePosition); // Combine matrices (order matters: scale → rotate → translate) // Since matrix multiplication is right-to-left, we multiply translate * rotate * scale const modelMatrix = multiplyMatrices(translateMat, multiplyMatrices(rotateMat, scaleMat)); // 3. Pass matrices to the shader gl.useProgram(program); gl.uniformMatrix4fv(modelMatrixLoc, false, new Float32Array(modelMatrix)); // Set view/projection matrices too (example values) gl.uniformMatrix4fv(viewMatrixLoc, false, new Float32Array(createTranslationMatrix(0, 0, 0))); gl.uniformMatrix4fv(projectionMatrixLoc, false, new Float32Array(createPerspectiveMatrix(45, gl.canvas.width/gl.canvas.height, 0.1, 100))); // 4. Draw your cube (your existing draw logic here) gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexBuffer); gl.drawArrays(gl.TRIANGLES, 0, 36); requestAnimationFrame(render); } // Helper for perspective matrix (add this to your utilities) function createPerspectiveMatrix(fovY, aspect, near, far) { const f = 1.0 / Math.tan(fovY * Math.PI / 360); const rangeInv = 1.0 / (near - far); return [ f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (near + far) * rangeInv, -1, 0, 0, near * far * rangeInv * 2, 0 ]; }
4. Add Control Buttons
Finally, add buttons to modify the cube’s state and trigger re-renders.
HTML Buttons
Add these to your HTML (anywhere outside the canvas):
<div class="controls"> <button id="rotateLeft">Rotate Y Left</button> <button id="rotateRight">Rotate Y Right</button> <button id="reset">Reset Rotation</button> </div>
JS Event Listeners
Add these to your JS to update the rotation state when buttons are clicked:
document.getElementById('rotateLeft').addEventListener('click', () => { rotationY -= 15; // Rotate 15 degrees left }); document.getElementById('rotateRight').addEventListener('click', () => { rotationY += 15; // Rotate 15 degrees right }); document.getElementById('reset').addEventListener('click', () => { rotationY = 0; });
Since render() is running in a requestAnimationFrame loop, the cube will automatically update whenever rotationY changes.
Key Notes
- Matrix Order: Always remember transformation order matters! For most cases, you’ll want to apply scale first, then rotation, then translation (hence the multiplication order in the code).
- Column-Major Order: WebGL expects matrices in column-major order, which matches how we built our functions—don’t mix this up with row-major, or your transformations will be broken.
内容的提问来源于stack exchange,提问作者user9516512

