Unity - URP RenderFeature - 实现类似多 Pass 的 XRay: Rim、Pattern

文章目录

  • 环境
  • XRay : Rim, Pattern, Pattern+Rim
    • 在 Built-RP 中
    • 在 URP 中
      • Shader
      • 脚本
      • 效果
  • Pattern
  • URP RendererFeatures 相对 Built-in RP 多 Pass 的优势
  • Project
  • References


环境

先声明使用的环境信息:
Unity : 2019.4.30f1
URP : 7.7.1

为何要声明环境信息?

因为我自己下了好多个 Unity 版本,发现 URP 都几乎发生了变化,这不得不说:很折腾使用 URP 的开发人员

我已经下载了 5 个 Unity 版本了,-_-!
Unity - URP RenderFeature - 实现类似多 Pass 的 XRay: Rim、Pattern_第1张图片

其中我也发现了: URP学习之七–RenderFeature 他使用的 URP 版本和我的也是不一样的

如下图,是 RendererFeatures Inspector 界面区别
Unity - URP RenderFeature - 实现类似多 Pass 的 XRay: Rim、Pattern_第2张图片

下面我是使用了 高版本的 Unity 2021.1.21f1 ,还好发现和我现在的 RenderFeatures 的功能差不多
Unity - URP RenderFeature - 实现类似多 Pass 的 XRay: Rim、Pattern_第3张图片


XRay : Rim, Pattern, Pattern+Rim


在 Built-RP 中

如果在 Built-in RP 中,我们可能需要这么写 Shader

// jave.lin 2021/10/19 这是伪代码
Shader "YourShaderName" {
	Properties {}
	SubShader {
		Pass {
			Name "DrawBody"
		}
		Pass {
			Name "DrawXRayPattern+Rim"
		}
	}
	Fallback "xxx"
}

然后你打开 Unity 的 Window/Analysic/Frame Debugger…,你会发现材质直接 YourShaderName Shader 的都不能合批,合批原因就是因为使用了多 Pass

为何多 Pass 不能合批?因为每个 Pass 的 GPU Data 可能都不一样,GPU Data 可能包含:ZTest, ZWrite, Blend, Fog, ColorMask, LightMode,还有各种 uniform 可能都不一样,所以合不了 DC


在 URP 中


Shader

先给原始的绘制对象写好 shader

// jave.lin 2021/10/19
// 测试 PP RenderObj 的 原始绘制 Shader

Shader "Test/RF_DrawCharacter"
{
    Properties
    {
        _Color("Color", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Tags
        {
            "RenderType" = "Opaque"
        }
        LOD 100
        Pass
        {
            // 标记上 Stencil,给 XRay 效果时防止重复绘制
            Stencil
            {
                Ref 1
                Comp Always
                Pass Replace
            }
            HLSLPROGRAM
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #pragma vertex vert
            #pragma fragment frag
            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 normal : NORMAL;
            };
            CBUFFER_START(UnityPerMaterial)
                float4 _Color;
            CBUFFER_END
            float3 _LightDirection;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex.xyz);
                o.normal = TransformObjectToWorldNormal(v.normal);
                return o;
            }
            half4 frag(v2f i) : SV_Target
            {
                //half3 L = _LightDirection;
                //return half4(L, 1); // 有时候 _LightDirection 没有数据,xyzw == 0
                // 
                // 建议使用下面的 GetMainLight() 的方式来获取灯光相关信息
                // struct Light 需要 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
                Light light = GetMainLight();
                half3 L = light.direction;
                //return half4(L, 1); // 这里输出颜色可以看到是有效的
                half diffuse = dot(L, normalize(i.normal)) * 0.5 + 0.5;
                return _Color * diffuse;
            }
            ENDHLSL
        }
    }
}

注意,我们用了 Stencil,目的:标记上 Stencil,给 XRay 效果时防止重复绘制

给遮挡的 墙体 对象的 Shader 也写上:(普通的 half lambert 着色即可)

// jave.lin 2021/10/19
// 测试 PP RenderObj 的 原始用于遮挡 Character 的 Shader

Shader "Test/RF_DrawWall"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Tags
        {
            "RenderType" = "Opaque"
        }
        LOD 100
        Pass
        {
            HLSLPROGRAM
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #pragma vertex vert
            #pragma fragment frag
            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 normal : NORMAL;
            };
            CBUFFER_START(UnityPerMaterial)
                float4 _Color;
            CBUFFER_END
            float3 _LightDirection;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex.xyz);
                o.normal = TransformObjectToWorldNormal(v.normal);
                return o;
            }
            half4 frag(v2f i) : SV_Target
            {
                //half3 L = _LightDirection;
                //return half4(L, 1); // 有时候 _LightDirection 没有数据,xyzw == 0
                // 
                // 建议使用下面的 GetMainLight() 的方式来获取灯光相关信息
                // struct Light 需要 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
                Light light = GetMainLight();
                half3 L = light.direction;
                //return half4(L, 1); // 这里输出颜色可以看到是有效的
                half diffuse = dot(L, normalize(i.normal)) * 0.5 + 0.5;
                return _Color * diffuse;
            }
            ENDHLSL
        }
    }
}

虽然说是简单的 Half Lambert,但是也遇到了一些 BUG

留意代码中的:

			...
			#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
			...
            half4 frag(v2f i) : SV_Target
            {
                //half3 L = _LightDirection;
                //return half4(L, 1); // 有时候 _LightDirection 没有数据,xyzw == 0
                // 
                // 建议使用下面的 GetMainLight() 的方式来获取灯光相关信息
                // struct Light 需要 #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
                Light light = GetMainLight();
                half3 L = light.direction;
                //return half4(L, 1); // 这里输出颜色可以看到是有效的
                half diffuse = dot(L, normalize(i.normal)) * 0.5 + 0.5;
                return _Color * diffuse;
            }

后来发现,原来这个是 shadows 使用的灯光方向,在 反编译 unity API ShadowsUtil 中发现的内容

        public static void SetupShadowCasterConstantBuffer(CommandBuffer cmd, ref VisibleLight shadowLight, Vector4 shadowBias)
        {
            Vector3 vector = -shadowLight.localToWorldMatrix.GetColumn(2);
            cmd.SetGlobalVector("_ShadowBias", shadowBias);
            cmd.SetGlobalVector("_LightDirection", new Vector4(vector.x, vector.y, vector.z, 0f));
        }

最后是我们的主角 shader,XRay 的代码,用于 RendererFeatures 中的材质用的 shader:

// jave.lin 2021/10/19
// 测试 PP RenderObj 的 shader
//  - pass 0 绘制 z buffer greater 的部分
//  - pass 1 绘制 z buffer greater 的 Pattern效果

Shader "Test/RF_DrawGreaterZ"
{
    Properties
    {
        _Color ("Color", Color) = (1, 1, 1, 1)
        _RimColor ("Rim Color", Color) = (1, 1, 1, 1)
        _PatternTex ("PatternTex", 2D) = "white" {}
        _PatternAlpha ("Pattern Alpha", Range(0, 1)) = 1
    }
    HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/UnityInput.hlsl"
        CBUFFER_START(UnityPerMaterial)
        half4  _Color;
        half4 _RimColor;
        half4 _PatternTex_ST;
        half _PatternAlpha;
        CBUFFER_END
    ENDHLSL
    SubShader
    {
        Tags 
        {
            "RenderType"="Opaque" 
        }
        LOD 100

        Pass
        {
            // 下面的 draw state 可以在 RendererFeature 中设置 depth, stencil 的 override 来设置,不一定要在这设置
            //ZTest Greater
            //ZWrite Off
            //Stencil
            //{
            //    Ref 1
            //    Comp NotEqual
            //    Pass Replace
            //}
            Name "DrawGreaterZ_Rim"
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 normal : NORMAL;
                float3 positionWS : TEXCOORD0;
            };
            v2f vert (appdata v)
            {
                v2f o;
                o.positionWS = TransformObjectToWorld(v.vertex.xyz);
                o.vertex = TransformWorldToHClip(o.positionWS);
                o.normal = TransformObjectToWorldNormal(v.normal);
                return o;
            }
            half4 frag(v2f i) : SV_Target
            {
                float3 N = normalize(i.normal);
                float3 V = normalize(GetCameraPositionWS() - i.positionWS);
                half rim = 1 - dot(N, V);
                return lerp(_Color, _RimColor, rim);
            }
            ENDHLSL
        }
        Pass
        {
            // 下面的 draw state 可以在 RendererFeature 中设置 depth, stencil 的 override 来设置,不一定要在这设置
            //ZTest Greater
            //ZWrite Off
            //Stencil
            //{
            //    Ref 1
            //    Comp NotEqual
            //    Pass Replace
            //}
            Name "DrawGreaterZ_Pattern"
            Blend SrcAlpha OneMinusSrcAlpha
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            struct appdata
            {
                float4 vertex : POSITION;
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
            TEXTURE2D(_PatternTex);
            SAMPLER(sampler_PatternTex);
            v2f vert (appdata v)
            {
                v2f o = (v2f)0;
                o.vertex = TransformObjectToHClip(v.vertex.xyz);
                float4 uv = ComputeScreenPos(o.vertex);
                o.uv = uv.xy / uv.w;
                o.uv.x *= _ScreenParams.x / _ScreenParams.y;
                o.uv = TRANSFORM_TEX(o.uv, _PatternTex);
                return o;
            }
            half4 frag(v2f i) : SV_Target
            {
                half pattern = SAMPLE_TEXTURE2D(_PatternTex, sampler_PatternTex, i.uv).a;
                return half4(_Color.rgb, pattern * _PatternAlpha);
            }
            ENDHLSL
        }
        Pass
        {
            // 下面的 draw state 可以在 RendererFeature 中设置 depth, stencil 的 override 来设置,不一定要在这设置
            //ZTest Greater
            //ZWrite Off
            //Stencil
            //{
            //    Ref 1
            //    Comp NotEqual
            //    Pass Replace
            //}
            Name "DrawGreaterZ_Pattern+Rim"
            Blend SrcAlpha OneMinusSrcAlpha
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
                float3 positionWS : TEXCOORD1;
            };
            TEXTURE2D(_PatternTex);
            SAMPLER(sampler_PatternTex);
            v2f vert (appdata v)
            {
                v2f o = (v2f)0;
                o.positionWS = TransformObjectToWorld(v.vertex.xyz);
                o.vertex = TransformWorldToHClip(o.positionWS);
                o.normal = TransformObjectToWorldNormal(v.normal);
                float4 uv = ComputeScreenPos(o.vertex);
                o.uv = uv.xy / uv.w;
                // _ScreenSize 在 FrameDebug 中查看到,xyzw 都是 0,BUG?
                //o.uv.x *= _ScreenSize.y / _ScreenSize.x;
                //o.uv.x *= _ScreenParams.y / _ScreenParams.x;
                o.uv.x *= _ScreenParams.x / _ScreenParams.y;
                o.uv = TRANSFORM_TEX(o.uv, _PatternTex);
                return o;
            }
            half4 frag(v2f i) : SV_Target
            {
                // _ScreenSize 在 FrameDebug 中查看到,xyzw 都是 0,BUG?
                //return half4((float(_ScreenSize.y) / _ScreenSize.x).xxx, 1.0);
                // _ScreenParams 才有值
                //return half4((_ScreenParams.y / _ScreenParams.x).xxx, 1.0);
                float3 N = normalize(i.normal);
                float3 V = normalize(GetCameraPositionWS() - i.positionWS);
                half rim = 1 - dot(N, V);
                half pattern = SAMPLE_TEXTURE2D(_PatternTex, sampler_PatternTex, i.uv).a;
                return half4(lerp(_Color.rgb, _RimColor.rgb, rim), pattern * _PatternAlpha);
            }
            ENDHLSL
        }
    }
}

可以看到有三个 Pass,本来一个就够的,只不过,我们可以演示,可以动态的激活其中我们需要的一个 或是 多个 RendererFeatures

然后我们可以新建一个材质,应用一下此 shader
Unity - URP RenderFeature - 实现类似多 Pass 的 XRay: Rim、Pattern_第4张图片

再给 Renderer 添加 RendererFeatures

添加 RendererFeature 的时候,需要设置好:

  • Name - 名字,便于理解、维护用
  • Event - 决定在什么时候开始绘制,下图我们设置的是:AfterRenderingTransparents 就是在渲染透明内容后再渲染这个 RendererFeature 的内容
  • Filters
    • Queue - 渲染所属队列ID,Opaque 是不透明
    • Layer Mask - 要筛选的 layer 渲染层级
    • LightModeTags - 筛选tags 中 LightMode 包含 一个或多个 当中的其一
  • Overrides
    • Material - 此 RendererFeature 渲染时使用的材质
      • Pass Index - 使用 Material 中对应的那个 Pass 索引,从 0 开始
    • Depth - 是否覆盖设置 Depth 状态,如果没有勾,就使用 材质中、ShaderLab 中的设置,深度相关可参考:Unity Shader - ShaderLab: Culling & Depth Testing 剔除与深度测试
      • Write Depth - 是否写入深度,这里不用写入,所以不勾
      • Depth Test - 深度测试的比较条件,这是因为需要 XRay 效果,只需要绘制被遮挡部分所以设置为:Greater
    • Stencil - 是否覆盖设置 Stencil 状态,如果没有勾,就使用 材质中、ShaderLab 中的设置,模板相关可参考:Unity Shader - ShaderLab: Stencil 模板(缓存)
      • Value - Stencil 的参考值
      • Compare Function - 与 Value 参考值的比较方式
        • Pass - 如果比较条件通过,如何操作缓存值,这里使用 Replace 就是使用 Value 参考值替代缓存值的意思
        • Fail - 如果比较条件失败,如何操作缓存值,这里使用 Keep 就是保持缓存值不变的意思
      • Z Fail - 如果在深度比较失败,如何操作缓存值,这里使用 Keep 同上
    • Camera - 相机相关设置,这里暂时用不上,不用管
      Unity - URP RenderFeature - 实现类似多 Pass 的 XRay: Rim、Pattern_第5张图片

脚本

最后我们可以添加一些脚本,在运行时动态的切换不同的 RendererFeature

// jave.lin 2021/10/19
// 测试 URP RendererFeature 的功能脚本

using UnityEngine;
using UnityEngine.Rendering.Universal;

public class TestRendererFeatureScript : MonoBehaviour
{
    public ScriptableRendererData data;
    public void OnRim()
    {
        EnabledRendererFeatureNew(0);
    }

    public void OnPattern()
    {
        EnabledRendererFeatureNew(1);
    }

    public void OnPatternRim()
    {
        EnabledRendererFeatureNew(2);
    }
    private void EnabledRendererFeatureNew(int idx)
    {
        if (data == null)
        {
            Debug.LogError($"{nameof(TestRendererFeatureScript)}.EnabledRendererFeatureNew data == null.");
            return;
        }
        var features = data.rendererFeatures;
        for (int i = 0; i < features.Count; i++)
        {
            features[i].SetActive(i == idx);
        }
    }
}


效果

Unity - URP RenderFeature - 实现类似多 Pass 的 XRay: Rim、Pattern_第6张图片


Pattern

可以使用纹理,也可以使用算法代码生成 Shader Graph 中,我记得有类似的节点

在我很久之前翻译过的一篇文章:Unity Shader - Shader semantics 着色器语义 - 搜索文本:Screen space pixel position: VPOS

            fixed4 frag (v2f i, UNITY_VPOS_TYPE screenPos : VPOS) : SV_Target
            {
                // screenPos.xy 将包含像素的整数坐标值。
                // 用它们实现棋盘团来,替代渲染4x4的像素块(4x4纹理)

                // 棋盘图案每个格将使用4x4的像素块
                screenPos.xy = floor(screenPos.xy * 0.25) * 0.5;
                float checker = -frac(screenPos.r + screenPos.g);

                // 如果checker为负数,则使用HLSL 内置函数 clip 来停止绘制该像素
                clip(checker);

                // 保持绘制的像素将采样纹理值并输出
                fixed4 c = tex2D (_MainTex, i.uv);
                return c;
            }

效果如下:
Unity - URP RenderFeature - 实现类似多 Pass 的 XRay: Rim、Pattern_第7张图片

下面是我显示我再Photoshop中随便创建的一个 PatternTex,4x4 尺寸

原本使用的是 2x2 尺寸,但是 DX11 下显示不最小只能 4x4 才能使用 对应的纹理块压缩算法格式,所以我就调整为 4x4 尺寸的
Unity - URP RenderFeature - 实现类似多 Pass 的 XRay: Rim、Pattern_第8张图片

然后对 PatternTex 的 Filtering 设置为 Point

Pattern 还可以通过 uv 的 scale 的实现 Pattern 的像素粗细度
如下:
Unity - URP RenderFeature - 实现类似多 Pass 的 XRay: Rim、Pattern_第9张图片

下面是偶然发现 unity 2019.4.30f1 中的某个 LOD 淡入淡出的 Dither Shader 处理:
UnityCG.cginc 文件下

#ifdef LOD_FADE_CROSSFADE
    #define UNITY_APPLY_DITHER_CROSSFADE(vpos)  UnityApplyDitherCrossFade(vpos)
    sampler2D unity_DitherMask;
    void UnityApplyDitherCrossFade(float2 vpos)
    {
        vpos /= 4; // the dither mask texture is 4x4
        float mask = tex2D(unity_DitherMask, vpos).a;
        float sgn = unity_LODFade.x > 0 ? 1.0f : -1.0f;
        clip(unity_LODFade.x - mask * sgn);
    }
#else
    #define UNITY_APPLY_DITHER_CROSSFADE(vpos)
#endif

URP RendererFeatures 相对 Built-in RP 多 Pass 的优势

但是 SRP Batcher 也是不能合并 DC 的,那他有什么优势?其实大家都因为了解过?这里就不厌其烦在说一次:可以减少 SetGPUData 次数,也就是 SetPassCall 的次数,可以参考:Unity Shader - shader lab 的 SRP Batcher compatible 兼容性(未使用 RenderDoc 验证 API)

URP 每个 shader 渲染实体对象,都只调用一个 Pass,为的就是提高 SRP Batcher 的可能性

而且 URP 中的灯光都在一个 Pass 支持多个,这有点像我之前写的 OpenGL 中实现多光源,在一个 Pass 中实现:LearnGL - 14 - MultiLight - 多光源,性能通常都会比多 Pass 要高


Project

TestingURP_RendererFeatures_Pattern_Rim.unitypackage 提取码: 73dz


References

  • URP学习之七–RenderFeature
  • Unity URP渲染管线—自定义渲染详解(入门)

你可能感兴趣的:(unity,unity-shader,unity,Renderer,Features)