Vulkan 视口设置简介
这篇简短的教程将介绍 Vulkan 的视口设置,它与 OpenGL 和其他 API 中的设置有所不同。我将尝试解释如何让你的(OpenGL)场景正确渲染,以及如何使用 VK_KHR_MAINTENANCE1 等扩展来处理不同 API 之间的差异。这对于你尝试在不改变实际场景数据的情况下将 Vulkan 添加为渲染器的后端尤其有帮助。
问题描述
如果你像在 OpenGL 中一样传递顶点数据,并使用相同的状态(如管道设置)和着色器,你的场景很可能不会像你预期的那样显示。
以这个演示为例,它使用了类似 OpenGL 示例中相同的顶点数据和管道设置逻辑,然后在 Vulkan 中渲染 Sibenik 大教堂:
这个场景的渲染条件是:
- 顶点数据的 Y 值向上
- 启用了背面剔除
- 环绕顺序设置为逆时针
将其与场景正确的渲染效果进行对比:
很明显,这里的问题在于:
- 场景被上下颠倒地渲染了
- 剔除操作被移除了正面而非背面
不同的坐标系
造成这个问题的原因很简单:Vulkan 使用了不同的坐标系。
在 OpenGL 中,视口的原点位于屏幕的左下角,Y 轴方向向上。而在 Vulkan 中,原点位于屏幕的左上角,Y 轴方向向下。这解释了为什么我们的场景会被上下颠倒地渲染。
快速解决方案
一个简单粗暴的解决方法是在主机端或顶点着色器中反转所有 Y 坐标,例如在顶点着色器中这样做:
void main()
{
...
gl_Position.y = -gl_Position.y;
}
虽然这种方法可行,但你需要为每个顶点着色器都添加这行代码,这对于一个拥有多个后端并共享相同着色器的渲染器来说尤其麻烦。
翻转视口
幸运的是,Khronos 意识到不同的坐标系可能会影响 Vulkan 的普及,因此决定解决这个问题。设计一个 API 并非易事,尤其是当它需要覆盖多种操作系统、平台和设备时。
在收集开发者反馈后,Vulkan 1.0 添加了 VK_KHR_Maintenance1 扩展。该扩展除了其他 API 修复外,还支持通过设置负视口高度实现坐标系翻转:
允许在
VkViewport::height
字段中指定负高度值,以对裁剪空间到帧缓冲空间的变换进行 Y 轴翻转。这使得应用程序可以避免在需要同时兼容其他 API 的着色器中编写gl_Position.y = -gl_Position.y
这类操作。
注意:此扩展功能已在 Vulkan 1.1 中成为核心特性。因此,如果仅针对 Vulkan 1.1(及更高版本),无需检查扩展支持即可直接翻转视口。
现在,在设置视口状态时,只需简单指定负的视口高度即可实现 Y 轴翻转:
VkViewport viewport{};
viewport.width = renderer.extent.width;
viewport.height = -renderer.extent.height;
viewport.x = 0;
viewport.y = renderer.extent.height;
vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
需要注意的是,你还需要更改视口的原点(第 6 行),否则整个视口会位于窗口之外,导致你无法看到场景的任何部分。
通过这种设置,你现在可以使用与 OpenGL 应用程序中相同的着色器、顶点数据和管道配置。
示例
我还添加了一个新示例,用于演示如何在 Vulkan 中翻转视口,Vulkan 与 OpenGL 的对比,以及不同的管道状态如何影响视口。
引用: