顶点位置的 Vulkan 默认坐标系
Vulkan default coord system for vertex positions
我正在通过在玩具渲染器上工作来研究 Vulkan 坐标系。
我对顶点位置的坐标感到困惑。
在线 Vulkan 信息,例如:
https://matthewwellings.com/blog/the-new-vulkan-coordinate-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}},
};
-5(Z) 将顶点移回屏幕。应该是 +5 才行。
坐标系好像是这样的:
如果我把相机放在原点,它看起来像这样:
另一个镜头,相机远离三角形(视图在 Z 轴上平移 -4)。
一些相关代码。模型和视图矩阵都是恒等的。
对比:
outColor = inColor;
gl_Position = ubo.projectionMatrix * ubo.viewMatrix * ubo.modelMatrix * vec4(inPos.xyz, 1.0);
FS:
outFragColor = vec4(inColor, 1.0);
投影是:
glm::perspective(glm::radians(60.0f), w/h, 0.1, 256.0);
裁剪和归一化设备坐标
剪辑坐标是我们从顶点着色器获得的坐标。归一化设备坐标 (NDC) 相同,但除以 w。有两种常见的用户选项(左撇子和右撇子):
“向上”的含义实际上取决于您。但是,如果您希望它与几乎所有的表示引擎兼容,您希望“up”在视口变换之后表示 -y(因此在 NDH 中,您的“up”应该是 -y 在香草视口变换的情况下,或者它应该是 +y 以防视口变换稍后翻转它)。
在 framebuffer\image 坐标中选择“向上”总是 -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}},
};
现在,这又取决于对这实际上意味着什么的解释。我们需要另一个“向上”参考方向。
为了保持理智,我们可能希望“向上”处于 y 增加的方向。所以这意味着我们在底部有一些红色的角。我们在左上角有一个绿色角。我们在右上角有一个蓝色的角。或者我认为是作者的意图。
此外,为了保持理智,我们更喜欢右手坐标系。所以,如果我们选择 +y 表示“向上”,而 +x 表示“向右”,那么 -z 必须是“前面”(并且 +z 必须是“后面”):
(这与 Blender 具有世界坐标的方式相匹配。)现在我们陷入了困境。我们的 z 是负数而不是 NDH 要求的正数。与“向上”相比,我们的 y 指向与 NDH 不同的方向。无论我们做什么转换,我们都需要使它们匹配。
glm::perspective()
做什么
glm::perspective()
主要是让它看起来很好看。但为此它需要做几个假设。
最简单的是深度。在 NDC 的 Vulkan 中,它是零比一。方便的是 GLM_FORCE_DEPTH_ZERO_TO_ONE
。这指示 perspective()
它应该将您的 near
和 far
分别映射到 0
和 1
。 (默认值为 -1 到 1,除非手动更正,否则在 Vulkan 中不起作用。)x 和 y 仍然始终为 -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";
}
对于左右手设置,我们得到:
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)
Ups,这一切都被剪掉了。但无论如何“右”和“上”都是正数。所以是的,我们可能想要翻转 y 某种方式以与表示引擎坐标兼容。 "front" 是 (0,0,1) 方向,"back" 停止存在。
注意代码中的变化是世界坐标中使用的 z 的方向。
我正在通过在玩具渲染器上工作来研究 Vulkan 坐标系。 我对顶点位置的坐标感到困惑。 在线 Vulkan 信息,例如: https://matthewwellings.com/blog/the-new-vulkan-coordinate-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}},
};
-5(Z) 将顶点移回屏幕。应该是 +5 才行。
坐标系好像是这样的:
如果我把相机放在原点,它看起来像这样:
另一个镜头,相机远离三角形(视图在 Z 轴上平移 -4)。
一些相关代码。模型和视图矩阵都是恒等的。
对比:
outColor = inColor;
gl_Position = ubo.projectionMatrix * ubo.viewMatrix * ubo.modelMatrix * vec4(inPos.xyz, 1.0);
FS:
outFragColor = vec4(inColor, 1.0);
投影是:
glm::perspective(glm::radians(60.0f), w/h, 0.1, 256.0);
裁剪和归一化设备坐标
剪辑坐标是我们从顶点着色器获得的坐标。归一化设备坐标 (NDC) 相同,但除以 w。有两种常见的用户选项(左撇子和右撇子):
“向上”的含义实际上取决于您。但是,如果您希望它与几乎所有的表示引擎兼容,您希望“up”在视口变换之后表示 -y(因此在 NDH 中,您的“up”应该是 -y 在香草视口变换的情况下,或者它应该是 +y 以防视口变换稍后翻转它)。
在 framebuffer\image 坐标中选择“向上”总是 -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}},
};
现在,这又取决于对这实际上意味着什么的解释。我们需要另一个“向上”参考方向。
为了保持理智,我们可能希望“向上”处于 y 增加的方向。所以这意味着我们在底部有一些红色的角。我们在左上角有一个绿色角。我们在右上角有一个蓝色的角。或者我认为是作者的意图。
此外,为了保持理智,我们更喜欢右手坐标系。所以,如果我们选择 +y 表示“向上”,而 +x 表示“向右”,那么 -z 必须是“前面”(并且 +z 必须是“后面”):
(这与 Blender 具有世界坐标的方式相匹配。)现在我们陷入了困境。我们的 z 是负数而不是 NDH 要求的正数。与“向上”相比,我们的 y 指向与 NDH 不同的方向。无论我们做什么转换,我们都需要使它们匹配。
glm::perspective()
做什么
glm::perspective()
主要是让它看起来很好看。但为此它需要做几个假设。
最简单的是深度。在 NDC 的 Vulkan 中,它是零比一。方便的是 GLM_FORCE_DEPTH_ZERO_TO_ONE
。这指示 perspective()
它应该将您的 near
和 far
分别映射到 0
和 1
。 (默认值为 -1 到 1,除非手动更正,否则在 Vulkan 中不起作用。)x 和 y 仍然始终为 -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";
}
对于左右手设置,我们得到:
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)
Ups,这一切都被剪掉了。但无论如何“右”和“上”都是正数。所以是的,我们可能想要翻转 y 某种方式以与表示引擎坐标兼容。 "front" 是 (0,0,1) 方向,"back" 停止存在。
注意代码中的变化是世界坐标中使用的 z 的方向。