本篇主要通过向小型动画场景添加一些体积光散射,了解如何使用 Unity 的通用渲染管线创建你自己的自定义渲染功能。首先上效果图
(甩锅声明:本教程默认你已经熟练使用unity,c#和shader。有向量代数的知识很有帮助,但是没有的话你也可以学到很多。)
文末下载源文件,然后解压项目并在unity中打开,项目中查看到的应该是这样子的
下面是每个文件夹包含的内容的快速细分:
Animations:玩家角色的动画。
Materials:用于玩家和场景的材料。
Models:场景和玩家的模型。
Scenes:工作的示例场景。
Scripts:示例项目的一堆脚本。
Settings:通用渲染管线设置的资产。
Shaders : 一个空的文件夹,你的着色器将放在其中。
Textures:用于播放器和调试的纹理贴图。
现在,在RW/Scenes中打开Sunset God Rays场景。查看场景视图,点击Play试玩:
你将看到游戏关卡的概览。使用A和D键左右移动播放器。按空格键跳跃。
你在本教程中的目标是学习如何使用一些很酷的体积效果来改善游戏的视觉效果,使其更加有趣。你的第一步是了解什么是体积光照以及如何实现它。
在现实世界中,光不会在真空中传播,在真空中,你和你正在看的物体之间不存在任何东西,除非你在太空中。
在实时渲染中,这被称为参与媒体对光传输的影响。最常见的现象是雾。
当空气中的粒子密度足够高时,部分遮挡光源的物体将以光束或射线的形式在这些粒子上投射阴影。
在游戏设计中,这些被称为God Rays或Light shafts。此效果可让你增强场景的真实感和润色,让一切看起来都非常漂亮。
接下来,你将了解如何在 Unity 游戏中实现此效果。
虽然在物理上并不准确,但屏幕空间方法非常简单。首先,渲染光源颜色,然后将场景中的所有对象涂成黑色。你可以在occluders map的屏幕外纹理中执行此操作。它看起来像这样:
之后,你在后期处理中对图像应用径向模糊。从光源中心开始,你沿着从光源到你正在评估的当前像素的向量采集多个颜色样本。你将此像素的最终颜色设置为这些样本的加权和。
最后,将此图像叠加在原始彩色图像之上。
了解了这个过程之后,你将看到如何将这个概念应用到你自己的游戏中。
Universal Render Pipeline 提供了一个脚本模板来创建特征。现在,你将创建自己的自定义渲染器功能。
在RW/Scripts中,选择Create Rendering Universal Render Pipeline Renderer Feature并将其命名为VolumetricLightScattering。
接下来,双击VolumetricLightScattering.cs以启动代码编辑器。你将看到以下内容:
这是你渲染器的类。它派生自基本抽象类 ,ScriptableRendererFeature它使你能够将渲染传递注入渲染器并在不同的事件上执行它们。
ScriptableRendererFeatures由一个或多个组成ScriptableRenderPasses。默认情况下,Unity 为你提供一个名为CustomRenderPass. 当你编写自定义pass时,你会了解这个class的详细信息。目前,先看VolumetricLightScattering.
Unity 在脚本运行时以预定的顺序调用一些方法:
Create():在函数首次加载时调用。你将使用它来创建和配置所有ScriptableRenderPass实例。
AddRenderPasses():每帧调用一次,每个摄像机调用一次。你将使用它将你的ScriptableRenderPass实例注入到ScriptableRenderer.
首先,你将定义一些设置来配置此功能。首先在上面添加以下类VolumetricLightScattering:
VolumetricLightScatteringSettings包含以下参数:
resolutionScale:屏幕配置外的大小。
Intensity:设置light的强度。
blurWidth:组合像素颜色时使用的模糊半径。
注意:System.Serializable在类的顶部添加以使属性可通过检查器进行编辑。
接下来,通过在上面添加以下行来声明设置的实例Create():
这就是使用自定义渲染器功能所需的全部内容。保存你的脚本并切换到 Unity。
现在你有了一个renderer feature,你需要将它添加到ForwardRenderer中。
为此,在 Project 窗口中找到RW/Settings并选择ForwardRenderer。
在 Inspector 窗口中,选择Add Renderer Feature Volumetric Light Scattering。
渲染器现在使用你创建的renderer feature。单击设置以显示你刚刚定义的属性。
然后,单击“播放”,然后……你会发现没有任何变化。这是因为该功能的渲染通道还没有做任何事情。
注意:如果你在检查器中看不到设置,请尝试重新加载VolumetricLightScattering.cs。右键单击脚本并选择Reimport。回到ForwardRenderer——现在应该可以看到设置了。
首先回到VolumetricLightScattering.cs。寻找CustomRenderPass,你会看到:
CustomRenderPass派生自基本抽象类 ,ScriptableRenderPass它提供了实现逻辑渲染过程的方法。
与 一样ScriptableRendererFeature,Unity 在脚本运行时调用一些方法。以下是本文需要了解的内容:
OnCameraSetup():在渲染相机以配置渲染目标之前,调用它。
Execute():调用每一帧来运行渲染逻辑。
OnCameraCleanup():在这个渲染过程执行后,调用它来清理所有分配的资源——通常是渲染目标。
你还可以用更多方法,但不会在本文中使用,包括:Configure():在执行渲染过程以配置渲染目标之前,你可以调用此函数,它会在之后执行OnCameraSetup()
OnFinishCameraStackRendering():此函数在渲染相机堆栈中的最后一个相机后调用一次。一旦堆栈中的所有相机完成渲染,你就可以使用它来清理任何分配的资源。
重命名CustomRenderPass为LightScatteringPass. 使用代码编辑器的重命名功能,因为这个出现在多个位置。然后,在上面声明以下变量OnCameraSetup():
occluders:需要一个RenderTargetHandle来创建纹理。
resolutionScale:分辨率比例。
intensity:效果强度。
blurWidth:径向模糊宽度。
在设置中定义resolutionScale,intensity和blurWidth
下一步是声明一个构造函数来初始化这些变量。通过在刚刚添加的变量下方添加以下代码来执行此操作:
这里,LightScatteringPass是renderpass构造函数。
第一步是resolutionScale根据设置进行初始化。然后你需要occluders通过调用Init()纹理名称来进行初始化。
接下来,将Create()替换VolumetricLightScattering为以下内容:
这里表示,你调用 pass 构造函数并将setting作为参数。你还可以配置输入render pass的位置。在这种情况下,你就是在后处理效果之前设置的它。
现在,你将创建一个屏幕外纹理来存储所有遮挡光源的对象的轮廓。你将在OnCameraSetup(). 具体代码如下:
这里有几件重要的问题:
1.首先,你会获得当前相机的RenderTextureDescriptor. 此描述符包含创建新纹理所需的所有信息。
2.然后,禁用深度缓冲区,因为你不会使用它。
3.将纹理尺寸缩放resolutionScale.
4.要创建新纹理,请用GetTemporaryRT()图形命令。第一个参数是的ID occluders。第二个参数是你从创建的描述符中获取的纹理配置,第三个参数是纹理过滤模式。
5.最后,调用ConfigureTarget()纹理RenderTargetIdentifier来完成配置。
保存脚本并返回编辑器。
接下来,你需要创建自己的无光照着色器。为什么要编写自己的而不是使用默认的无光照着色器?
默认着色器将雾设置考虑在内,在渲染远处对象时使用它们来影响远处对象的颜色。这对最终图像很好,但不适用于此纹理贴图。这就是为什么你创建一个自定义的无光照着色器并在SubShader中Fog {Mode Off}.
在RW/Shaders中,选择Create Shader Unlit Shader并将其命名为UnlitColor。双击UnlitColor.shader启动编辑器,然后将所有行替换为:
这里,你创建一个着色器,该着色器采用名为的颜色属性_Color并将其传递给Color着色器命令。这就是用黑色绘制对象所需的全部内容。
保存着色器代码并切换到编辑器进行编译。
首先,你需要使用着色器创建材质。返回VolumetricLightScattering.cs并在LightScatteringPass构造函数上方添加以下行:
然后,在构造函数中添加这一行:
这会使用UnlitColor着色器创建一个新的材质实例。你可以使用Shader.Find()着色器名称获取对着色器的引用。
要执行渲染逻辑,请找到Execute()并将其替换为以下内容:
上面代码的功能是:
1.如果缺少材质,会停止传递渲染。
2.通过命令缓冲区发出图形命令。CommandBufferPool只是准备好使用的预先创建的命令缓冲区的集合。你可以使用Get().
3.将图形命令包装在 aProfilingScope中,以确保FrameDebugger可以分析代码。
4.将所有命令添加到 后CommandBuffer,你就可以安排它执行并释放它。