从世界坐标计算归一化设备坐标(NDC)范围异常排查
3D点转归一化设备坐标(NDC)时x值超出[-1,1]范围,投影矩阵是否存在问题?
我尝试实现了一个表示3D点的Vector3对象,用于将世界坐标转换为归一化设备坐标(NDC)。按理解,NDC的x、y、z值应处于[-1, 1]区间,但当前实现的结果并不总是符合该范围。以下是我的代码实现:
import math import numpy as np WIN_SIZE: int = 800 # square window ASPECT_RATIO: float = 1 # square window FOV: float = math.radians(60) SCALING_FACTOR: float = 1 / math.tan(FOV / 2) Z_FAR: float = 100 Z_NEAR: float = 1 class Vector3: def __init__(self, x: float, y: float, z: float) -> None: self.x: float = x self.y: float = y self.z: float = z def xyzw(self) -> np.array: return np.array([self.x, self.y, self.z, 1]) def projection_matrix(self) -> np.array: m = np.zeros((4, 4)) m[0, 0] = ASPECT_RATIO * SCALING_FACTOR m[1, 1] = SCALING_FACTOR m[2, 2] = Z_FAR / (Z_FAR - Z_NEAR) m[2, 3] = (-Z_FAR * Z_NEAR) / (Z_FAR - Z_NEAR) m[3, 2] = 1 return m def normalized_device_coordinates(self) -> np.array: result = np.dot(self.projection_matrix(), self.xyzw()) x, y, z, w = result if w != 0: result[0] /= w result[1] /= w result[2] /= w return result
测试用例Vector3(6, 0, 10).normalized_device_coordinates()的输出为[ 1.03923048, 0, 0.90909091, 10],其中x值超出了[-1, 1]范围。请问是否是我的投影矩阵实现存在问题?
问题分析与解决
测试点超出视锥体导致x值越界
你的测试点Vector3(6, 0, 10)本身位于视锥体之外,所以NDC x值超出[-1,1]是正常现象:
- 视锥体在z=10位置的x方向半宽计算公式为:
z * tan(FOV/2) - 代入数值:
10 * tan(30°) ≈ 10 * 0.577 = 5.77 - 你的测试点x=6,已经超过这个半宽,因此投影后NDC x>1,属于视锥体外的点,后续会被裁剪流程剔除。
投影矩阵的z分量不符合[-1,1]的NDC要求
你当前的投影矩阵生成的z值范围是[0,1],而非你预期的[-1,1]。若要让z也落在[-1,1]区间,需要修改投影矩阵的第3行:
修改前的z行:
m[2, 2] = Z_FAR / (Z_FAR - Z_NEAR) m[2, 3] = (-Z_FAR * Z_NEAR) / (Z_FAR - Z_NEAR)
修改后的z行(适配NDC z∈[-1,1]):
m[2, 2] = -(Z_FAR + Z_NEAR) / (Z_FAR - Z_NEAR) m[2, 3] = -2 * Z_FAR * Z_NEAR / (Z_FAR - Z_NEAR)
修改后,视锥体内的点(z∈[Z_NEAR, Z_FAR])的NDC z值会落在[-1,1]区间内。
验证修改效果
以测试点Vector3(5.77, 0, 10)(刚好在视锥边缘)为例:
- 修改前NDC x≈1.0,z≈0.909
- 修改后NDC x≈1.0,z≈-0.82(落在[-1,1]范围内)
而原测试点Vector3(6,0,10)即使修改矩阵,NDC x仍会>1,因为它本身就在视锥体外,这是合理的。
内容的提问来源于stack exchange,提问作者Kirk KD
相关产品推荐
相关产品推荐

