You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将变换矩阵从着色器迁移至JavaScript应用的技术疑问

Moving Transformation Matrices from GLSL to JavaScript for a Controllable Rotating Cube

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:45:55