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

JS实现3D光栅化器时X轴旋转矩阵失效,求修复方案

排查JavaScript 3D光栅化器的X轴旋转与透视除法问题

问题概述

  • X轴旋转矩阵无法正常工作,已确认矩阵方向与乘法函数匹配
  • 透视除法在系数为1时(w=1)无变化,导致透视效果失效

原代码

const canvas = document.createElement("canvas");
canvas.setAttribute("id", "myCanvas");
canvas.setAttribute("width", 1000);
canvas.setAttribute("height", 500);
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");
let element;

let fps = 1000/24;

var theta = 0;

let run = true;
switch (run) {
    case true:
        setInterval(function() {
            ctx.beginPath();
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            theta++;
            var tta = (theta/180)*3.14159;
            
            //point
            function pt(x, y, z) {
                return [x, y, z, 1];
            }
            
            //line drawing function
            function drawL(p, p2) {
                ctx.moveTo(p[0]+(canvas.width*0.5), -p[1]+(canvas.height*0.5));
                ctx.lineTo(p2[0]+(canvas.width*0.5), -p2[1]+(canvas.height*0.5));
            }
            
            //triangle drawer function
            function dt(p, p2, p3) {
                drawL(p, p2);
                drawL(p2, p3);
                drawL(p3, p);
            }

            //matrix multiplication
            function matmul(point, m) {
                var x = point[0]*m[0][0]+point[1]*m[0][1]+point[2]*m[0][2]+point[3]*m[0][3];
                var y = point[0]*m[1][0]+point[1]*m[1][1]+point[2]*m[1][2]+point[3]*m[1][3];
                var z = point[0]*m[2][0]+point[1]*m[2][1]+point[2]*m[2][2]+point[3]*m[2][3];
                var w = point[0]*m[3][0]+point[1]*m[3][1]+point[2]*m[3][2]+point[3]*m[3][3];
            
                switch(true) {
                    case w!=0:
                        x/=w;
                        y/=w;
                        z/=w;
                        break;                
                }
                var POINT = [x, y, z, w];
                return POINT;
            }
            
            //matrix variables for my perspective projection
            var znear = 0.1;
            var zfar = 1000;
            var fov = (90/180)*3.14159;
            var h = canvas.height, w = canvas.width;
            var a = h/w;
    
            var prjM = [[a*(1/Math.tan(fov/2)), 0, 0, 0], [0, 1/Math.tan(fov/2), 0, 0], [0, 0, -(zfar/(zfar-znear)), -((zfar*znear)/(zfar-znear))], [0, 0, 1, 0]];

            var sm = [[300, 0, 0, 0], [0, 150, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
            
            function all(p) {
                var sp = matmul(p, sm)
                var np = matmul(sp, prjM);
                return np;
            }

            //front tris
            var tp = pt(0, 0, 0);
            var tp2 = pt(1, 0, 0);
            var tp3 = pt(1, 1, 0);

            var p = all(tp);
            var p2 = all(tp2);
            var p3 = all(tp3);
           
            var tp4 = pt(1, 1, 0);
            var tp5 = pt(0, 1, 0);
            var tp6 = pt(0, 0, 0);

            var p4 = all(tp4);
            var p5 = all(tp5);
            var p6 = all(tp6);

            //back tris
            var tp7 = pt(0, 1, 1.1);
            var tp8 = pt(1, 1, 1.1);
            var tp9 = pt(1, 0, 1.1);

            var p7 = all(tp7);
            var p8 = all(tp8);
            var p9 = all(tp9);
            
            var tp10 = pt(1, 0, 1.1);
            var tp11 = pt(0, 0, 1.1);
            var tp12 = pt(0, 1, 1.1);

            var p10 = all(tp10);
            var p11 = all(tp11);
            var p12 = all(tp12);

            //right side tris
            var tp13 = pt(1, 1, 0);
            var tp14 = pt(1, 0, 0);
            var tp15 = pt(1, 1, 1.1);

            var p13 = all(tp13);
            var p14 = all(tp14);
            var p15 = all(tp15);

            var tp16 = pt(1, 0, 0);
            var tp17 = pt(1, 0, 1.1);
            var tp18 = pt(1, 1, 1.1);

            var p16 = all(tp16);
            var p17 = all(tp17);
            var p18 = all(tp18);

            //left side tris
            var tp19 = pt(0, 1, 0);
            var tp20 = pt(0, 0, 1.1);
            var tp21 = pt(0, 1, 0);

            var p19 = all(tp19);
            var p20 = all(tp20);
            var p21 = all(tp21);

            var tp22 = pt(0, 0, 1.1);
            var tp23 = pt(0, 1, 1.1);
            var tp24 = pt(0, 1, 0);

            var p22 = all(tp22);
            var p23 = all(tp23);
            var p24 = all(tp24);
            
            //top
            var tp25 = pt(0, 1, 0);
            var tp26 = pt(0, 1, 1.1);
            var tp27 = pt(1, 1, 1.1);

            var p25 = all(tp25);
            var p26 = all(tp26);
            var p27 = all(tp27);

            var tp28 = pt(1, 1, 1.1);
            var tp29 = pt(0, 1, 0);
            var tp30 = pt(1, 1, 0);

            var p28 = all(tp28);
            var p29 = all(tp29);
            var p30 = all(tp30);

            //bottom
            var tp31 = pt(0, 0, 0);
            var tp32 = pt(0, 0, 1.1);
            var tp33 = pt(1, 0, 1.1);

            var p31 = all(tp31);
            var p32 = all(tp32);
            var p33 = all(tp33);

            var tp34 = pt(1, 0, 1.1);
            var tp35 = pt(0, 0, 0);
            var tp36 = pt(1, 0, 0);

            var p34 = all(tp34);
            var p35 = all(tp35);
            var p36 = all(tp36);
            
            dt(p, p2, p3);
            dt(p4, p5, p6);
            
            dt(p7, p8, p9);
            dt(p10, p11, p12);
            
            dt(p13, p14, p15);
            dt(p16, p17, p18);
            
            dt(p19, p20, p21);
            dt(p22, p23, p24);

            dt(p25, p26, p27);
            dt(p28, p29, p30);

            dt(p31, p32, p33);
            dt(p34, p35, p36);
            
            ctx.closePath();
            ctx.stroke();
        }, fps);
        break;
    
}

问题分析与修复方案

1. X轴旋转失效的核心原因

原代码未在变换流程中应用X轴旋转矩阵,所有点只做了缩放和透视投影,没有旋转步骤。

修复:添加X轴旋转矩阵定义,并插入到变换链中:

// 行向量形式的X轴旋转矩阵
function getXRotationMatrix(angle) {
  const c = Math.cos(angle);
  const s = Math.sin(angle);
  return [
    [1, 0, 0, 0],
    [0, c, -s, 0],
    [0, s, c, 0],
    [0, 0, 0, 1]
  ];
}

// 修改all函数,加入旋转步骤
function all(p) {
  const sp = matmul(p, sm);
  const rotXMat = getXRotationMatrix(tta);
  const rotated = matmul(sp, rotXMat);
  const np = matmul(rotated, prjM);
  return np;
}

2. 透视除法效果不明显的原因

模型的z值范围是0~1.1,过于接近znear(0.1),导致透视矩阵计算出的w值(等于z)接近1,透视除法后坐标无明显变化。

修复:将模型沿z轴向后平移,增加z值范围:

// 平移矩阵:将模型向后移动5个单位
const translateMat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 5, 1]];

// 更新all函数,变换顺序:缩放→平移→旋转→透视
function all(p) {
  const sp = matmul(p, sm);
  const translated = matmul(sp, translateMat);
  const rotXMat = getXRotationMatrix(tta);
  const rotated = matmul(translated, rotXMat);
  const np = matmul(rotated, prjM);
  return np;
}

3. 左侧面三角形点错误

原代码中左侧面的tp19与tp21是同一个点,导致无法形成有效三角形。

修复:修正左侧面点定义:

// 左侧面 tris
var tp19 = pt(0, 1, 0);
var tp20 = pt(0, 0, 0);
var tp21 = pt(0, 1, 1.1);

var p19 = all(tp19);
var p20 = all(tp20);
var p21 = all(tp21);

var tp22 = pt(0, 0, 0);
var tp23 = pt(0, 0, 1.1);
var tp24 = pt(0, 1, 1.1);

var p22 = all(tp22);
var p23 = all(tp23);
var p24 = all(tp24);

4. 其他优化

  • 用Math.PI替代硬编码的3.14159,提升精度
  • 将函数定义移出setInterval,避免重复创建函数浪费性能

修复后完整代码

const canvas = document.createElement("canvas");
canvas.setAttribute("id", "myCanvas");
canvas.setAttribute("width", 1000);
canvas.setAttribute("height", 500);
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");

const fps = 1000 / 24;
let theta = 0;

// 点构造函数
function pt(x, y, z) {
  return [x, y, z, 1];
}

// 画线函数
function drawL(p, p2) {
  ctx.moveTo(p[0] + canvas.width * 0.5, -p[1] + canvas.height * 0.5);
  ctx.lineTo(p2[0] + canvas.width * 0.5, -p2[1] + canvas.height * 0.5);
}

// 画三角形函数
function dt(p, p2, p3) {
  drawL(p, p2);
  drawL(p2, p3);
  drawL(p3, p);
}

// 矩阵乘法(行向量 × 矩阵)
function matmul(point, m) {
  const x = point[0] * m[0][0] + point[1] * m[0][1] + point[2] * m[0][2] + point[3] * m[0][3];
  const y = point[0] * m[1][0] + point[1] * m[1][1] + point[2] * m[1][2] + point[3] * m[1][3];
  const z = point[0] * m[2][0] + point[1] * m[2][1] + point[2] * m[2][2] + point[3] * m[2][3];
  const w = point[0] * m[3][0] + point[1] * m[3][1] + point[2] * m[3][2] + point[3] * m[3][3];

  if (w !== 0) {
    x /= w;
    y /= w;
    z /= w;
  }
  return [x, y, z, w];
}

// 获取X轴旋转矩阵
function getXRotationMatrix(angle) {
  const c = Math.cos(angle);
  const s = Math.sin(angle);
  return [
    [1, 0, 0, 0],
    [0, c, -s, 0],
    [0, s, c, 0],
    [0, 0, 0, 1]
  ];
}

// 透视投影矩阵参数
const znear = 0.1;
const zfar = 1000;
const fov = (90 / 180) * Math.PI;
const aspectRatio = canvas.height / canvas.width;

const prjM = [
  [aspectRatio * (1 / Math.tan(fov / 2)), 0, 0, 0],
  [0, 1 / Math.tan(fov / 2), 0, 0],
  [0, 0, -(zfar / (zfar - znear)), -(zfar * znear / (zfar - znear))],
  [0, 0, 1, 0]
];

const sm = [[300, 0, 0, 0], [0, 150, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]];
const translateMat = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 5, 1]];

function all(p) {
  const sp = matmul(p, sm);
  const translated = matmul(sp, translateMat);
  const rotXMat = getXRotationMatrix((theta / 180) * Math.PI);
  const rotated = matmul(translated, rotXMat);
  return matmul(rotated, prjM);
}

// 定义立方体所有三角形点
const cubeTris = [
  // 前面
  [pt(0,0,0), pt(1,0,0), pt(1,1,0)],
  [pt(1,1,0), pt(0,1,0), pt(0,0,0)],
  // 后面
  [pt(0,1,1.1), pt(1,1,1.1), pt(1,0,1.1)],
  [pt(1,0
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 05:35:58