(材质NPR篇)【第一节:UE5|基于延迟渲染管线的后处理卡通渲染】
首先,根据渲染方式和实现方式不同,本文将展示基于延迟管线的后处理卡通渲染,该思路仅推荐于风格化渲染,不推荐给单独的人物卡渲。
延迟渲染
开头先科普一下延迟渲染管线
延迟渲染(Deferred Rendering, Deferred Shading)最早由Michael Deering在1988年的论文 The triangle processor and normal vector shader: a VLSI system for high performance graphics提出,是一种基于屏幕空间着色的技术,也是最普遍的一种渲染路径(Rendering Path)技术。与之相类似的是前向渲染(Forward Shading)。
它的核心思想是将场景的物体绘制分离成两个Pass:几何Pass和光照Pass,目的是将计算量大的光照Pass延后,和物体数量和光照数量解耦,以提升着色效率。在目前的主流渲染器和商业引擎中,有着广泛且充分的支持。
延迟渲染不同于前向渲染存在两个主要的Pass:几何通道(Geometry Pass, UE称为Base Pass)和光照通道(Lighting Pass),它的渲染过程如下图所示:
延迟渲染存在两个渲染Pass:第一个是几何通道,将场景的物体信息光栅化到多个GBuffer中;第二个是光照阶段,利用GBuffer的几何信息计算每个像素的光照结果。
- 延迟渲染优劣
由于最耗时的光照计算延迟到后处理阶段,所以跟场景的物体数量解耦,只跟Render Targe尺寸相关,复杂度是O(Nlight×WRT×HRTNlight×WRT×HRT)。所以,延迟渲染在应对复杂的场景和光源数量的场景比较得心应手,往往能得到非常好的性能提升。
但是,也存在一些缺点,如需多一个通道来绘制几何信息,需要多渲染纹理(MRT)的支持,更多的CPU和GPU显存占用,更高的带宽要求,有限的材质呈现类型,难以使用MSAA等硬件抗锯齿,存在画面较糊的情况等等。此外,应对简单场景时,可能反而得不到渲染性能方面的提升。
基础知识有了后,我们就正式开始吧
基础设置
- Post process Material Setting 后处理材质设置,在虚幻编辑器中创建新材质后,设置detail选项如上图。Material Domain设为“Post Process”,Post Process Material选项中选择“Before Tonemapping”,意味着该shader会在Tonemapping之前运行。
- SceneTexture是一个可选择G-Buffer来导入的节点,上图中PostProcessInput0表示SceneColor。现在创建的材质只是一个照原样输出最终结果Buffer的 Post Process 材质。
分阶阴影
要做卡通渲染,第一步是简化光照阴影
计算Directional Lighting Buffer
如在普通shader中做NDotL计算一样,我们要计算把光照阴影buffer信息可视化的Lighting Buffer,我们需要两个buffer来求lighting buffer。
-Post process Input:混合到当前材质之前的最终结果buffer画面
-Diffuse Color:无光照,无后处理,只有漫反射颜色的buffer画面
Desaturation 节点的功能是把Color图变成GrayScale,把PostProcessInput0 和 DiffuseColor 变为GrayScale再相除可得到Directional Light Buffer,效果如下图。
Banded Lighting
我们使用Lut贴图实现明暗效果
LUT从字面解释就是预先计算好的数据数组(具体请找度娘)。举个栗子,若要解数学试卷中的题目,得先读题目再解答吧。但假若我们有正确答案的话,只要在试卷上找到对应的题目,就算不解题也能马上知道答案,这里的正确答案就是LUT。
基于贴图来定明暗效果,我们称之为LUT texture,你可以用ps自定义lut效果
导入时,我们需要设置贴图格式,因为不用变为Linear Color,所以取消勾选 sRGB 选项。同时纹理的Tiling不能重复,所以设置改为Clamp。
Shadow Color(阴影颜色)
Sky Light Color(Ambient Color) 天空光照颜色(环境光颜色)
为获取天光颜色,我们可以使用 PostProcessInput0 buffer和 BaseColor(for lighting)buffer。
BaseColor(for lighting) buffer和Diffuse buffer类似
最后,与 Banded Lighting 结果相乘、混合,就得到了混合天光的结果。
Specular(High Light) 高光
跟加天光的道理一样,为了得到更好的品质,我们继续添加高光,如我们之前提取 Diffuse Buffer 或 Sky Light Buffer 一样,这次我们用 Specular节点(for lighting)获取 Specular Buffer。然后,用乘法来调整高光的强度,并用 if 节点将该值除以大于1的值来切割梯度
Exception Unlit Shader
因为我们用的延迟渲染管线,Post process Material写的shader,所以看不见天空盒(SkyBox),所有其他Unlit shader也不可见。所以说,我们用同样的方式使用Lerp节点提取天空球
轮廓线
本文会用两种检测轮廓线的方法。
1. 在Custom Depth Buffer中使用Sobel Operator 轮廓线检测算法
2. 通过Normal Buffer相邻像素差值检测轮廓线
同时使用这两种方法的原因是Sobel Operator容易检测外轮廓线,而Normal Buffer的方法容易检测内部的轮廓线
Sobel Operator算法的核心原理是将上面的3x3矩阵和按水平和垂直方向各取一个像素为标准比较周边像素,判断是否为轮廓线
//边缘检测的原理时利用一些边缘检测算子对图像进行卷积操作
啥是卷积
卷积操作就是使用一个卷积核,对一张图像中的每个像素进行一系列操作,卷积核通常是一个四方形网格结构,该区域内每个方格都有一个权重值,党对图像中的每个像素进行卷积时,我们会把卷积核的中心放置于该像素上,反转核后再一次计算核中的每个元素和其覆盖的图像像素值的乘积并求和,得到的结果就是该位置的新像素值
常见的边缘检测算子
//边是怎么形成的?
当相邻像素之间存在明显的颜色,亮度,纹理等属性,我们就会认为它们之间应该有一条边界。这种相邻像素之间的差值可以用梯度来表示。边缘处的梯度绝对值会比较大,基于这种理解,有几种不同的边缘检测算子被先后提出来
这三种算子都包含两个方向的卷积核,分别用于检测水平方向,竖直方向上的边缘信息。在进行边缘检测时,我们需要对每个像素分别进行一次卷积计算,得到两个方向上的梯度值Gx和Gy,公式如下
出于性能考虑,我们有时会使用绝对值操作来代替开根号
得到梯度G后,就可以据此来判断哪些像素对应的边缘了(梯度值越大,越有可能时边缘点)
这里我们需要在物体上通过在 Custom Depth Buffer中使用该算法,可以准确地检测出物体的外部轮廓,要使物体在Custom Depth Buffer中可见,你要单独设置物体的Custom Depth。。
获取周边方向的UV 信息
我们要获取一个像素周围8个方向的UV信息。创建“MF_GetNeighbourUVs” 材质函数,主要也是为计算sobel做准备,根据输入的UV坐标输出8个方向的UV信息值就行了
然后我们需要创建另一个“MF_GetOffsetPixel”材质函数,若输入SceneTexture尺寸(InvSize)和Offset Direction,就能得到对应的UV信息
使用周边UV信息得到Depth Buffer 像素信息
我们再新建一个"MF_ExtractDepthMap" 材质函数只是将之前计算的 UV 值放入 Custom Depth Buffer 中,并得到与UV匹配的像素值
将之前获取的UV信息输入到9个“MF_ExtractDepthMap”材质功能节点
通过该操作,可得到实际Depth Buffer的像素值。
最后,这是用提取的Depth Buffer像素值执行 Sobel Operator算法的内容。
再新建一个“MF_CombineMap”材质函数检查是否有轮廓线并输出结果值。
红框是最初介绍的Sobel算法的矩阵信息。分别检查水平和垂直像素后,把结果值相加。
再lerp到主材质中
Normal Base Outline(Inner Outline)
Normal Base Outline 可用 World Normal Buffer检测轮廓线,尤其是能检测内部轮廓线。 原理如上图,计算“Normal buffer - UV移动normal buffer”,通过差值可得到轮廓线。
这个功能也是通过创建材质函数来实现的。
看起来非常复杂是吧,其实确实很复杂(bushi)
插入调整UV的方向值,上下左右重复计算“-1,0”、“1,0”、“0,-1”、“0,1”的值。 之后,将四个方向计算的所有值都加到Add节点。
Rim Light(边缘光)
我们合并Banded lighting,Diffuse Buffer,Depth Outline 三个buffer才能得到边缘光
首先,重新创建Directional Lighting Buffer,这次使用if语句执行2个Banded Lighting,那么第一个需要的Banded Lighting Buffer就完成了。
DepLine Buffer和之前一样使用 Sobel Filter 材质函数创建,再做一个 Diffuse Color Buffer 节点以将 BandedLighting Buffer 和我们刚刚创建的 2 个 Buffer 相乘得到Rim Light Buffer
以上,把全部节点组合起来,用lerp节点合并buffer后就完成了
下一篇NPR章节将探索其他方法的卡通渲染,敬请期待