38 · csm_PositionRaw:直接控制裁剪空间位置
本章依据 CSM 6.4.0 与 Three.js r185。csm_PositionRaw 适合固定屏幕尺寸标记等特殊投影需求,但使用之前必须知道:它只改 GPU 光栅化的位置,不会把其他空间数据一起改对。
1. 类型、默认值和真正的补丁
它是顶点阶段 vec4,普通内置材质上的默认值为:
csm_PositionRaw = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
这已经是 clip space 裁剪空间,不是未投影的局部位置。应以源码为准,不能被源码枚举处含混的“without projection”注释误导。
实际替换保留了原始投影:
#include <project_vertex>
gl_Position = csm_PositionRaw;
因此原来的 mvPosition 仍会计算,但刚生成的 gl_Position 随即被覆盖。后面的对数深度等流程仍有机会继续修改位置;这不是对整个顶点程序最后一行的控制权。
默认表达式不包含实例矩阵、批量矩阵、morph、skinning 或位移贴图产生的位置变化。即使只写 csm_PositionRaw = csm_PositionRaw;,也不能假定它等价于所有内置变换完成后的 gl_Position。
6.4.0 用字符串 includes() 判断输出是否出现。csm_PositionRaw 的名字还包含 csm_Position,所以会同时激活位置初值补丁;没有另外写 csm_Position 时,它保持默认 attribute position。因此也会继承第 37 章所述的 alphaHash 初值丢失问题。两个输出不会自动关联:先修改 csm_Position,再使用默认 Raw 值,最终 Raw 投影仍可能把前面的变形覆盖掉。
2. clip 与 NDC 的区别
GPU 在透视除法后得到:
若希望屏幕上产生固定 NDC 偏移 clip.xy += delta * clip.w。漏乘 w,透视除法会让偏移随着距离改变。直接把所有位置写为 vec4(ndc, 1.0) 也意味着改变了裁剪与深度语义,不能当成普通三维投影的无损替代。
3. 案例:始终面向相机的固定像素标记
完整例子 使用无光照 MeshBasicMaterial 和 1×1 的 XY 平面。对象原点投影为中心,再在屏幕水平/垂直方向展开矩形。
vec4 centerClip = projectionMatrix * modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0);
vec2 offsetNdc = position.xy * uCsmBillboardPixels * 2.0 / uCsmViewportCss;
csm_PositionRaw = centerClip;
csm_PositionRaw.xy += offsetNdc * centerClip.w;
由于 position.xy 为 [-0.5, 0.5],NDC 横向跨度是 2 * pixels / viewportWidth,映射到页面后恰好是目标 CSS 像素宽度。uCsmViewportCss 来自 canvas 的 getBoundingClientRect(),与目标尺寸使用同一单位;这里不需要再乘 devicePixelRatio。
相机绕场景运动时,矩形保持面向屏幕且尺寸稳定。所有顶点使用中心的 z/w,得到一个位于该深度的屏幕平面。本例默认开启深度测试,仍可被场景中更靠近相机的几何遮挡。
4. 为什么不用 Standard 做这个演示
Standard 的 vViewPosition、normal、世界坐标和阴影坐标依然来自原顶点管线。只修改 gl_Position,会让光照以为几何仍在旧位置。Basic 材质避开了这类光照需求;例子同时不使用雾、裁剪平面、阴影和 transmission,不能据此宣称这些功能已与 billboard 一致。
当项目需要真实三维 billboard 的光照或碰撞,优先考虑 CPU 更新朝向、Sprite,或在观察空间构造位置后同步所有依赖数据。csm_PositionRaw 不是完整 billboard 系统。
5. 剔除、拾取与边界条件
例子关闭该小标记的 frustumCulled,因为固定像素面积不对应固定世界半径。数量很大时应该做显式屏幕范围判断,不能无限制关闭全部对象剔除。
Raycaster 仍检测原始平面,且材质面朝向与屏幕标记可能不同。交互可以直接比较鼠标与投影中心的屏幕矩形;真实几何拾取需要另一套同步策略。近裁剪面、相机后方和中心 w 接近零时,应制定隐藏策略,避免把跨越相机平面的标记当作普通可见对象。本例的固定相机轨迹始终让标记在相机前方。
练习(暂不提供答案)
- 删除
centerClip.w乘法,移动相机距离,解释尺寸变化。 - 将标记尺寸改为独立的宽高 Vector2,并保持 CSS 像素语义。
- 为标记实现屏幕矩形点击检测,正确处理 canvas 的页面偏移。
- 对比 CPU billboard 与 Raw 投影 billboard 在阴影、Raycaster 和实例化上的实现成本。