URP下基于后处理的热空气扭曲效果
前言
前几天在网上看到一位大神的 Unity Shader-热空气扭曲效果 文章,感觉应该是个常见的效果,所以准备在URP里实现一下,正好再次深入使用一下URP,期间也遇到了一些匪夷所思的坑,也会在文章中说明。
原文中的全屏扭曲和基于GrabPass的方式都省略不谈,这里来用URP实现一下基于后处理的热空气扭曲。
环境
URP版本:7.3.1
Unity版本:2019.4.8f1
正文
原文中的实现核心思路是在需要扭曲的地方摆放一个面片,然后将这个面片渲染到一张RenderTexture上作为Mask,后处理的时候以Mask为基准决定ColorTexture哪些地方需要扭曲,然后对一张Noise图进行采样,对目标像素做偏移,达到扭曲的效果。
仔细分析后发现其实就一个难点,就是如何在URP下将物体渲染到一个RenderTexture上。
恰巧前阵子 研究战争迷雾 的时候看到了这篇文章:流朔 -【Unity URP】以Render Feature实现卡通渲染中的刘海投影 ,其中就有将物体渲染到RenderTexture的相关操作,这样一来就没有问题了,开搞。
首先创建一个RenderFeature命名为RenderMaskFeature,用于将指定层级物体渲染到一张RenderTexture上,核心代码就是这RenderPass中的这两个函数
1 | public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor) |
其中有几个注意点
首先是降采样,因为扭曲效果本身不会用到太精细的分辨率,所以可以使用很低的分辨率进行采样(当然如果这个扭曲效果要求比较高,比如刀光的扭曲,那么可能就得用屏幕分辨率的1/4,具体情况具体分析),比如我这里使用了128*128的RenderTexture,效果也说得过去(效果见文末)
其次m_ShaderTag类型为ShaderTagId,对应一个Pass中的Tag的LightMode关键字,其实这一点从URP自带的RenderObject的Shader Pass字段ToolTip不难看出
Controls which shader passes to use when rendering objects. The name given here must match the LightMode tag in a shader pass.
最后是overrideMaterial,表示此批次绘制会使用的材质,然后是overrideMaterialPassIndex,表示会用此材质的第几个Pass进行渲染,这个值默认为0,到这里其实就可以发觉,overrideMaterialPassIndex是有可能与我们的m_ShaderTag发生冲突的,我这边实验的结果是,以overrideMaterialPassIndex为准(例如m_ShaderTag的Pass Index为0,但是overrideMaterialPassIndex为1,那么就会使用Pass Index为1的Pass进行渲染)。
然后我就开始遇到坑了,我这里的m_ShaderTag设置和刘海投影那篇文章一样,都是ShaderTagId(“UniversalForward”),当我更换物体材质的时候,打开FrameDebug却发现绘制Mask这一步骤凭空消失了
???这是什么情况,我只是更换了物体的材质,没有更换Render Feature的材质,为什么Render Feature直接失效了?
先来复盘一下场景,物体使用的材质Shader是Universal Render Pipeline/Lit,其中正好有一个Tags{“LightMode” = “UniversalForward”}的Pass,但是我更换了Universal Render Pipeline/Unlit后Render Feature就失效了,思来想去只能是因为Universal Render Pipeline/Unlit中没有LightMode为UniversalForward的Pass,看了下Shader果真如此。
这就有点震惊了,我设置了overrideMaterial,在这个Render Feature中我都没有用到物体自身材质的Pass,那么物体的材质改变就不应该导致Render Feature的失效。随便翻了翻URP的源码也没有看出个所以然来,索性再多试验几种情况,总结一下吧:
- 如果不设置override material的话,就会默认使用物体身上的材质上的目标Pass进行渲染,但是缺点是如果物体的材质没有达成SRP Batch的条件,就无法进行SRP Batch,SRP Batcher相关内容参见:关于静态批处理/动态批处理/GPU Instancing /SRP Batcher的详细剖析
- 如果设置了override marerial的话,就会使用override marerial的目标Pass进行渲染,这种方式可以忽略任何情况(除非你材质本身无法进行SRP Batch)直接一个SRP Batch搞定
- 对于overrideMaterialPassIndex和ShaderTagId冲突的情况,以overrideMaterialPassIndex为准
- 对于更换材质会导致写入Mask Texture失效的情况,是因为,不知道为什么他似乎有个潜规则,就是你的物体身上的材质,必须有一个Pass,它的Tag和你设置的shaderTagId相同,不然就会失效
然后创建另一个RenderFeature,命名为AirDistortionFeature,用于进行后处理操作,核心代码如下
1 | public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) |
然后是空气扭曲的后处理Shader,除了改用了HLSL,内容基本和原文一致
1 | Shader "URP_Practise/Air-distortion" |
然后在RenderFeature中添加我们的RenderMaskFeature和AirDistortionFeature即可,至于噪声图的选择,使用一般水面的噪声图效果应该就挺好了了,我这里随便找了一张噪声
效果如图所示:(可以看到即使场景有非常多的空气扭曲效果,但渲染耗时和单个空气扭曲基本一致)
项目地址
https://github.com/wqaetly/URP_Practise