04 · Camera Space:把世界放到相机面前

返回目录 · 上一章 · 下一章

Camera Space 与 View Space 在本系列中指同一空间:以相机为原点、以相机轴为基底表达位置。它不是屏幕空间,也没有执行透视投影。

1. 为什么要换到相机坐标

拍摄者向右移动,静止物体在取景器里向左移动。为了使用统一投影公式,我们可以先把所有世界点转成“相机看到的位置”,再交给投影矩阵。

相机世界矩阵 C 把相机局部点变到世界,视图矩阵则走反方向:

V=C−1,pview=Vpworld V=C^{-1},\qquad p_{view}=Vp_{world}

在相机及其父层级保持单位缩放、没有剪切时,Three.js 中对应 camera.matrixWorld 和 camera.matrixWorldInverse。本系列的相机公式和示例都采用这个前提。

r185 的 Camera 更新方法会从世界矩阵分解位置、旋转和缩放,再排除缩放构造视图矩阵。因此缩放相机或其父节点后,matrixWorldInverse 不一定严格等于 matrixWorld 的数学逆;不要通过缩放相机实现 zoom。使用透视相机的 fov/zoom 或正交相机的视域参数,并调用 updateProjectionMatrix()。

2. 相机轴与深度符号

常规 Three.js 相机沿自身局部 -Z 看,+X 为右、+Y 为上。因此相机前方的点通常有负的 view.z。不要据此断言世界 z 为负的点一定在前面:相机可以转身。

import * as THREE from 'three';

const camera = new THREE.PerspectiveCamera(60, 1, 0.1, 100);
camera.position.set(0, 0, 5);
camera.lookAt(0, 0, 0);
camera.updateWorldMatrix(true, false);
const originWorld = new THREE.Vector3(0, 0, 0);
const originView = originWorld.clone().applyMatrix4(camera.matrixWorldInverse);
console.log(originView.toArray()); // [0, 0, -5]

物体在前方 5 个单位。相机移到 z=10,物体的世界位置没有变,但 view.z 变为 -10。

-view.z 是沿视线轴的深度,不是到相机的欧氏距离。view 点 (3,0,-4) 的轴向深度是 4,距离是 5。

3. Model-view 矩阵

对于局部顶点,可以合并两次变换:

pview=VMplocal p_{view}=VMp_{local}

在 ShaderMaterial 顶点着色器中,Three.js 提供 modelViewMatrix,它是当前对象的模型世界矩阵与相机视图矩阵的组合:

vec4 positionView = modelViewMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * positionView;

position 是顶点 attribute,JavaScript 上传几何数据后,每个顶点分别运行这段程序。gl_Position 必须是裁剪坐标,所以还有 projectionMatrix 这一步。

4. 相机空间中的光照与方向

如果表面法线在 view space,光照方向也必须变到 view space 才能点积。仅给变量命名 lightDirection 不足以保证正确。

const lightDirectionWorld = new THREE.Vector3(1, 2, 1).normalize();
const lightDirectionView = lightDirectionWorld.clone()
  .transformDirection(camera.matrixWorldInverse);

这表示一个不含平移的单位朝向。点光源应先把光源位置变到 view space,再减去表面 view position。相机通常保持单位缩放;复杂缩放父节点会增加朝向解释和控制器行为的不确定性,应谨慎设计层级。

5. 前方检测的边界

positionView.z < 0 只能说明在相机前方,不能说明它在视锥里,也不能说明没被其他物体挡住。完整显示判断还需要投影后的范围,遮挡则需要射线或深度相关方法。

练习(不附答案)

  1. 相机位于 (5,0,0) 并看向原点,预测原点的 view 坐标,再验证。
  2. 构造一个在相机前方但在画面外的点,说明单独判断 view.z 的不足。
  3. 比较 view 点 (0,0,-5) 与 (4,0,-5) 的轴向深度和距离。
  4. 保持相机及父层级单位缩放,移动相机后验证 matrixWorld * matrixWorldInverse 约为单位矩阵。