fullscreen_quad_rendering

渲染全屏四边形(或填充整个屏幕的东西)是 3D 实时图形中的一项常见任务,因为许多效果依赖于在 [0..1] 范围内使用适当的 uv 坐标在整个屏幕上渲染纹理。这适用于后期处理(光照计算、ssao)、延迟着色或程序生成的输出。

渲染此类全屏四边形的最先进方法是设置包含顶点(可选纹理坐标)和索引的缓冲区,用于渲染由两个三角形组成的四边形,绑定这些缓冲区并将它们作为着色器的输入属性。虽然这在 OpenGL 中只需几行就可以完成,但vulkan需要设置大量属性。因此,无需使用缓冲区中的顶点(和索引)来渲染填充屏幕的内容,而是可以使用 gl_VertexIndex (请参阅GL_KHR_vulkan_GLSL )顶点输入变量(类似于 OpenGL gl_VertexID)在顶点着色器中轻松生成顶点和纹理坐标。虽然这有点简化,但它基本上保存了调用顶点着色器的当前顶点的索引。

我们将使用它来生成一个全屏三角形,其作用类似于全屏四边形。为什么是三角形?因为它只需要 3 次顶点着色器调用(而不是由两个三角形组成的四边形调用 6 次)。

生成的三角形将如下所示:

该三角形包含我们的整个屏幕以及 [0..1] 的整个 uv 范围,以便我们可以像普通的全屏四边形一样使用它来应用后期处理效果。由于 GPU 剪切了屏幕边界之外的所有内容,我们实际上得到了一个只需要 3 个顶点的四边形。而且由于裁剪是免费的,因此我们不会浪费带宽,因为三角形的裁剪部分(灰色部分)不会被采样。

Vulkan 部分

在 Vulkan 应用程序中使用它非常简单,并且由于我们不需要任何缓冲区,因此我们无需设置大量属性(包括描述符集和布局),将其添加到现有的 Vulkan 应用程序非常简单。

图形管线

VkPipeline的pVertexInputState成员指定顶点输入属性和顶点输入绑定。由于我们不将任何顶点传递给着色器,因此我们需要为不包含任何输入或属性绑定的全屏管道设置一个空顶点输入状态:

1
2
3
4
5
6
7
8
9
10
VkPipelineVertexInputStateCreateInfo emptyInputState;
emptyInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
emptyInputState.vertexAttributeDescriptionCount = 0;
emptyInputState.pVertexAttributeDescriptions = nullptr;
emptyInputState.vertexBindingDescriptionCount = 0;
emptyInputState.pVertexBindingDescriptions = nullptr;
...
pipelineCreateInfo.pVertexInputState = &emptyInputState;
...
vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo;, nullptr, &fullscreenPipeline;));

剔除

请注意,顶点按顺时针顺序排列(参见上图),因此如果您使用剔除,则需要考虑到这一点,例如以下管道设置:

1
2
rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT;
rasterizationState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;

渲染

通过上述全屏管道的顶点输入状态设置,我们现在可以绘制某些内容,而无需像从缓冲区渲染几何图形时那样预先绑定顶点(和索引)缓冲区:

vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, fullscreenPipeline);
vkCmdDraw(commandBuffer, 3, 1, 0, 0);
只需将管道与空顶点输入状态绑定,并发出顶点数为 3 的绘制命令(非索引)。如果您使用正确的管道,这不会生成任何验证层错误,即使存在(技术上) )没有要实际渲染的顶点。

顶点着色器

有趣的部分是顶点着色器,它将根据 gl_VertexIndex 生成顶点:

1
2
3
4
5
6
7
8
9
#version 450 
...
layout (location = 0) out vec2 outUV;
...
void main()
{
outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
gl_Position = vec4(outUV * 2.0f + -1.0f, 0.0f, 1.0f);
}

该着色器不包含任何输入属性,并仅根据 gl_VertexIndex 生成位置和纹理坐标(传递给片段着色器),其中纹理坐标位于三角形可见部分的 [0..1] 范围内。


fullscreen_quad_rendering
http://example.com/2023/05/16/fullscreen-quad-rendering/
作者
bergzha
发布于
2023年5月16日
许可协议