坐标系统

OpenGL需要渲染点从CPU到GPU再到图片需要进行一系列的空间变换,每个空间变化对应不同的空间系统。
在每次顶点着色器运行完过后,传入片段着色器都为标准化坐标,及x、y、z的坐标堵在$[-1, 1]$范围内。
这就需要我门在顶点着色器进行一系列空间变换,我们将在顶点着色器进行的空间变化为标准化设备坐标(NDC)N的过程可划分为四个不同坐标系统:

  • 局部空间(Local Space,或者称为物体空间(Object Space)
  • 世界空间(World Space)
  • 观察空间(View Space,或者称为视觉空间(Eye Space)
  • 裁剪空间(Clip Space)
    之后在片段着色器到屏幕坐标的空间变化可划分的坐标系统:
  • 屏幕空间(Screen Space)
    coordinate_systems.png

局部空间

局部空间就是创建顶点从CUP传入顶点着色器里面的空间,说白了就是初始位置位置和大小,由于初始的位置大小不同、之后将其作用Model矩阵后其变化的位置也不同

世界空间

通过Model矩阵运算后,我们可以称现在变化为空间为世界坐标系

观察空间

在世界坐标找一个观察点(相机位置),通过View矩阵运算,我们可以讲其坐标空间称为观察空间。
事实上就是以摄像机在世界坐标上的位置为原点、姿态为坐标轴从新规划坐标系、事实上是通过glm::lookat来计算出View矩阵
注意:观察者空间摄影机方向$forward$为z的负轴
下面为lookat实现代码

glm::mat4 lookat(const glm::vec3& eye, const glm::vec3& center, glm::vec3& world_up) {
    // 观察空间坐标轴 注意z_aixs方向与相机相机朝向相反
    glm::vec3 z_aixs = glm::normalize(eye - center);
    glm::vec3 x_aixs = glm::normalize(glm::cross(glm::normalize(world_up), z_aixs));
    glm::vec3 y_aixs = glm::cross(z_aixs, x_aixs);

    glm::mat4 translate = glm::translate(glm::mat4(1.0), -eye);

    glm::mat4 view(1.0);
    view[0] = glm::vec4(x_aixs, 0.0);
    view[1] = glm::vec4(y_aixs, 0.0);
    view[2] = glm::vec4(z_aixs, 0.0);

    // 先讲相机平移到原点 之后根据局部坐标的逆矩阵(正交转置即可)变化为观察者空间
    return glm::inverse(view) * translate; 
}

裁剪空间

由于我们传入片段着色器的坐标要是NDC、我们必须通过投影矩阵,我们可以设定观察箱。将顶点被变换到裁剪空间之后通过GPU管道变为

其中投影矩阵又分为正交投影、透视投影

透视投影

透视投影它将所有顶点数据从观察空间转换为剪切空间。然后,这些剪辑坐标也通过与剪辑坐标的 w 分量除法转换为归一化设备坐标 (NDC)

下图显示了眼空间中的点 $(x_e, y_e, z_e)$如何在近平面上投影到 $(x p, y p, z p)$

由于三角形相似比例得 $x_p$, $y_p$ 也以类似的方式计算

接下来需要我们将$(x_p, y_p)$与NDC关联起来 其中$[l, r]->[-1, 1] [b, t]->[-1, 1]$

将$(x_p, y_p)$带入得到$(x_c, y_c)$与NDC的关系

这样我们初步构建出矩阵

由于我们知道 z 不依赖于 x 或 y 值,因此我们借用 w 分量来查找 $z_n$ 和 $z_e$ 之间的关系

为了找到系数 A 和 B,我们使用$(z_e, z_n)$ 带入特殊点 $(-n, -1)$ 和 $(-f,1)$,并将它们放入上式中,获得方程组

求得A B解

最后完整矩阵

假如 如果观察点观察箱对称(例如原点向-z轴看),及$r = -l t = -b$ 那么可以化简个更简便的矩阵

正交投影

正交投影矩阵构造同上

标准设备化坐标

再转换NDC之前,先进行视锥剔除 其中满足$w_c < x_c, y_c, z_c < w_c$才不会被题主,之后我们将剪切向量的x,y,z分量分别除以向量的齐次w分量(w相当于起作用)——即可将其变化为NDC。

ps:NDC是左手坐标系 -z表示距离眼睛进

屏幕空间

OpenGL会使用glViewPort内部的参数来将标准化设备坐标映射到屏幕坐标(指定关联屏幕的左下角和长宽),每个坐标都关联了一个屏幕上的(x, y)像素坐标的 颜色缓存和深度缓存等(深度越小距离观察点越近)。这个过程称为视口变换

屏幕坐标转换.png

$$ \left( \begin{array}{l} x_w \\ y_w \\ d \end{array}\right) = \left( \begin{array}{l} \frac {w}{2} \times x_{ndc} + (x + \frac {w}{2}) \\ \frac {h}{2} \times y_{ndc} + (y + \frac {h}{2}) \\ \frac {f-n}{2} * z_{ndc} + \frac {f-n}{2} \end{array}\right) $$

参考文档

LearnOpenGL

顶点从对象坐标系到屏幕坐标系的计算流程

glViewport

glDepthRange

计算OpenGL投影矩阵

最后修改:2023 年 01 月 22 日
如果觉得我的文章对你有用,请随意赞赏