抓手
本文详说如何在URP管线下做毛玻璃效果。
有两种方法,方法一是用URP自带的OpaqueTexture,但是Opaque是不透明的意思,
所以它的缺陷是半透明的物体它是不会渲染到OpaqueTexture里去的。
方法二是用RendererFeature,本文主要讲第二种方法。
底部附有工程下载。
实现原理
先在一帧里用RendererFeature用某个camera截一帧的图。
然后用CommandBuffer做多次高斯模糊,达到这种毛玻璃效果。
最后渲染到一个RenderTexture上。
具体实现
先写一个ScriptableRendererFeature和ScriptableRenderPass。
RendererFeature的具体理论可以看我之前的文章:《Unity的URP的自定义后处理效果》https://blog.csdn.net/zakerhero/article/details/106793571
先实现ScriptableRendererFeature,名为GlassBlurRenderPassFeature:
using UnityEngine;
using UnityEngine.Rendering.Universal;
public class GlassBlurRenderPassFeature : ScriptableRendererFeature
{
//设置一个RendererFeature的配置
[System.Serializable]
public class Settings
{
public RenderPassEvent renderEvent = RenderPassEvent.BeforeRenderingPostProcessing;
public LayerMask layerMask = -1;
public Material blurMat;
public string textureName = "";
public string cmdName = "";
public string passName = "";
//目标RenderTexture
public RenderTexture blurRt = null;
}
GlassBlurRenderPass m_ScriptablePass;
private RenderTargetHandle dest;
public Settings settings;
public override void Create()
{
m_ScriptablePass = new GlassBlurRenderPass(settings);
m_ScriptablePass.renderPassEvent = settings.renderEvent;
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
var src = renderer.cameraColorTarget;
dest = RenderTargetHandle.CameraTarget;
//把camera渲染到的画面src 传入GlassBlurRenderPass里。
m_ScriptablePass.Setup(src,this.dest);
//注入队列
renderer.EnqueuePass(m_ScriptablePass);
}
}
接下来是ScriptableRenderPass,名为GlassBlurRenderPass
在构造函数中,我把GlassBlurRenderPassFeature的Settings的数据传过来了。
public class GlassBlurRenderPass : ScriptableRenderPass
{
private CommandBuffer cmd;
private string cmdName;
private RenderTargetHandle dest;
private Material m_blurMat;
private RenderTexture m_blurRt;
private RenderTargetIdentifier source { get; set; }
RenderTargetHandle m_temporaryColorTexture;
RenderTargetHandle blurredID;
RenderTargetHandle blurredID2;
public GlassBlurRenderPass(GlassBlurRenderPassFeature.Settings param)
{
renderPassEvent = param.renderEvent;
cmdName = param.cmdName;
m_blurMat = param.blurMat;
m_blurRt = param.blurRt;
blurredID.Init("blurredID");
blurredID2.Init("blurredID2");
}
//其他代码
}
在Setup方法中,我们把camera的渲染到的画面传入到变量source中。该方法在ScriptableRendererFeature的AddRenderPasses方法中被调用。
public void Setup(RenderTargetIdentifier src, RenderTargetHandle _dest)
{
this.source = src;
this.dest = _dest;
}
在Execute函数中,我们用CommandBuffer把source通过高斯模糊的shader处理完后,最终通过CommandBuffer把内容Blit到一个RenderTexture上。
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
if (renderingData.cameraData.isSceneViewCamera) return;
//如果cullingMask包含UI层的camera,返回
if ((renderingData.cameraData.camera.cullingMask & 1<0)
return;
//通过一个外部的静态变量,控制是否只执行一帧
if (!GlassCtrl.takeShot)
return;
cmd = CommandBufferPool.Get(cmdName);
Vector2[] sizes = {
new Vector2(Screen.width, Screen.height),
new Vector2(Screen.width / 2, Screen.height / 2),
new Vector2(Screen.width / 4, Screen.height / 4),
new Vector2(Screen.width / 8, Screen.height / 8),
};
int numIterations = 3;
RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
opaqueDesc.depthBufferBits = 0;
cmd.GetTemporaryRT(m_temporaryColorTexture.id, opaqueDesc, FilterMode.Bilinear);
cmd.Blit(source, m_temporaryColorTexture.Identifier());
for (int i = 0; i < numIterations; ++i)
{
cmd.GetTemporaryRT(blurredID.id, opaqueDesc, FilterMode.Bilinear);
cmd.GetTemporaryRT(blurredID2.id, opaqueDesc, FilterMode.Bilinear);
cmd.Blit(m_temporaryColorTexture.Identifier(), blurredID.Identifier());
cmd.SetGlobalVector("offsets", new Vector4(2.0f / sizes[i].x, 0, 0, 0));
cmd.Blit(blurredID.Identifier(), blurredID2.Identifier(), m_blurMat);
cmd.SetGlobalVector("offsets", new Vector4(0, 2.0f / sizes[i].y, 0, 0));
cmd.Blit(blurredID2.Identifier(), blurredID.Identifier(), m_blurMat);
cmd.Blit(blurredID.Identifier(), m_temporaryColorTexture.Identifier());
}
//把最终内容Blit到一个RenderTexture上。
cmd.Blit(blurredID.Identifier(), m_blurRt);
GlassCtrl.takeShot = false;
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
//Debug.LogError("glass pass render");
}
高斯模糊shader的具体原理不在这里阐述。通俗的理解就是获取一个像素及周围的像素,通过一定的权重(算子)来算出最终模糊的颜色值。可以理解为,我有一堆糖果,但是我想从身边的人换巧克力等其他零食,通过一定的比例换出去换回来,最终我和其他人既有糖果,又有巧克力、冰激凌等各种零食。
具体的shader可以看底部的源码及工程下载,里面的SeparableBlur.shader文件。
最后把GlassBlurRenderPassFeature添加到工程UniversalRenderPipelineAsset_Renderer里,设置它,即可生效。
温馨提示
在Excute方法里,可以通过renderingData.cameraData.camera获取某个camera的数据,
甚至可以获得它的name,来判断它是哪个camera,再判断要不要执行该RendererFeature。
例子:
if ((renderingData.cameraData.camera.cullingMask & 1<0) return;
源码下载:
链接:https://pan.baidu.com/s/1TLPTMJABDCtiADeQ-zFSig
提取码:u70p
源码工程使用:
Unity导入该工程后,切换URP环境,打开SampleScene并play。
选择Main Camera,在Inspector面板勾选Glass Ctrl组件的Show Glass即可看到效果。
参考文章
https://lab.uwa4d.com/lab/5b5613a3d7f10a201fd80bbb