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

Three.js是否应将顶点w值设为深度?透视投影畸变疑问

问题:Three.js难道不应该将顶点的w值设置为其深度吗?

将物体投影到屏幕时,远离视点的物体会向屏幕中心偏移。在齐次坐标中,这种透视畸变效果是通过将点的(x, y, z)坐标除以其到视点的距离w来实现的。但我研究Three.js的投影矩阵后发现,它似乎并没有这么做。

请看以下场景:

// src/main.ts
import { AmbientLight, BoxGeometry, DirectionalLight, Mesh,
MeshPhongMaterial, PerspectiveCamera, Scene, WebGLRenderer } from "three";

const canvas = document.getElementById('canvas') as HTMLCanvasElement;
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;

const renderer = new WebGLRenderer({
  alpha: false,
  antialias: false,
  canvas: canvas,
  depth: true
});

const scene = new Scene();

const camera = new PerspectiveCamera(45, canvas.width / canvas.height, 0.01, 100);
camera.position.set(0, 0, 10);

const light = new DirectionalLight();
light.position.set(-1, 0, 3);
scene.add(light);
const light2 = new AmbientLight();
scene.add(light2);

const cube = new Mesh(new BoxGeometry(1, 1, 1, 1), new MeshPhongMaterial({ color: `rgb(0, 125, 125)` }));
scene.add(cube);
cube.position.set(3.42, 3.42, 0);

renderer.render(scene, camera);
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  </head>
  <body>
    <div id="app" style="width: 600px; height: 600px;">
      <canvas id="canvas" style="width: 100%; height: 100%;"></canvas>
    </div>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>

这段代码生成如下图像:
threejs场景中右上角远处的立方体无畸变

注意青绿色立方体的边缘与画布边缘完全平行,但它的前右上顶点比前左下顶点离眼睛更远,难道这个右上顶点不该向中心轻微畸变吗?

我了解WebGL会在顶点后处理阶段自动将顶点除以w坐标。Three.js难道不该用深度来设置w以实现这种畸变效果吗?

我预想的效果如下例所示:

import { AmbientLight, BoxGeometry, Mesh, MeshBasicMaterial, PerspectiveCamera,
  Scene, ShaderMaterial, SpotLight, TextureLoader, Vector3, WebGLRenderer } from "three";


const canvas = document.getElementById('canvas') as HTMLCanvasElement;
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;

const loader = new TextureLoader();
const texture = await loader.loadAsync('bricks.jpg');

const renderer = new WebGLRenderer({
  alpha: false,
  antialias: true,
  canvas: canvas,
  depth: true
});

const scene = new Scene();

const camera = new PerspectiveCamera(45, canvas.width / canvas.height, 0.01, 100);
camera.position.set(0, 0, -1);
camera.lookAt(new Vector3(0, 0, 0));

const light = new SpotLight();
light.position.set(-1, 0, -1);
scene.add(light);
const light2 = new AmbientLight();
scene.add(light2);

const box = new Mesh(new BoxGeometry(1, 1, 1, 50, 50, 50), new MeshBasicMaterial({
  map: texture
}));
scene.add(box);
box.position.set(-0.6, 0, 1);

const box2 = new Mesh(new BoxGeometry(1, 1, 1, 50, 50, 50), new ShaderMaterial({
  uniforms: { 
    tTexture: {value: texture } 
  },
  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      vec4 clipSpacePos = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
      clipSpacePos.w = length(clipSpacePos.xyz);
      gl_Position = clipSpacePos;
    }
  `,
  fragmentShader: `
    varying vec2 vUv;
    uniform sampler2D tTexture;
    void main() {
      gl_FragColor = texture2D(tTexture, vUv);
    }
  `,
}));
scene.add(box2);
box2.position.set(0.6, 0, 1);

renderer.render(scene, camera);

该代码生成如下输出:
两个带砖块纹理的立方体,左侧边缘有强烈畸变

注意左侧立方体的边缘有明显的向中心畸变效果。


回答

首先得明确:Three.js的透视投影矩阵已经正确设置了w值,你看到的“无畸变”是场景设置导致透视效果太弱,而非Three.js没做透视处理。

核心逻辑

标准OpenGL/Three.js的透视投影中,透视矩阵会把顶点转换到裁剪空间,此时w值被设置为视点空间中的-z值(Three.js用右手坐标系,相机看向-z方向)。WebGL后续会自动执行透视除法(除以w),完成透视畸变——让远处物体更小、向中心收缩。

第一个场景看不到畸变的原因

  1. 比例问题:相机在z=10位置,立方体在z=0,距离10个单位,但x/y仅偏移3.42,这个偏移量相对于距离来说极小,透视畸变的程度本来就非常弱,肉眼几乎察觉不到。
  2. 立方体尺寸太小:1x1的立方体在600px画布上渲染后,边缘的偏移量可能只有几个像素,远低于人眼分辨阈值。

自定义Shader的效果为什么不一样

你手动把w设为length(clipSpacePos.xyz),这不是标准透视投影,而是额外叠加了一次非线性径向畸变。标准透视的w是基于视点空间z值的线性值,而你用的是距离模长,会让畸变呈放射状夸张放大,所以效果和标准透视完全不同。

验证Three.js透视效果的方法

把立方体的x/y位置拉远,比如设为cube.position.set(8, 8, 0),或者把相机靠近到z=2的位置,你就能明显看到立方体边缘不再平行于画布,会向中心收缩——这就是标准的透视畸变。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 02:31:02