Unity Shader学习:毛玻璃(Frosted Glass)
毛玻璃也叫磨砂玻璃、霜花玻璃,用来表达能透光但不清晰的半透明效果,如下图所示。本文使用前向渲染和URP管线。
From Unsplash.com
原理
使用高斯模糊来表达不清晰的半透明效果。
先渲染除毛玻璃外的不透明和半透明物体,然后做全屏高斯模糊,将结果保存到RT。最后渲染毛玻璃,在vertex阶段计算毛玻璃顶点在屏幕空间的位置,fragment阶段根据上述屏幕空间位置采样高斯模糊RT,将毛玻璃范围内的RT画在毛玻璃上。
实现
上面原理部分描述了最基础效果。实际开发中,用URP的方式照搬了开源工程 ,完成了一些花活:lerp不同分辨率的高斯模糊,使模糊效果更佳;毛玻璃多一张贴图,控制lerp数值,来呈现不同的毛玻璃效果。
RenderFeature
在场景中放置一Plane作为毛玻璃,设置layer为Glass
毛玻璃物体
在默认ForwardRenderer中添加两个RenderFeature:
[*]GrabScreenBlur:自定义的RendererFeature。使用MyBlur shader的材质,完成除毛玻璃外的全屏高斯模糊,时机为AfterRenderingTransparents;
[*]FrostedGlass:内置的RenderObjects。指定渲染Glass layer的毛玻璃,时机为AfterRenderingTransparents。
ForwardRenderer
GrabScreenBlurRendererFeature源码:
执行MyBlur shader,产出4张RT:_BluredTexture0~3,供MyFrostedGlass shader使用。
public class GrabScreenBlurRendererFeature : ScriptableRendererFeature
{
public class Config
{
public float blurAmount;
public Material blurMaterial;
}
private Config config;
private GrabScreenBlurPass grabScreenBlurPass;
public override void Create()
{
grabScreenBlurPass = new GrabScreenBlurPass(config);
grabScreenBlurPass.renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
grabScreenBlurPass.SetUpColorRT(renderer.cameraColorTarget);
renderer.EnqueuePass(grabScreenBlurPass);
}
// render pass
class GrabScreenBlurPass : ScriptableRenderPass
{
private Material blurMat;
private float blurAmount;
private RenderTextureDescriptor rtDesc;
private RenderTargetIdentifier colorRT;
private int[] sizes = { 1, 2, 4, 8 };
public GrabScreenBlurPass(Config config)
{
blurMat = CoreUtils.CreateEngineMaterial(Shader.Find("MyURPShader/MyBlur"));
blurAmount = config.blurAmount;
profilingSampler = new ProfilingSampler(nameof(GrabScreenBlurPass));
}
public void SetUpColorRT(RenderTargetIdentifier rt)
{
colorRT = rt;
}
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
rtDesc = cameraTextureDescriptor;
}
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
CommandBuffer cmd = CommandBufferPool.Get();
using (new ProfilingScope(cmd, profilingSampler))
{
for (int i = 0; i < sizes.Length; ++i)
{
//downsample
int size = sizes;
rtDesc.width = Screen.width / size;
rtDesc.height = Screen.height / size;
//申请临时RT
int blurRT1 = Shader.PropertyToID(&#34;_BlurRT1_&#34; + i);
int blurRT2 = Shader.PropertyToID(&#34;_BlurRT2_&#34; + i);
cmd.GetTemporaryRT(blurRT1, rtDesc);
cmd.GetTemporaryRT(blurRT2, rtDesc);
//Blur
cmd.SetGlobalVector(&#34;_BlurAmount&#34;, new Vector4(blurAmount / rtDesc.width, 0, 0, 0));
cmd.Blit(colorRT, blurRT1, blurMat);
cmd.SetGlobalVector(&#34;_BlurAmount&#34;, new Vector4(0, blurAmount / rtDesc.height, 0, 0));
cmd.Blit(blurRT1, blurRT2, blurMat);
cmd.SetGlobalVector(&#34;_BlurAmount&#34;, new Vector4(blurAmount * 2 / rtDesc.width, 0, 0, 0));
cmd.Blit(blurRT2, blurRT1, blurMat);
cmd.SetGlobalVector(&#34;_BlurAmount&#34;, new Vector4(0, blurAmount * 2 / rtDesc.height, 0, 0));
cmd.Blit(blurRT1, blurRT2, blurMat);
cmd.SetGlobalTexture(&#34;_BluredTexture&#34; + i, blurRT2);
}
cmd.SetRenderTarget(colorRT);
}
//schedule command buffer
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}
}
Shader
MyBlur:完成全屏高斯模糊。
Pass:
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD;
};
struct Varings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD;
float4 uv01 : TEXCOORD1;
float4 uv23 : TEXCOORD2;
float4 uv45 : TEXCOORD3;
};
Varings vert(Attributes i)
{
Varings o;
VertexPositionInputs posInputs = GetVertexPositionInputs(i.positionOS.xyz);
o.positionCS = posInputs.positionCS;
o.uv = TRANSFORM_TEX(i.uv, _MainTex);
o.uv01 =i.uv.xyxy + _BlurAmount.xyxy * float4(1, 1, -1, -1);
o.uv23 =i.uv.xyxy + _BlurAmount.xyxy * float4(1, 1, -1, -1) * 2.0;
o.uv45 =i.uv.xyxy + _BlurAmount.xyxy * float4(1, 1, -1, -1) * 3.0;
return o;
}
float4 frag(Varings i) : SV_Target
{
float4 color = float4(0, 0, 0, 0);
color += 0.40 * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
color += 0.15 * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv01.xy);
color += 0.15 * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv01.zw);
color += 0.10 * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv23.xy);
color += 0.10 * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv23.zw);
color += 0.05 * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv45.xy);
color += 0.05 * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv45.zw);
return color;
}MyFrostedGlass:完成毛玻璃渲染。
_FrostTexture为毛玻璃纹理,实际只需要一个通道。资源来自参考工程 ,出彩的效果主要靠它。
不同的毛玻璃纹理
_FrostTexture与_FrostIntensity一起控制_BluredTexture0~3四张RT的lerp效果。
Pass:
TEXTURE2D(_FrostTexture);
SAMPLER(sampler_FrostTexture);
TEXTURE2D(_BluredTexture0);
SAMPLER(sampler_BluredTexture0);
TEXTURE2D(_BluredTexture1);
TEXTURE2D(_BluredTexture2);
TEXTURE2D(_BluredTexture3);
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float4 uvBluredTex : TEXCOORD1;
};
Varings vert(Attributes i)
{
Varings o;
VertexPositionInputs posInputs = GetVertexPositionInputs(i.positionOS.xyz);
o.positionCS = posInputs.positionCS;
o.uv = TRANSFORM_TEX(i.uv, _FrostTexture);
o.uvBluredTex = ComputeScreenPos(o.positionCS);
return o;
}
half4 frag(Varings i) : SV_Target
{
float surfSmooth = 1 - SAMPLE_TEXTURE2D(_FrostTexture, sampler_FrostTexture, i.uv).x * _FrostIntensity;
surfSmooth = clamp(0, 1, surfSmooth);
half4 ref00 = SAMPLE_TEXTURE2D(_BluredTexture0, sampler_BluredTexture0, i.uvBluredTex.xy / i.uvBluredTex.w);
half4 ref01 = SAMPLE_TEXTURE2D(_BluredTexture1, sampler_BluredTexture0, i.uvBluredTex.xy / i.uvBluredTex.w);
half4 ref02 = SAMPLE_TEXTURE2D(_BluredTexture2, sampler_BluredTexture0, i.uvBluredTex.xy / i.uvBluredTex.w);
half4 ref03 = SAMPLE_TEXTURE2D(_BluredTexture3, sampler_BluredTexture0, i.uvBluredTex.xy / i.uvBluredTex.w);
float step00 = smoothstep(0.75, 1.00, surfSmooth);
float step01 = smoothstep(0.5, 0.75, surfSmooth);
float step02 = smoothstep(0.05, 0.5, surfSmooth);
float step03 = smoothstep(0.00, 0.05, surfSmooth);
return lerp(ref03, lerp(lerp(lerp(ref03, ref02, step02), ref01, step01), ref00, step00), step03);
}效果
毛玻璃效果
https://www.zhihu.com/video/1447289479442210816
blur次数、downsample数值、毛玻璃贴图、颜色差值方法的不同组合能呈现出不同的效果,挺有意思。
参考
[*]^abhttps://github.com/andydbc/unity-frosted-glass
页:
[1]