对unitySRP的理解


SRP

SRP即unity的可编程渲染管线,我们可以用它来创建自定义的渲染管线。最近看了很多文章,总结一下对它的理解和它的基本使用。也为后面自己重头开始写一条custom SRP做一个准备。

Scriptable Render Pipeline其实是一个unity抽出来的C#层,它集合了底层的渲染API,让我们可以自己调用,然后我们就可以用这个C#层去创建我们自定义的render pipeline然后插入到unity中。

为了弥补build-in与SRP之间的差距,unity又提出了两个可以直接使用的渲染方案:URP和HDRP。HDRP主要适用于主机平台的渲染方案,URP则是LWEP的延续,然后加入了一些新的特性。我们可以编写自己的feature,然后插入到pipeline中。

Command Buffer

这个buffer保存了渲染命令列表,可以设置在一个渲染摄像机的不同渲染阶段执行。在unity里面打开FrameDebugger,就可以看到场景的渲染流程,本质上就是一个一个顺序执行的pass,unity提供commandbuffer就是让我们可以根据自己的需求在不同阶段插入绘制指令,比如DrawRenderer,DrawMesh,DrawProcedure。

OnRenderImage的后处理其实就是commandBuffer的一个应用而已。

ScriptableRenderContext

要创建一个自定义管线,首先需要一个CustomRenderPipelineAsset资源,重写RenderPipeline方法,然后返回我们创建的管线实例,然后创建CustomRenderPipeline,这就是要创建的管线资源,在这个脚本的Render方法就是去渲染每个摄像机的地方,它的输入参数一个是当前要渲染的Camera,另一个是ScriptableRenderContex,渲染上下文。通过重写这个方法,使用CommandBuffer把渲染的指令添加到ScriptableRenderContex中,然后用Submit提交渲染指令。

ScriptableRenderPass

自定义的pass类,在这个类里就可以写自己想进行的渲染逻辑,需要复写三个方法:OnCameraSetup、Excute、OnCameraCleanup。然后创建出的实例就能够作为RenderFeature插入到渲染流程中去。

实际操作

现在看看如何编写一个自己的RenderFeature,然后插入到URP的后处理组件Volume中去。懒得贴代码了,直接记录一下操作逻辑,以及拓展Volume。

首先要创建一个继承自VolumeComponent,IPostProcessComponent的脚本,VolumeComponent的作用就是可以在volume组件里添加一个override,即我们自己的一个后处理效果,它没有必须要复写的函数,只需要在里面定义我们需要曝露出去的控制变量就行。IPostProcessComponent主要是将新加的后处理功能集成到Volume Framework的规范管理中,是不是必须要我也不知道,而且它必须有两个需要复写的函数:分别是IsActive和IsTileCompatible,对于IsActive你可以通过它给一些条件,如果不满足这个条件就关闭这个后处理特效,不过好像你自己写一个函数也行。

这个脚本写完之后你就可以在Volume里面添加自己的一个override。

然后就是定义我们的renderfeature的脚本,这个脚本要继承ScriptableRendererFeature,它有两个需要复写的函数:Create、AddRenderPasses。

在Create里要具体创建出一个pass实例,即初始化我们要用的pass。然后在AddRenderPasses里面要把这个创建好的pass给插入到renderer里面去。

这个pass就是我们需要定义的,它继承自ScriptableRenderPass,主要的渲染逻辑都是在这里写的,它会被我们插入到renderer中去。这个类必须要实现的是Excute方法:

1
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)

首先是如何把刚刚自定义的volume和这个renderfeature关联起来,通过VolumeManager.instance.stack,我们可以取得当前场景中的Volume组件,它被定义成一个VolumeStack,通过它使用GetComponent方法,可以得到我们刚刚创建的Volume override。

对于renderingData,他就是定义了渲染的相关数据,Unity里的定义是这样的:

在execute里面我们就可以创建我们的commandbuffer,然后写渲染指令,写完之后通过ExecuteCommandBuffer去执行就行,执行完之后release掉。

由于没有了OnrenderImage方法,就需要我们通过SetGlobalTexture、GetTemporaryRT这些api,去与shader交互,然后实现后处理特效。Blit方法还是和之前在buildin里实现的一样,主要是需要通过PropertyToID去获得shader里的变量名字,然后在execute里面使用这些ID号去给这些变量赋值。

如果需要一个空的rendertexture去暂存一个东西,那么就需要RenderTargetIdentifier去定义,可以通过cmd.GetTemporaryRT去获得当前摄像机的texture,然后存到我们指定的rendertexture里。

除此之外的后处理特效方法就和在buildin里面一摸一样了。

URP

Unity已经帮我们预先创建好了URP,它是一个继承自RenderPipelineAsset的管线资源,在里面可以设置这条管线的各种参数,然后我们也可以创建自己的asset,通过切换asset去实现不同的效果需求。比如unity的URP模板项目里面就有high、mediu和low三种asset,切换它就可以在游戏里面实现不同画质的切换。

general

对于每一个管线资源都需要给他一个具体的管线,URP默认给的管线是一个前向渲染的管线,这是包含在general里面的设置,然后还有对Depth和Opaque Texture的设置,指定相机是否生成深度纹理和不透明纹理,要在shader里得到的话就是_CameraDepthTexture 和 _CameraOpaqueTexture,后者就是buildin里的grabpass,相当于unity帮我们直接封装好了。不需要再用GrabPass{“name”}去取得了。然后在这里的是默认设置,如果某个摄像机选择了UsePipelineSettings,那就会采用这个默认设置,当然也可以对每个摄像机进行单独的设置:

Quality

这里参数就是控制渲染的效果,可以设置是否支持HDR,以及使用Multi Sample Anti-aliasing的层级,和摄像机rendertarget的分辨率:

Lighting

Lighting项主要的参数就是去设置灯光和这些灯光是否阴影

其中Main Light控制的是场景中最重要的平行光,在Lighting面板里面可以人为设置这个,比如指定这个光是目前游戏里面唯一的太阳,如果没有指定,引擎就会把最亮的那个平行光作为Main Light。然后对于Additional Lights,因为在URP里面的光照计算采用的时单Pass,这里可以指定每个物体最多受到多少光照的影响,并且可以选择是逐顶点计算还是逐像素计算。

Shadows

这里就是设置阴影的质量

Distance决定从相机开始多远开始产生阴影,Cascades是基于Lod的思想,通过距离决定阴影的质量。

Advanced

这里就比较简单,主要就是去设置如何Batching。

URP渲染流程

unity会去依次渲染场景中的每个相机,每个相机渲染的时候还会执行一个camera loop,Unity官方给出的camera loop如下:


Author: cdc
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source cdc !
  TOC