首页 > 其他分享 >一个简单的Vulkan渲染器管线组织

一个简单的Vulkan渲染器管线组织

时间:2024-02-01 11:00:16浏览次数:28  
标签:采样 IBL 算法 Pass 渲染器 Shadow Vulkan 管线

目录

延迟渲染管线核心在于先记录一遍每个像素上的信息,如Material、Normal、Albedo等,即G-Buffer,世界空间坐标可通过MVP矩阵和深度逆向算出。使用这些信息进行屏幕空间的计算,对于每个像素进行光照或阴影等计算,主要都是屏幕空间算法(即基于每个像素处理,而不是前向渲染中的每个物体处理)。

渲染器架构基于Piccolo引擎,项目地址MWEngine,Shader及算法相关实现参考Examples and demos for the new Vulkan API,只实现了不透明物体的相关光栅化。

博客中不关注具体的实现细节以及在渲染管线之外的操作,如具体资源的管理及窗口操作等。(语文不好,碰到语句不顺请见谅)

主要分成预计算管线和逐帧管线

  • 其中预计算相关管线只需渲染器初始化时执行一次
  • 逐帧管线每帧都需要执行一次

大致流程如下:

image-20240131153255465

目前在延迟渲染中实现了Shadow、AO、Lighting,并希望其并行处理后进行Composite。

同时希望可以实现不同的算法,因此尝试实现一个良好的架构(目前尚未重构),架构如下:

image-20240131154725860

预计算管线

计算在渲染器中需要的数据,目前只有IBL所需数据,在IBL Pass初始化时即进行预计算生成图中所需的三个数据,三个数据分别走三个VkRenderPass,执行一次后相关资源直接销毁,保存所需数据。

逐帧管线

分为预计算部分和计算部分

  • 其中预计算部分主要生成每一帧中的所需资源,例如shadow map等。
  • 计算部分主要目的是在窗口中画出一张好的图(图中主要体现在RenderPass Begin和RenderPass End之中),使用Vulkan的subpass机制,尝试做出好的优化。

逐帧预计算部分

目前只有Shadow Depth Pass,生成ShadowMap。

Shadow Depth Pass

从光源角度生成一张深度图,即ShadowMap,这里不讲解ShadowMap原理,详情可参照实时阴影技术(1)Shadow Mapping。这里只有一个平行光,使用CSM技术,所以需要生成多张ShadowMap。

img

在架构上的是被Shadow Pass相关Pass所管理(不同的Shadow算法可能需要不同的Depth算法),在Main Camera Pass执行之前获取。

逐帧计算部分

这里主要指一个完整的延迟渲染管线流程,即G-Buffer、AO、Shadow、Lighting、Composite等。其中Composite Pass中可以混合数据和后处理。

屏幕空间算法使用一个覆盖整个屏幕空间的三角形进行操作,利用UV确定像素坐标对G-Buffer采样。

// Vulkan Draw
vkCmdDraw(commandBuffer, 3, 1, 0, 0);
// Vertex Shader
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);
}

由于某个Pass可能需要其他Pass的Descriptor相关信息,会生成全局变量+extern以供其他Pass使用,在代码中就是在Framebuffer中创建多个ImageView,同时在VkRenderPass的建立中设置好Dependcy。

G-Buffer Pass

类似于前向渲染,每个物体都进行一次Draw Call,但不进行复杂计算,进行MVP变换后,记录屏幕空间所需要的信息,生成多张屏幕大小的图(如1920*1080)。

优化:Piccolo提前用AABB和屏幕视锥体在CPU端进行相交判断,只将出现在视锥体的物体进行Draw Call,不过Piccolo是在CPU端实现这一过程,再优化可以在GPU端完成。

AO Pass

环境光遮蔽(Ambient Occulusion,AO),就是某个shading point因为被其它几何表面所遮挡 ,从而降低了接受环境光的比例(这种遮蔽常常发生在凹处表面),个人理解就是光打在几何表面的概率,如果这个表面被其他表面遮挡,概率便下降了:

img

目前只实现了SSAO算法,有待实现其他即插即用的AO算法。

SSAO

具体算法可参照基于屏幕空间的实时全局光照(Real-time Global Illumination Based On Screen Space),基本思路是利用像素的位置及法线,在着色点的法线方向上形成一个半球,在半球上进行重要性采样,如果采样点对应的G-Buffer中的深度如果小于采样点深度,即表示采样点被遮挡,光流入该表面的概率降低。

Shadow Pass

Shadow Pass所需的ShadowMap在逐帧预计算的Shadow Depth Pass中生成,这里直接使用生成好的ShadowMap进行阴影计算,目前实现了平行光的CSM算法.

有待实现其他光源的对应阴影算法。

CSM

CSM技术同样可参照上述的博客,主要用于平行光,将摄像机视锥体分成若干个Cascade,对于光源来说,每个视锥体生成一张ShadowMap,具体阴影计算仍然可参考实时阴影技术(1)Shadow Mapping,在生成阴影过程中实现了PCF算法,即同时比较ShadowMap对应的位置附近纹素,这里可以使用低差异序列进行采样,也可以直接使用一个卷积核。

Cascade设计参考原神设计:分为8级Cascade,前4级每帧更新,后4级第2/4/6/8帧更新(保证8帧将所有Cascade更新完)。本渲染器的后四级根据当前帧数对2/4/6/8取模更新(其实是一开始理解错意思实现错了,然后懒得改了)。

Lighting Pass

主要实现光照算法,目前实现了PBR-IBL。

PBR-IBL Pass

IBL原理可参考浅谈IBL(Image-based Lighting) - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/659128090),PBR原理也可参考这位大佬的其他文章。

IBL在预计算中需要算三项——irradiance map、prefilter environment map、LUT,将IBL公式分为漫反射和镜面反射两部分,其中漫反射项需要预计算irradiance map,镜面反射项需要计算prefilter environment map、LUT。

IBL只算出了环境光,具体的每个光源的PBR计算也同时结合在Shader中。

Composite Pass

主要将AO、Shadow、Lighting三项合并起来,代码中实现上就是直接做乘积。

layout (input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput inputShadow;
layout (input_attachment_index = 1, set = 1, binding = 0) uniform subpassInput inputAO;
layout (input_attachment_index = 2, set = 2, binding = 0) uniform subpassInput inputLighting;
//
vec3 shadow = subpassLoad(inputShadow).rgb;
float ao = subpassLoad(inputAO).r;
vec3 lighting = subpassLoad(inputLighting).rgb;
vec3 color = shadow * ao * lighting;

然后做一些后处理,目前只实现了ToneMapping,有待实现Bloom等后处理,但可能得更改管线,只需要在Composite Pass中之后继续对Framebuffer中的attachment进行操作即可。

// Tone mapping
color = Uncharted2Tonemap(color * uboParams.exposure);
color = color * (1.0f / Uncharted2Tonemap(vec3(11.2f)));
// Gamma correction
color = pow(color, vec3(1.0f / uboParams.gamma));

outFragColor = vec4(color, 1.0);

总结

预计算结果可以在渲染器完成前就处理完存到一张图中进行采样,而非如此每打开一次生成一次。

管线其实组织起来并不困难,理解到我们所处理的不过也是一张图,只不过这张图是呈现在窗口中的,逐像素单独处理出所需要的数据再按需结合即可。

窗口中的图也是Framebuffer的attachment之一,只不过可以绑定在swapchain中,所以可以呈现在窗口中。

每个Pass获取到的数据都是attachment中的一张图,保存并在所需要时拿出即可。

如果需要后处理,之前处理出的图需要再单独存到attachment,不能直接对swapchain中的图(即窗口图本身)进行操作,因为如果牵涉到临近像素采样时,无法保证各个像素的计算顺序一致。

问题

如果G-Buffer某一点的Depth为1,即该点不是所需要绘制的点,即采样天空盒。而天空盒只需要在一个Pass中采样即可,其他的Pass怎么处理。

SSAO在半球上采样并与G-Buffer中的深度比较,那么如果像素在屏幕边界,采样点在屏幕空间之外,如何处理。

如果想要实现即插即用的各个算法,以及优化Vulkan代码量,怎么实现架构及更好的RHI层封装,例如将目前架构中的三个Pass抽象出接口。

参考资料

F-Mu/MWEngine (github.com)

BoomingTech/Piccolo: Piccolo (formerly Pilot) – mini game engine for games104 (github.com)

SaschaWillems/Vulkan: Examples and demos for the new Vulkan API (github.com)

实时阴影技术(1)Shadow Mapping - KillerAery - 博客园 (cnblogs.com)

基于屏幕空间的实时全局光照(Real-time Global Illumination Based On Screen Space) - KillerAery - 博客园 (cnblogs.com)

[摸着原神学图形]平行光的cascade和软阴影 - 知乎 (zhihu.com)

浅谈IBL(Image-based Lighting) - 知乎 (zhihu.com)

标签:采样,IBL,算法,Pass,渲染器,Shadow,Vulkan,管线
From: https://www.cnblogs.com/F-Mu/p/18000764

相关文章

  • 04 OpenGL渲染管线
    OpenGL做了什么?坐标系+物体+摄像机(观察者),经过渲染最后得到二位屏幕的图像。渲染过程第一步:首先要初始化一个三角形(定点位置、每个定点的属性),还需要初始化摄像机(观察者)。第二步:移动三角形,移动摄像机(观察者)。第三步:以观察者为中心,观察世界。第四步:投影-->把能看到的......
  • Vulkan学习苦旅04:创建设备(逻辑设备VkDevice)
    设备是对物理设备的一种抽象,使我们更加方便地使用它。更准确地说,应该称其为“逻辑设备”,但由于逻辑设备在Vulkan中极为常用,后面几乎所有的API都需要它作为第一个参数,因此在Vulkan中直接简称为设备。1.实例、物理设备与设备的关系在之前的几篇文章中,我们依次创建了实例和物理设......
  • Vulkan学习苦旅02:看不见的窗口(创建VkInstance与VkSurfaceKHR)
    在上一节中,我们搭建了学习Vulkan所需的环境。今天,我们将会初步了解“地图”顶层的内容。 如图所示,“地图”的顶层有两个模块:Instance和SurfaceKHR.其中,Instance表示应用程序的一个实例,它用于保存一些状态,我们可以在应用程序中创建多个实例,但目前我们只创建一个实例;SurfaceKH......
  • vulkan/数据格式说明- normalized
    VK_FORMAT_A8B8G8R8_UNORM_PACK32VK_FORMAT_A8B8G8R8_SNORM_PACK32VK_FORMAT_A8B8G8R8_USCALED_PACK32VK_FORMAT_A8B8G8R8_SSCALED_PACK32VK_FORMAT_A8B8G8R8_UINT_PACK32 引用规范中的 IdentificationofFormats 和 ConversionfromNormalizedFixed-......
  • NLog 配置文件中布局渲染器(layout renderers)
    ​ NLog配置文件中,布局渲染器(layoutrenderers)是一种机制,用于在日志消息中插入动态内容或格式化信息。它们允许您将变量、属性、日期时间信息等添加到日志消息中,以便更详细地记录和分析日志。布局渲染器是在${}中包含的占位符,会在运行时替换为实际值。1、所有的布局参数......
  • ProjectorMultiply.Shader 非固定管线版本
    //UpgradeNOTE:replaced'_Projector'with'unity_Projector'//UpgradeNOTE:replaced'_ProjectorClip'with'unity_ProjectorClip'Shader"Projector/Multiply"{Properties{_ShadowTex("......
  • vulkan/图元重启(Primitive restart)
    在Vulkan/OpenGL绘制图形时,可能需要绘制多个并不相连的图形。这样的情况下这几个图形没法被当做一个图形来处理。也就需要多次调用 DrawArrays 或 DrawElements.如果图形很多,可能会需要用一个循环来调用:for(inti=0;i<num_objects;i++){glDrawArrays(GL_TRIA......
  • vulkan/descriptorSet
    参考Shaderlayout(binding=0)uniformUniformBufferObject{mat4model;mat4view;mat4proj;}ubo;layout(location=0)invec2inPosition;layout(location=1)invec3inColor;layout(location=2)invec2inTexCoord;layout(location=0)......
  • Vulkan/VkPresentModeKHR
    呈现模式:对于交换链对显示模式的设置应该是最重要的,因为它代表实际显示图像到屏幕的时机。在Vulkan中有四种显示模式:1.VK_PRESENT_MODE_IMMEDIATE_KHR由应用提交的图像立刻被传输到屏幕。这种方式可能导致图像不完整。2.VK_PRESENT_MODE_FIFO_KHR交换链是一个队列,当......
  • Vulkan/Graphics Pipelines
    渲染是vulkan最基础的功能,也是众多图形化应用最核心的部分。vulkan的渲染过程可以当作是通过执行不同阶段的命令以此来在展示设备上渲染出图片的过程。 vulkan中,渲染管线可以看作是一条生产流水线,命令在管线的开头进入,并且在管线内不同阶段执行。每个阶段都有诸如变换,读取命令......