在 Three.js 或 WebGL 开发中,将屏幕坐标 转换为标准设备坐标(Normalized Device Coordinates, NDC)是一个线性映射的过程。
理解推导过程的关键在于:将一个范围(区间)映射到另一个范围。
1. 明确两个坐标系的定义
-
屏幕坐标系 (Screen Space):
- 范围:从 (左)到 (右)。
- 范围:从 (上)到 (下)。
- 注意: 屏幕坐标的 轴是向下的。
-
标准设备坐标系 (NDC):
- 范围:从 (左)到 (右)。
- range:从 (上)到 (下)。(这是为了匹配 3D 世界坐标,向上为正)。
2. X 轴的推导过程
我们需要将 映射到 。
-
归一化 (Normalize): 先将坐标缩放到 之间。
-
放大区间: 将范围从 变为 (因为 NDC 的区间长度是 )。
-
平移: 将起点从 移动到 。
这就是代码中的:mouse.x = (event.clientX / width) * 2 - 1;
3. Y 轴的推导过程
Y 轴稍微复杂一点,因为屏幕坐标的 Y 轴是向下的(0 在顶部),而 NDC 的 Y 轴是向上的(1 在顶部)。
第一步:归一化 (Normalization)
首先,我们将屏幕上的像素高度 转化为一个比例( 到 之间的数)。
- 公式:
- 结果: 此时,屏幕最上方为 ,最下方为 。
第二步:拉伸区间 (Scaling)
NDC 的总长度是从 到 ,跨度为 。所以我们要把刚才的比例放大 倍。
- 公式:
- 结果: 此时,屏幕最上方为 ,最下方为 。
第三步:平移起点 (Translation)
为了让数值围绕中心分布,从[0, 2] 区间平移到 [-1, 1],我们减去 。
- 公式:
- 结果: 此时,屏幕最上方(原 )变成了 ,最下方(原 )变成了 。
注意: 这里的映射结果是:上方 ,下方 。但这和 3D 世界坐标是相反的(3D 里上方应该是正数)。
第四步:符号翻转 (Inversion) —— 关键一步
为了让上方变成 ,下方变成 ,我们需要给整个结果取反(乘以 )。
-
公式:
-
分配负号: 把负号带进去,就变成了:
总结公式对比
| 坐标点 | 屏幕坐标 (x, y) | NDC 坐标 (x, y) |
|---|---|---|
| 左上角 | ||
| 中心点 | ||
| 右下角 |
通过这个线性推导,Three.js 就能精确地知道鼠标点击在 3D 投影平面上的位置,从而通过 Raycaster(光线投射器)进行物体拾取。
mouse.x = (event.clientX / renderer.domElement.offsetWidth) * 2 - 1;
mouse.y = -(event.clientY / renderer.domElement.offsetHeight) * 2 + 1;