透视投影与深度
先记录两篇博客:
这两篇笔记都是推导透视投影矩阵的,通过第二篇文章可知第一篇文章的推导是基于左手坐标系的,最明显的区别就在于,透视投影矩阵的第四行是(0,0,1,0)还是(0,0,-1,0),前者是左手坐标系,后者是右手坐标系。
相机空间点 (x_cam, y_cam, z_cam, 1)(z_cam 为负,因为在相机前方)→ 透视投影矩阵变换 → 裁剪空间点 (x_clip, y_clip, z_clip, w_clip=-z_cam) → 透视除法 (x_clip/w_clip, y_clip/w_clip, z_clip/w_clip) → NDC。
经过透视投影之后,观察空间转换到裁剪空间,OpenGL采用右手坐标系,w_clip = -z_cam,如果是左手坐标系,w_clip = z_cam。
注意是裁剪空间中的w与观察空间中z的关系。
这个关系是透视除法的关键,因为 w_clip 恰好是物体到相机的距离,我们经过透视除法能营造出“近大远小”的效果。
透视投影还会改变坐标系手性,注意看透视投影矩阵第三行,把z轴翻了个面,所以透视投影之后的裁剪空间,NDC,视口变换都在左手坐标系中。
我们可以利用透视除法(齐次除法),将裁剪空间转换为NDC(Normalized Device Coordinates,标准化设备坐标),NDC中xyz的范围都是[-1,1],其z值就是规范化的深度值,超出这个范围的顶点将被裁剪不做渲染。
1 | vec3 ndc = gl_Position.xyz / gl_Position.w; |
手动计算 NDC 一般都是为了获取规范化的深度值做一些渲染工作。
需要注意 NDC 的 z 轴方向符合左手系特性(+z指向屏幕内)。
在OpenGL渲染管线中,裁剪之后会自动执行透视除法转换到NDC。这里要提及一件事,就是深度缓冲的取值范围是[0,1],深度缓冲中的深度值才是真正的深度值,但是“值越小越近”的逻辑和前面是一样的,切忌和规范化的深度值产生混淆。
计算深度缓冲:
1 | depth = (z_ndc + 1) / 2; // 将[-1,1]转换到[0, 1] |