一、如何获得颜色缓冲

网上搜索Unity的后处理或者获得屏幕缓冲,大部分会提到用grabpass到一张指定纹理上或者写一个后处理脚本挂在摄像机上。但是这种方式在Urp管线下已经不生效了。urp取消了默认管线抓取颜色缓冲的grabpass,同时也取消了MonoBehaviour.OnRenderImage,需要使用ScriptableRenderPass 来完成类似的功能。ScriptableRenderPass是urp中的pass基类,urp预定义的pass都继承自该类,我们自定义的pass也需要继承自该类。

1.1 Urp的渲染顺序

urp中通过类型RenderPassEvent定义了一些列pass的渲染顺序或者说时机,大致的顺序是ShadowPass->PrePass(Depth Or DepthNormal)->Opaques->SkyBox->Transparents->PostProcessing,这个顺序也是Urp渲染管线的大致执行顺序。每个Pass或者说每个渲染事件都分Before和After,比如BeforePostProcessing和AfterPostProcessing分别表示后处理之前和后处理之后。
说了这么多,现在说结论,我们的特效Pass或者说特效管线就是要插入在BeforePostProcessing这个事件范围内。对了,同一个事件,比如BeforePostProcessing事件内的pass,最终的执行顺序是已加入管线的先后为准的。

1.2 Urp内置的CameraOpaqueTexture

那么,我们是一定要自定义一个Pass才能获得颜色缓冲吗?不需要,其实Urp的ForwardRenderer内会在某种情况下给我生成一个颜色缓冲存储到贴图_CameraOpaqueTexture中,通过调用函数SampleSceneColor就得获得屏幕颜色。不过,这个贴图的生成时机是固定的,只会在渲染不透明物体之后,更准确的说是在渲染天空盒之后,通过CopyColorPass把摄像机的颜色缓冲Blit到_CameraOpaqueTexture。同时,需要摄像机或者Urp设置中有开启需要OpaqueTexture或者某个Pass的Input有要求ColorTexture。
假如,不需要颜色缓冲中有半透明物体的信息,那么这个_CameraOpaqueTexture就已经足够了。问题是,特效基本是半透明物体,部分场景物体也可能是半透明物体。所以,默认的_CameraOpaqueTexture大概率满足不了需求。
因此,需要在半透明物体渲染之后再获取一次颜色缓冲。这个可以通过在AfterTransparents或者BeforePostProcessing事件中插入一个CopyColorPass来实现。

二、特效渲染管线

说实话,特效同学的要求有点多,要求部分特效受到全屏效果影响部分不受到影响。那么,特效要分成两部分渲染,一部分在全屏特效前,另外一部分在全屏特效后。那么,需要至少4个Pass,全屏特效前的特效Pass->CopyColorPass->全屏特效Pass->全屏特效后的特效Pass。
特效渲染管线如下:

  1. EffectPass (渲染后处理特效前的特效)
  2. CopyColorPass (拷贝屏幕颜色)
  3. UberEffectPostRenderPass (渲染后处理特效)
  4. EffectPass(渲染后处理特效后的特效)

其中,中间2个Pass最好是能够根据是否有全屏特效来动态激活。

2.1 EffectRenderFeature

Urp中需要定义RenderFeature来配置相应的Pass。因此,我们定义一个专门用于特效管线的Feature。在这个Feature中,我们按照上述的顺序加入这4个Pass,其中2和3根据全屏特效是否存在来判断是否加入渲染管线。

2.2 兼容UI特效穿插UI的解决方案

由于发现自定义一个BeforeRenderingPostProcessing的特效Pass来专门渲染特效,会导致所有的特效都在半透明物体之后渲染,而UI都是在半透明Pass渲染的,ShaderTag是UniversalForward,这样子会导致根据UI的Canvas来动态计算UI特效的sortingOrder以解决UI特效穿插UI的问题失效。因此,需要去除后处理特效前的特效pass,将这个Pass对应的特效改成默认的UniversalForward的ShaderTag。
那么,特效渲染管线最终是:

  1. Urp默认的DrawObjectsPass(渲染后处理特效前的特效,兼容解决UI特效穿插界面问题的方案)
  2. CopyColorPass (拷贝屏幕颜色)
  3. UberEffectPostRenderPass (渲染后处理特效)
  4. EffectPass(渲染后处理特效后的特效)

关键代码如下,在这个Feature中还定义ColorRT的名字和采样方式、全屏后处理超级Shader的名字等。

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{renderer.EnqueuePass(mEffectBeforePostProcessRenderPass);if (UberEffectPostRenderPass.IsPostProcessEnable()){mCopyColorRenderTarget.Init(RenderTargetName);mCopyColorPass.Setup(renderer.cameraColorTarget, mCopyColorRenderTarget, RenderTargetSampling);renderer.EnqueuePass(mCopyColorPass);renderer.EnqueuePass(mUberEffectPostRenderPass);}renderer.EnqueuePass(mEffectAfterPostProcessRenderPass);
}public override void Create()
{Instance = this;//后处理特效前的特效pass(UniversalForward就会在后处理之前,因此不需要定义专门的Pass,专门的Pass会造成SortingOrder排序失效,UI无法遮挡特效)//mEffectBeforePostProcessRenderPass = new EffectRenderPass(new ShaderTagId("EffectBeforePostProcess"));//mEffectBeforePostProcessRenderPass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;//拷贝颜色缓冲passmSamplingMaterial = CoreUtils.CreateEngineMaterial(Shader.Find("Hidden/Universal Render Pipeline/Sampling"));mCopyColorMaterial = CoreUtils.CreateEngineMaterial(Shader.Find("Hidden/Universal Render Pipeline/Blit"));mCopyColorPass = new CopyColorPass(RenderPassEvent.BeforeRenderingPostProcessing, mSamplingMaterial, mCopyColorMaterial);//特效后处理超级PassmUberEffectPostRenderPass = new UberEffectPostRenderPass();mUberEffectPostRenderPass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;//后处理特效后的特效passmEffectAfterPostProcessRenderPass = new EffectRenderPass(new ShaderTagId("EffectAfterPostProcess"));mEffectAfterPostProcessRenderPass.renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing;
}

Urp的ForwardRender配置如图:

2.3 EffectRenderPass

特效渲染Pass用于渲染普通的特效,Pass跟Shader的对应方式是ShaderTag。关键代码如下,

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{DrawingSettings drawingSettings = CreateDrawingSettings(mShaderTag, ref renderingData, SortingCriteria.CommonTransparent);FilteringSettings filteringSettings = new FilteringSettings(RenderQueueRange.all);context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings);
}

有个需要注意的地方是物体渲染的排序方式要用SortingCriteria.CommonTransparent,毕竟特效都是半透明物体。这个标志是Urp默认的渲染半透明物体的排序方式,理论上是从后到前的顺序渲染。

2.4 UberEffectPostRenderPass

后处理特效Pass为了兼容面片类型的扭曲特效和全屏类型的色散、黑白屏、径向模糊特效,调用了2次绘制函数。第一次是用context.DrawRenderers绘制普通的物体;第二次是用cmd.DrawMesh绘制一个全屏三角形。同时为了支持,场景中出现多个全屏特效,该Pass中保存了一个材质数组,同时根据优先级来排序,优先级高的先渲染,这样就可以实现多个全屏特效的叠加效果。
代码如下,

public void AddMaterial(Material mat, int order = 0)
{var matOrder = mMaterialOrders.Find((temp) => temp.Mat == mat);if (matOrder == null){matOrder = new MaterialOrder();matOrder.Mat = mat;mMaterialOrders.Add(matOrder);}matOrder.Order = order;mMaterialOrders.Sort((a, b) => a.Order - b.Order);
}public void RemoveMaterial(Material mat)
{mMaterialOrders.RemoveAll((temp) => temp.Mat == mat);
}public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{DrawingSettings drawingSettings = CreateDrawingSettings(new ShaderTagId("UberEffectPost"), ref renderingData, SortingCriteria.RenderQueue | SortingCriteria.SortingLayer);FilteringSettings filteringSettings = new FilteringSettings(RenderQueueRange.all);context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings);if (mMaterialOrders == null || mMaterialOrders.Count == 0){return;}CommandBuffer cmd = CommandBufferPool.Get();using (new ProfilingScope(cmd, mProfilingSampler)){//set V,P to identity matrix so we can draw full screen quad (mesh's vertex position used as final NDC position)cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity);for (int i = 0; i < mMaterialOrders.Count; ++i){Material mat = mMaterialOrders[i] != null ? mMaterialOrders[i].Mat : null;if (mat != null && mat.shader.name == mUberEffectPostShaderName){cmd.DrawMesh(RenderingUtils.fullscreenMesh, Matrix4x4.identity, mat, 0, 0);}}cmd.SetViewProjectionMatrices(renderingData.cameraData.camera.worldToCameraMatrix, renderingData.cameraData.camera.projectionMatrix); // restore}context.ExecuteCommandBuffer(cmd);CommandBufferPool.Release(cmd);
}

三、后处理特效

3.1 屏幕扭曲

屏幕扭曲的效果最简单,只是偏移uv坐标即可。实现方式很多,基本上是采样噪声或者法线贴图来偏移uv坐标,核心代码大概如下:

 half2 screenUV = input.screenPos.xy / input.screenPos.w;float2 uvDiffuse = input.uv + float2(_ScreenDistortionU, _ScreenDistortionV) * _Time.y;float4 diffuseTex = tex2D(_ScreenDistortionDiffuse, TRANSFORM_TEX(uvDiffuse, _ScreenDistortionDiffuse));half2 offset = float2(diffuseTex.r, diffuseTex.g) * _ScreenDistortStrength;screenUV = screenUV + offset; return half4(SampleScreenColor(screenUV).rgb, 1);

以上代码计算了2次偏移,第一次偏移是计算噪声图的uv,第二次是计算颜色缓冲的uv,也就是屏幕uv。
效果如下,中间的部分放了一个扭曲面片特效。

3.2 色散

色散的原理也很简单,计算一个偏移的uv,分别在两个方向上计算r和b,不偏移的位置计算g,合并起来作为完整的颜色输出。

    half2 deltaUv = half2(_ColorDispersionStrength * _ColorDispersionU, _ColorDispersionStrength * _ColorDispersionV);result.r = SampleScreenColor(screenUV + deltaUv).r;result.g = SampleScreenColor(screenUV).g;result.b = SampleScreenColor(screenUV - deltaUv).b;

3.3 黑白屏

黑白屏的关键实现代码也很短。但是想出来不太容易。网上大部分实现,就是简单的灰度化加上和屏幕颜色的插件。后面发现特效同学要的东西其实就是网上找了位特效大佬用ASE生成的shader效果,拿到代码后,过滤掉生成的冗余代码发现核心就是下面2个插值计算。

    half luminosity = dot(screenColor.rgb, half3(0.299, 0.587, 0.114));float smoothstepResult = smoothstep(_BlackWhiteThreshold, _BlackWhiteThreshold + _BlackWhiteWidth, luminosity.x);result = lerp(_BlackWhiteWhiteColor,_BlackWhiteBlackColor, smoothstepResult);

关键代码是smoothstep,在阈值和阈值+阈值范围之间曲线插值,返回的值再用来插值白屏颜色色和黑屏颜色。

3.4 径向模糊

径向模糊的思想是沿着到中点的方向采样几个点,然后平均。代码如下,这里假定是6次采样。

    half2 dir = screenUV - half2(_RadialBlurHorizontalCenter, _RadialBlurVerticalCenter);half4 blur = 0;for (int i = 0; i < 6; ++i){half2 uv = screenUV + _RadialBlurWidth * dir * i;blur += SampleScreenColor(uv);}blur /= 6;result = lerp(result, blur, saturate(_RadialBlurStrength));

不过,以上代码不一定能满足美术的需求。比如dir是否需要归一化,lerp时候是否需要考虑距离中点的远近等都会影响最终的效果。

3.5 色散和径向模糊的结合

如果先计算色散的DeltaUv,再将取屏幕颜色替换为屏幕扭曲的话,就能得到一个色散和径向模糊结合的效果,关键代码如下:

 half2 deltaUv = half2(_ColorDispersionStrength * _ColorDispersionU, _ColorDispersionStrength * _ColorDispersionV);result.r = RadialBlur(screenUV + deltaUv, screenColor).r;result.g = RadialBlur(screenUV, screenColor).g;result.b = RadialBlur(screenUV - deltaUv, screenColor).b;

3.6 黑白屏和其它后处理效果的结合

实现方式是,如果开启了黑白屏,将屏幕颜色都应用一次黑白屏,然后再进行其它的处理,比如色散的代码修改为如下,

    half2 deltaUv = half2(_ColorDispersionStrength * _ColorDispersionU, _ColorDispersionStrength * _ColorDispersionV);half4 tempScreenColor = SampleScreenColor(screenUV + deltaUv);
#if _BLACKWHITEtempScreenColor = BlackWhite(tempScreenColor);
#endifresult.r = tempScreenColor.r;tempScreenColor = SampleScreenColor(screenUV);#if _BLACKWHITEtempScreenColor = BlackWhite(tempScreenColor);#endifresult.g = tempScreenColor .g;tempScreenColor = SampleScreenColor(screenUV - deltaUv);#if _BLACKWHITEtempScreenColor = BlackWhite(tempScreenColor);#endifresult.b = tempScreenColor .b;

黑白屏和色散结合:

黑白屏和径向模糊结合:

黑白屏和色散、径向模糊结合:

3.7 UberEffectPost超级Shader

具体实现上,我是用一个超级shader将这些功能整合到一起(除了屏幕扭曲,特效的需求是面片)形成一个UberShader。不同的效果通过shader_feature_local的开关来控制,这样既不用增加额外的大小和内存,也更方便美术同学的使用,整合到一起也是美术提出来的。
材质界面如下,

3.8 UberEffectPost脚本

该脚本继承自MonoBehavior,用于判断是否存在全屏特效以及全屏特效材质、全屏特效优先级设置,并且在材质改变时候将后处理材质传入Pass等。
另外,美术同学要求加的后处理参数控制曲线也是在该脚本中,截图如下:

这些参数曲线相对于TimeLine来说,可以更快的生成动态变化的后处理效果,减少美术去编辑TimeLine的工作量,不过自由度会有所降低。

四、参考资料

1、OnRenderImage
2、仿.碧蓝幻想versus黑白闪后处理shader分享(build_in 与urp双版本)

Urp下自定义特效管线和后处理特效实现相关推荐

  1. 用osgEarth实现Cesium的后处理特效(1)

    写这个博客的初衷:开源的数字地球发展,从最开始的worldWind(纯正的NASA血统,大约从2006年开始流行,当我还是学生的时候用这个框架实现了中石油的林火蔓延模拟系统,以及在这个球上实现救援应急 ...

  2. 虚幻UE4的后处理特效介绍

    虚幻UE4提供了后处理特效的功能,可以实现景深,光溢出,色调调整,饱和度等等.要使用虚幻4的后处理,就一定要用到PostProcessVolumn,这是一种特殊的体积,可以放置在场景中的任何位置.   ...

  3. 虚幻UE4的后处理特效介绍 http://www.52vr.com/thread-31215-1-1.html

    转载 虚幻UE4提供了后处理特效的功能,可以实现景深,光溢出,色调调整,饱和度等等.要使用虚幻4的后处理,就一定要用到PostProcessVolumn,这是一种特殊的体积,可以放置在场景中的任何位置 ...

  4. android+直播点赞,Android自定义View实现直播点赞特效的方法

    Android自定义View实现直播点赞特效的方法 发布时间:2020-07-30 09:24:13 来源:亿速云 阅读:77 作者:小猪 这篇文章主要讲解了Android自定义View实现直播点赞特 ...

  5. Unity3D图像后处理特效——Grayscale image effect

    Grayscale is a simple image effect that changes colors to grayscale by default. It can also use a Te ...

  6. unityURP管线学习+后处理

    unityURP管线学习+后处理 一,前置知识 RenderPipeline 默认管线RenderPipeline Scriptable Render Pipeline可编程渲染管线 二,URP渲染流 ...

  7. URP下Alpha从Gamma空间到Linner空间转换(二)——多Alpha贴图叠加

    文章目录 URP 下取消勾选贴图SRGB, 后处理脚本 多重后处理问题 效果对比 完整参考代码 使用后处理进行一次GammaToLinner的转换,能达到正确效果 URP 下取消勾选贴图SRGB, 后 ...

  8. 异贝,通过移动互联网技术,为中小微实体企业联盟、线上链接、线上线下自定义营销方案推送。案例57

    欢迎关注异贝.异贝5G营销工具,今天为各位分享一个空手套白狼的顶尖思维带案例,相信一定会对大家有帮助,这是一位汗蒸店老板做的一套方案,下面为各位详细分享具体操作步骤. 首先老板做了以下的一个活动: 1 ...

  9. opensuse-KDE桌面下自定义快捷键,ctrl+alt+t打开konsole

    2019独角兽企业重金招聘Python工程师标准>>> opensuse-KDE桌面下自定义快捷键,ctrl+alt+t打开konsole 转载于:https://my.oschin ...

最新文章

  1. 史上最全的“大数据”学习资源
  2. 微软出手,干翻 IDEA?网友:先干翻Eclipse吧..
  3. NSUserDefaults 简介
  4. PDF签名系列(1):PDF签名机制的漏洞分析
  5. 获取arraylist的长度_啃碎JDK源码(三):ArrayList
  6. java的JDK配置
  7. (转)收集 Spring Boot 相关的学习资料,Spring Cloud点这里 重点推荐:Spring Boot 中文索引...
  8. Flink中task之间的数据交换机制
  9. 【车载IoT】国标《电动汽车远程服务与管理系统技术规范》:车载设备设计规范
  10. 计算机台式电源3c号,3C认证和电脑电源有什么关系呢 其实很重要
  11. ASK、OOK、FSK、GFSK 学习
  12. lzg_ad:FBW控制台命令详解
  13. 存储器——Cache
  14. 2021-2027全球及中国G Suite销售软件行业研究及十四五规划分析报告
  15. 谷歌牛逼:720p高清+长镜头,网友:对短视频行业冲击太大
  16. oracle中文名转拼音,oracle 汉字转拼音
  17. Prometheus Operator 配置PrometheusRule告警规则
  18. 方舟原始恐惧mod生物代码_《方舟:恐惧进化4》登场,各种幽灵生物你成功驯服了吗?...
  19. You may have an infinite update loop in a component render function问题解决
  20. 彻底搞清 C/C++中a++与++a的区别

热门文章

  1. Office Word 孤行控制
  2. ElasticSearch使用(嵌套查询、嵌套高亮)
  3. 由siri谈苹果公司的发展
  4. Linux系列(一): 在虚拟机中安装Ubuntu
  5. python列表嵌套元组拆分_Python进阶之元组拆包及嵌套元组拆包
  6. 免费网盘如何选择@2020年
  7. mailbox的controller
  8. Hyper-V导出虚拟机/导入虚拟机步骤(克隆),以及克隆之后设置静态IP无效的解决办法
  9. CDN服务及如何获取CDN服务背后的真实IP
  10. 腾讯运维专家的自我修养