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

如何在JavaScript 2D Canvas中实现3D顶点到2D顶点的投影?

3D点透视投影实现问题

我有一个Camera类,用于将3D点投影到2D平面,但不知道如何正确计算3D点的透视投影。我尝试过将x和y除以z,但我认为这不是正确的透视投影公式。我使用的是左手坐标系(示意图如下):

左手坐标系示意图

(X轴代表左右,Y轴代表上下,Z轴代表前后)

以下是完整代码:

JavaScript代码

function degreesToRadians(degrees) {
  return degrees * Math.PI / 180;
}

function random(min, max) {
  return Math.random() * (max - min) + min;
}

function clamp(min, max, value) {
  if (value < min) {
    value = min;
  } else if (value > max) {
    value = max;
  }

  return value;
}

class Camera {
  constructor(x = 0, y = 0, z = 0, fov = 60, rotation = {
    x: 0,
    y: 0,
    z: 0
  }, zNear = 0.1, zFar = 1000) {
    this.x = x;
    this.y = y;
    this.z = z;
    this.fov = fov;
    this.rotation = rotation;
    this.zNear = zNear;
    this.zFar = zFar;
  }

  project3dPointTo2d(point) {
    // Fix these calculations
    return {
      x: point.x / point.z,
      y: point.y / point.z
    }
  }
}


/**
 * @type { HTMLCanvasElement }
 */
var scene = document.getElementById("scene");
var ctx = scene.getContext("2d");

var vWidth = window.innerWidth;
var vHeight = window.innerHeight;

var fps = 60;
var updateLoop;

var keysDown = [];

function resizeCanvas() {
  vWidth = window.innerWidth;
  vHeight = window.innerHeight;
  scene.width = vWidth;
  scene.height = vHeight;
}

resizeCanvas();

var testcamera = new Camera(0, 0, -10, 60, {
  x: 0,
  y: 0,
  z: 0
}, 0.1, 1000);

var testpoint = {
  x: 0,
  y: 0,
  z: 100
}

function main() {
  // Logic
  if (keysDown["w"]) {
    testcamera.z++;
  }

  if (keysDown["s"]) {
    testcamera.z--;
  }

  if (keysDown["a"]) {
    testcamera.x--;
  }

  if (keysDown["d"]) {
    testcamera.x++;
  }

  var proj2dPoint = testcamera.project3dPointTo2d(testpoint);

  // Drawing
  ctx.clearRect(0, 0, vWidth, vHeight);

  ctx.save();

  ctx.fillStyle = "#000000";
  ctx.beginPath();
  ctx.arc(proj2dPoint.x, proj2dPoint.y, 5, 0, Math.PI * 2);
  ctx.fill();
  ctx.closePath();

  ctx.restore();
}

window.onload = function() {
  updateLoop = setInterval(main, 1000 / fps);
}

window.addEventListener("keydown", (e) => {
  e.preventDefault();
  keysDown[e.key] = true;
});

window.addEventListener("keyup", (e) => {
  e.preventDefault();
  keysDown[e.key] = false;
});

window.addEventListener("resize", (e) => {
  resizeCanvas();
});

CSS代码

*,
*:before,
*:after {
  font-family: roboto, Arial, Helvetica, sans-serif, system-ui;
  padding: 0px 0px;
  margin: 0px 0px;
}

canvas {
  display: block;
}

HTML代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <canvas id="scene"></canvas>
</body>

</html>

(可以看到点出现在屏幕左上角而非中心)


内容的提问来源于stack exchange,提问作者Pete21

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 08:39:52