目录
延迟渲染管线核心在于先记录一遍每个像素上的信息,如Material、Normal、Albedo等,即G-Buffer,世界空间坐标可通过MVP矩阵和深度逆向算出。使用这些信息进行屏幕空间的计算,对于每个像素进行光照或阴影等计算,主要都是屏幕空间算法(即基于每个像素处理,而不是前向渲染中的每个物体处理)。
渲染器架构基于Piccolo引擎,项目地址MWEngine,Shader及算法相关实现参考Examples and demos for the new Vulkan API,只实现了不透明物体的相关光栅化。
博客中不关注具体的实现细节以及在渲染管线之外的操作,如具体资源的管理及窗口操作等。(语文不好,碰到语句不顺请见谅)
主要分成预计算管线和逐帧管线
- 其中预计算相关管线只需渲染器初始化时执行一次
- 逐帧管线每帧都需要执行一次
大致流程如下:
目前在延迟渲染中实现了Shadow、AO、Lighting,并希望其并行处理后进行Composite。
同时希望可以实现不同的算法,因此尝试实现一个良好的架构(目前尚未重构),架构如下:
预计算管线
计算在渲染器中需要的数据,目前只有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。
在架构上的是被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因为被其它几何表面所遮挡 ,从而降低了接受环境光的比例(这种遮蔽常常发生在凹处表面),个人理解就是光打在几何表面的概率,如果这个表面被其他表面遮挡,概率便下降了:
目前只实现了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抽象出接口。
参考资料
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