顶点位置的 Vulkan 默认坐标系统

Des*_*ess 1 vulkan

我正在通过开发玩具渲染器来研究 Vulkan 坐标系统。我对顶点位置的坐标感到困惑。在线 Vulkan 信息,例如: https://matthewwellings.com/blog/the-new-vulkan-cooperative-system/

...提及 +X 是正确的,+Y 是向下的,+Z 是回来的。

在我的渲染器中,+Z 指向前方,我不明白为什么。我有一个这样定义的三角形:

    // CCW is facing forward
    std::vector<PosColorVertex> vertexBuffer = {
        {{ 0.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}},
        {{-1.0f, 1.0f, 0.0f},  {0.0f, 1.0f, 0.0f}},
        {{ 1.0f, 1.0f, -5.0f}, {0.0f, 0.0f, 1.0f}},
    };
Run Code Online (Sandbox Code Playgroud)

-5(Z) 将顶点移回屏幕中。应该是 +5 才能做到这一点。

坐标系看起来是这样的: 在此输入图像描述

如果我将相机放在原点,它看起来像这样:

在此输入图像描述

另一张镜头,相机远离三角形(Z 轴上的视图由 -4 转换)。

在此输入图像描述

一些相关代码。模型和视图矩阵都是恒等的。

对比:

 outColor = inColor;
 gl_Position = ubo.projectionMatrix * ubo.viewMatrix * ubo.modelMatrix * vec4(inPos.xyz, 1.0);
Run Code Online (Sandbox Code Playgroud)

FS:

outFragColor = vec4(inColor, 1.0);
Run Code Online (Sandbox Code Playgroud)

投影为:

glm::perspective(glm::radians(60.0f), w/h, 0.1, 256.0);
Run Code Online (Sandbox Code Playgroud)

krO*_*oze 6

剪辑和标准化设备坐标

剪辑坐标是我们从顶点着色器获得的坐标。标准化设备坐标 (NDC) 相同,但除以w。有两种常见的用户选项(左撇子和右撇子):

NDH

“向上”的含义实际上取决于您。但是,如果您希望它与几乎所有演示引擎兼容,则您希望“up”在视口变换后表示-y (因此在 NDH 中,在普通视口变换的情况下,您的“up”应该是-y,或者应该是+y以防视口变换稍后翻转它)。

在帧缓冲区\图像坐标中选择“up”始终为-y是因为几乎所有呈现引擎上的表面坐标都假定左上角原点:

表面坐标

许多图像文件格式也假设相同。

世界坐标

世界坐标完全取决于您。通过顶点着色器,您可以将世界坐标转换为 Vulkan 可以处理的剪辑坐标。

您通过您的glm::perspective.

让我们首先看看我们有什么:

std::vector<PosColorVertex> vertexBuffer = {
    {{ 0.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}},
    {{-1.0f, 1.0f, 0.0f},  {0.0f, 1.0f, 0.0f}},
    {{ 1.0f, 1.0f, -5.0f}, {0.0f, 0.0f, 1.0f}},
};
Run Code Online (Sandbox Code Playgroud)

现在,这又取决于对其实际含义的解释。我们需要另一个“向上”的参考方向。

为了保持理智,我们可能希望“向上”朝着y递增的方向。这意味着我们在底部有一些红角。我们在左上角有一个绿色的角。我们在右上角有一个蓝色的角。或者说我认为这是作者的意图。

此外,为了保持理智,我们更喜欢右手坐标系。因此,如果我们选择+y表示“向上”,+x表示“右侧”,那么-z必须是“前面”(+z必须是“后面”):

世界坐标

(这与 Blender 拥有世界坐标的方式相匹配。)不过现在我们陷入了困境。我们的z是负数,而不是 NDH 要求的正数。与“向上”相比,我们的y指向与 NDH 中不同的方向。无论我们做什么转换,我们都需要使这些相匹配。

glm::perspective()做什么的

glm::perspective()主要是让它看起来,透视良好。但为此它需要做一些假设。

最简单的就是深度。在 NDC 的 Vulkan 中,它是零到一。方便的话有GLM_FORCE_DEPTH_ZERO_TO_ONE。这表明perspective()它应该将您的near和分别映射far01。(默认值为 -1 到 1,除非手动更正,否则在 Vulkan 中不起作用。)xy仍然始终为 -1 到 1。

第二个选择是惯用手。默认为右手。左撇子需要GLM_FORCE_LEFT_HANDED。或者它可以显式地用于单个函数,例如perspectiveRH()。这有点误导。这实际上意味着“右手”意味着“前面”是-z。“左手”意味着投影假设“前”是z的正方向:

在此输入图像描述

第三个选择是“向上”。glm::perspective()实际上并没有对此做任何事情,并且y的极性在整个变换过程中保持不变。如果我们希望+y表示“向上”,我们需要手动执行此操作。我们可以利用视口翻转功能,或者可以将其烘焙到视图投影矩阵中:proj[1][1] = -proj[1][1]

在此输入图像描述

如何测试这个东西

测试这个实际上非常简单。该代码可用于以下目的:

#include <cmath>
#include <iostream>

#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
//#define GLM_FORCE_LEFT_HANDED
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

int main(){
#ifdef GLM_FORCE_LEFT_HANDED
    const float near = 1.0f;
    const float far = 2.0f;
#else //right-handed
    const float near = -1.0f;
    const float far = -2.0f;
#endif

    glm::vec3 right { 1.0f,  0.0f, near};
    glm::vec3 left  {-1.0f,  0.0f, near};
    glm::vec3 up    { 0.0f,  1.0f, near};
    glm::vec3 down  { 0.0f, -1.0f, near};
    glm::vec3 front { 0.0f,  0.0f, (far-near) + near };
    glm::vec3 back  { 0.0f,  0.0f, (near-far) + near };


    const auto xform = glm::perspective(glm::radians(60.0f), 1.0f, std::abs(near), std::abs(far));

    auto r = xform * glm::vec4(right , 1.0f);
    auto l = xform * glm::vec4(left  , 1.0f);
    auto u = xform * glm::vec4(up    , 1.0f);
    auto d = xform * glm::vec4(down  , 1.0f);
    auto f = xform * glm::vec4(front   , 1.0f);
    auto b = xform * glm::vec4(back  , 1.0f);


    std::cout << "Right to clip: ("  << r.x << ", " << r.y << ", " << r.z << ", " << r.w << ")\n";
    std::cout << "Left to clip: ("   << l.x << ", " << l.y << ", " << l.z << ", " << l.w << ")\n";
    std::cout << "Up to clip: ("     << u.x << ", " << u.y << ", " << u.z << ", " << u.w << ")\n";
    std::cout << "Down to clip: ("   << d.x << ", " << d.y << ", " << d.z << ", " << d.w << ")\n";
    std::cout << "Front to clip: ("  << f.x << ", " << f.y << ", " << f.z << ", " << f.w << ")\n";
    std::cout << "Back to clip: ("   << b.x << ", " << b.y << ", " << b.z << ", " << b.w << ")\n";
}
Run Code Online (Sandbox Code Playgroud)

对于两种惯用手设置,我们得到:

Right to clip: (1.73205, 0, 0, 1)
Left to clip: (-1.73205, 0, 0, 1)
Up to clip: (0, 1.73205, 0, 1)
Down to clip: (0, -1.73205, 0, 1)
Front to clip: (0, 0, 2, 2)
Back to clip: (0, 0, -2, 0)
Run Code Online (Sandbox Code Playgroud)

呃,一切都被剪掉了。但无论如何“右”和“上”都是正数。所以,是的,我们可能想要以某种方式翻转y以与演示引擎坐标兼容。“前”为(0,0,1)方向,“后”不存在。

请注意,代码中的变化是世界坐标中使用的z方向。