JavaScript实现简易第一人称3D相机及Canvas 3D矩形问题问询
问题解答:简易第一人称3D相机与Canvas 3D矩形实现
一、Canvas原生没有直接绘制3D矩形的函数
Canvas是2D绘图API,不存在原生的3D矩形绘制函数。所有3D视觉效果都需要通过透视投影计算,把3D空间坐标转换为2D屏幕坐标后,用2D绘图API(如beginPath()、lineTo())模拟实现。
二、实现支持3D参数的矩形绘制与移动
1. 核心:透视投影原理
3D点转2D屏幕坐标的基础公式(模拟人眼近大远小):
// 把3D坐标(x,y,z)转换为Canvas屏幕坐标 function projectPoint(x, y, z, camera) { const fov = 60; // 视角,控制视野宽窄 const scale = Math.tan((fov * Math.PI / 180) / 2) * canvas.height; // 基于相机位置计算屏幕坐标 const screenX = canvas.width/2 + ((x - camera.x) * scale) / (z - camera.z); const screenY = canvas.height/2 + ((y - camera.y) * scale) / (z - camera.z); return { x: screenX, y: screenY }; }
2. 自定义3D矩形绘制函数
接收x,y,depth(z),width,height参数,计算矩形四个顶点的3D坐标,转成2D后绘制:
function draw3DRect(x, y, z, width, height, camera) { // 定义矩形四个顶点的3D坐标 const vertices = [ { x, y, z }, { x: x+width, y, z }, { x: x+width, y: y+height, z }, { x, y: y+height, z } ]; // 把所有顶点转成2D屏幕坐标 const screenVertices = vertices.map(v => projectPoint(v.x, v.y, v.z, camera)); // 用2D API绘制矩形 ctx.beginPath(); ctx.moveTo(screenVertices[0].x, screenVertices[0].y); screenVertices.forEach(v => ctx.lineTo(v.x, v.y)); ctx.closePath(); ctx.strokeStyle = '#222'; ctx.stroke(); ctx.fillStyle = 'rgba(100, 200, 255, 0.6)'; ctx.fill(); }
3. 实现矩形的水平/垂直移动
本质就是修改矩形的x(水平)或y(垂直)参数,在动画循环中重新计算投影并绘制。比如用键盘控制:
document.addEventListener('keydown', e => { const speed = 0.1; switch(e.key) { case 'ArrowLeft': rect.x -= speed; break; case 'ArrowRight': rect.x += speed; break; case 'ArrowUp': rect.y -= speed; break; case 'ArrowDown': rect.y += speed; break; } });
三、你的代码问题修复
你提供的代码存在几个关键问题:
- 矩阵变换逻辑错误:用了错误的三角函数组合,没有遵循正确的3D投影/旋转公式
- 变量不规范:
x2、y2等未用let/const声明,容易造成全局污染 - 无正确透视计算:直接加减
z值无法模拟3D深度效果 - 错误API调用:
ctx.reset()不是Canvas标准方法,应该用ctx.clearRect()清除画布
下面是修正后的完整示例(包含相机、矩形绘制和移动):
const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; // 相机参数 const camera = { x: 0, y: 0, z: 5 }; // 矩形参数 const rect = { x: 0, y: 0, z: 2, width: 2, height: 2, speed: 0.1 }; // 3D转2D投影函数 function projectPoint(x, y, z) { const fov = 60; const scale = Math.tan((fov * Math.PI / 180) / 2) * canvas.height; const screenX = canvas.width/2 + ((x - camera.x) * scale) / (z - camera.z); const screenY = canvas.height/2 + ((y - camera.y) * scale) / (z - camera.z); return { x: screenX, y: screenY }; } // 绘制3D矩形 function draw3DRect() { const vertices = [ { x: rect.x, y: rect.y, z: rect.z }, { x: rect.x+rect.width, y: rect.y, z: rect.z }, { x: rect.x+rect.width, y: rect.y+rect.height, z: rect.z }, { x: rect.x, y: rect.y+rect.height, z: rect.z } ]; const screenVertices = vertices.map(v => projectPoint(v.x, v.y, v.z)); ctx.beginPath(); ctx.moveTo(screenVertices[0].x, screenVertices[0].y); screenVertices.forEach(v => ctx.lineTo(v.x, v.y)); ctx.closePath(); ctx.stroke(); ctx.fillStyle = 'rgba(100,200,255,0.6)'; ctx.fill(); } // 键盘控制移动 document.addEventListener('keydown', e => { switch(e.key) { case 'ArrowLeft': rect.x -= rect.speed; break; case 'ArrowRight': rect.x += rect.speed; break; case 'ArrowUp': rect.y -= rect.speed; break; case 'ArrowDown': rect.y += rect.speed; break; } }); // 动画循环 function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); draw3DRect(); requestAnimationFrame(animate); } animate();
四、简易第一人称3D相机实现思路
第一人称相机的核心是让相机跟随用户输入移动/旋转:
- 移动:通过键盘控制
camera.x、camera.y、camera.z的增减,模拟前进/后退/左右移动 - 旋转:通过鼠标移动计算相机的俯仰角(pitch)和偏航角(yaw),对所有3D点先做旋转变换再投影,模拟转头效果
示例旋转逻辑:// 绕Y轴旋转(偏航,左右转头) function rotateY(x, z, yaw) { const cos = Math.cos(yaw); const sin = Math.sin(yaw); return { x: x * cos - z * sin, z: x * sin + z * cos }; }
内容的提问来源于stack exchange,提问作者mastersam
相关产品推荐
相关产品推荐

