Fork me on GitHub

透视投影矩阵的推导

透视投影矩阵的推导

本文完全 copy 自 透视投影矩阵的推导 - bluebean - 博客园 (cnblogs.com)

只是用 markdown 将公式全部又打了一遍

image-20240619170325428

图1: View Frustum

Perspective Projection Matrix 的任务就是把位于视锥体内的物体的顶点 (x, y, z) 坐标映射到 [-1, 1] 范围。(如果是 DX 可能是 [0, 1] 范围?)

这相当于把这个四棱台扭曲变形成为一个立方体。这个立方体叫做 规则观察体 (Canonical View Volume, CVV)。如下图

image-20240619170551215

图2 透视投影变换

变换方法或规则

如下图,有一点 P,位于视椎体内,设坐标为 (x,y,z)。分别对 x, y 坐标和 z 坐标变换到 [-1, 1] 的方式进行讨论

1. x, y 坐标的变换方式

  1. 连接视点 eye 与 P 点与 NearClipPlane 交于 P
  2. 设 NearClipPlane 的宽度为 W, 高度为 H, P 点的 x 坐标范围是 [W/2, W/2],y 坐标范围为 [H/2, H/2],然后分别映射至 [-1, 1] 内即可。

image-20240619183747755

图3 x, y 坐标的变换方式

2. z 坐标的变换方式

z 坐标的范围是 N 至 F,需要映射到 [-1, 1]。映射方法暂时按下不表。

透视投影函数形式

void Matrix4X4::initPersProjMatrix(float FOV, const float aspect, float zNear, float zFar)
  • FOV

    • 纵向的视角大小

      image-202406191931682

      图4 透视投影函数参数说明

  • aspect

    • 裁剪面的宽高比
  • zNear

    • NearClipPlane 离 camera 的距离,图 4 中的 n
  • zFar

    • FarClipPlane 离 camera 的距离,图 4 中的 f

计算 P 的投影 P‘ 的归一化 P''

通过这几个参数和三角函数可得 near clip plane 的高度:

(1)tan(FOV/2)=H/2zNear

推出

(2)H=2zNeartan(FOV/2)

因为 aspect=W/H,故

(3)W=2aspectzNeartan(FOV/2)

根据相似三角形

(4)zNearz=yy=xx

求得 P 在 near clip plane 的投影点 P 的坐标

(5)x=xzNearz ,x[W/2,W/2]

(6)y=yzNearz, y[H/2,H/2]

x, y 的范围沿原点对称,只要将它们分别除以 W/2, H/2 ,就可以使其范围位于 [-1, 1] 内。将 式(2), (3) 代入式 (5), (6),得

(7)x=xW/2=2xzNearz2aspectzNeartan(FOV/2)=xzaspecttan(FOV/2)

(8)y=yH/2=2yzNearz2zNeartan(FOV/2)=yztan(FOV/2)

假设 z[1,1],则最后需要的点 P

(9)P=(xzaspecttan(FOV/2),yztan(FOV/2),z)

推导矩阵

寻找一个矩阵使得

[m00m01m02m03m10m11m12m13m20m21m22m23m30m31m32m33][xyz1]=[xzaspecttan(FOV/2)yztan(FOV/2)z1]

我们发现求解

m00x+m01y+m02z+m03=xzaspecttan(FOV/2)

很难找到合适的 m00,m02,因为坐标 x 和 z 是以加法的形式相邻,右边 z 却成为了 x 的分母。

解决方法:

将右边以四维列向量表示的坐标每一项乘以 z ,有:

[m00m01m02m03m10m11m12m13m20m21m22m23m30m31m32m33][xyz1]=[xaspecttan(FOV/2)ytan(FOV/2)zzz]

因此可得矩阵为

[1aspecttan(FOV/2)00001tan(FOV/2)0000m22m230010]

第三行第一列

m22z+m23=zzz=m22+m23/z

又因为 z=zNear 时,z=1

z=zFar 时,z=1

m22+m23/zNear=1m22+m23/zFar=1

联立求得

m22=zFarzNearzNearzFarm23=2zNearzFarzNearzFar

1/tan(FOV/2)=cot(FOV/2),故 投影矩阵为

[cot(FOV/2)aspect0000cot(FOV/2)0000zFarzNearzNearzFar2zFarzNearzNearzFar0010]

将这样的矩阵乘以视锥体中的一个顶点坐标,得到一个新的向量,再将这个向量的每个分量除以第四个分量 w (这一步叫做透视除法,在 GPU 渲染管线中位于 VertexShader 处理之后,由硬件自动完成),之后就得到了 规则立方观察体中的新坐标。

ZBuffer 中的深度值 DepthValue

z 坐标的映射方式的获得,最后我们是为了方便矩阵乘法的操作,反向求得了 z 坐标与 CVV 中的 z 坐标的映射方式:

m22+m23/z=z

可见两者的映射并不是线性的,如下图所示

image-20240619202941119

上图为 深度缓存中的深度值 DepthValue物体距离摄像机深度 ZValue 的关系图,其中 DepthValue=z+12,将深度值从范围 [-1, 1] 转换到 [0,1]

代码示例

void Matrix4X4::initPersProjMatrix(float FOV, const float aspect, float zNear, float zFar)
{
    const float zRange = zNear - zFar;
    const float tanHalfFOV = tanf(ToRadian(FOV / 2.0f));
    
    elements[0][0] = 1.0f / (tanHalfFOV * aspect);
    elements[0][1] = 0.0f;
    elements[0][2] = 0.0f;
    elements[0][3] = 0.0f;
    
    elements[1][0] = 0.0f;
    elements[1][1] = 1.0f / tanHalfFOV;
    elements[1][2] = 0.0f;
    elements[1][3] = 0.0f;
    
    elements[2][0] = 0.0f;
    elements[2][1] = 0.0f;
    elements[2][2] = (-zNear - zFar) / zRange;
    elements[2][3] = (2.0f * zNear * zFar) / zRange;

    elements[3][0] = 0.0f;
    elements[3][1] = 0.0f;
    elements[3][2] = 1.0f;
    elements[3][3] = 0.0f;
}

本文是完全 copy 自 透视投影矩阵的推导 - bluebean - 博客园 (cnblogs.com)

posted @   icewalnut  阅读(192)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示