NoiseFloor 发表于 2021-11-27 16:44

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("_BlurRT1_" + i);
                                        int blurRT2 = Shader.PropertyToID("_BlurRT2_" + i);
                                        cmd.GetTemporaryRT(blurRT1, rtDesc);
                                        cmd.GetTemporaryRT(blurRT2, rtDesc);

                                        //Blur
                                        cmd.SetGlobalVector("_BlurAmount", new Vector4(blurAmount / rtDesc.width, 0, 0, 0));
                                        cmd.Blit(colorRT, blurRT1, blurMat);
                                        cmd.SetGlobalVector("_BlurAmount", new Vector4(0, blurAmount / rtDesc.height, 0, 0));
                                        cmd.Blit(blurRT1, blurRT2, blurMat);
                                        cmd.SetGlobalVector("_BlurAmount", new Vector4(blurAmount * 2 / rtDesc.width, 0, 0, 0));
                                        cmd.Blit(blurRT2, blurRT1, blurMat);
                                        cmd.SetGlobalVector("_BlurAmount", new Vector4(0, blurAmount * 2 / rtDesc.height, 0, 0));
                                        cmd.Blit(blurRT1, blurRT2, blurMat);

                                        cmd.SetGlobalTexture("_BluredTexture" + 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]
查看完整版本: Unity Shader学习:毛玻璃(Frosted Glass)