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
相关产品推荐
相关产品推荐

