APSchmidt 发表于 2022-1-12 08:58

Unity之URP初探和自定义的PostProcessing

首先看shader方面

在语法层面
Tags标签,使用了URP的shadere需要声明RenderPipeline为UniversalPipeline,LightMode为UniversalForward
                Tags
                {
                        "RenderPipeline" = "UniversalPipeline"
                }而关于URP的LightMode的Tags,则有这样几种类型:UniversalForward(前向渲染)、ShadowCaster(投射阴影)、DepthOnly(深度图)、Mata(烘培)、Universal2D(2D版本的前向渲染)、UniversalGBuffer(GBuffer相关,不太清楚干嘛的)
由于shader语言从cg转为了hlsl,所以语法方面也有一定变化。

[*]HLSL不再有fixed类型,而是用half类型代替
[*]对于由CGINCLUDE...ENDCG/CGPROGRAM...ENDCG包含的代码段,改为了使用HLSLINCLUDE...ENDHLSL/HLSLPROGRAM...ENDHLSL包裹
[*]HLSL使用采样函数和采样器函数TEXTURE2D(_Tex)和SAMPLER(sampler_Tex)取代之前的sampler2D函数,他们定义在GLCore库内。
[*]不需要再包含.cginc文件,而转为了.hlsl文件,以下是一些常用的.hlsl文件
//URP核心库文件
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
//灯光相关
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
//矩阵、输入相关
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl"
//空间变换
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl"
//深度图
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
//一些工具函数,包含Linear01Depth等
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"

[*]关于深度纹理要使用相机深度纹理,需要包含DeclareDepthTexture.hlsl,则会声明_CameraDepthTexture,然后也会包含一些工具辅助函数。
其他可以参见:
From-Built-in-to-URP
以下是一段《Shder入门精要》第八章AlphaTest的shader代码的URP版本:
Shader "Chapter8_AlphaTest"
{
        Properties
        {
                _Color("Main Tint",Color) = (1,1,1,1)
                _MainTex("Main Tex",2D) = "white"{}
                _Cutoff("Alpha _Cutoff",Range(1,0)) = 0.5
        }

        SubShader
        {
                Tags
                {
                        "Queue" = "AlphaTest"//2450
                        "IgnoreProjector" = "True"
                        "RenderType"="TransparentCutout"
                        //转到URP后,所有的sub tag中都要像下面这样指定URP渲染
                        "RenderPipeline" = "UniversalPipeline"
                }

                Pass
                {
                        Tags
                        {
                                "LightMode" = "UniversalForward"
                        }
                        HLSLINCLUDE
                        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
                        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
                        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Input.hlsl"
                        #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl"

                        half4 _Color;
                        float4 _MainTex_ST;
                        TEXTURE2D(_MainTex);
                        SAMPLER(sampler_MainTex);
                        half _Cutoff;

                        ENDHLSL

                        HLSLPROGRAM

                        #pragma vertex vert
                        #pragma fragment frag

                        struct a2v
                        {
                                float4 vertex   : POSITION;
                                float3 normal   : NORMAL;
                                float4 texcoord :TEXCOORD0;
                        };

                        struct v2f
                        {
                                float4 pos                    : SV_POSITION;
                                float3 worldNormal : TEXCOORD0;
                                float3 worldPos           : TEXCOORD1;
                                float2 uv                   :TEXCOORD2;
                        };

                        v2f vert(a2v v)
                        {
                                v2f o;
                                //o.pos = UnityObjectToClipPos(v.vertex);
                                //o.worldNormal = UnityObjectToWorldNormal(v.normal);
                                //o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                                //o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);

                                //模型空间变换到裁剪空间(SpaceTransforms.hlsl)
                                o.pos = TransformObjectToHClip(v.vertex);
                               
                                //获取世界空间下的法线方向
                                o.worldNormal = TransformObjectToWorldNormal(v.normal.xyz, true);
                                //o.worldNormal = UnityObjectToWorldNormal(v.normal);

                                //世界空间下的顶点位置
                                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                                o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);

                                return o;
                        }

                        half4 frag(v2f i) : SV_Target
                        {
                                //对贴图进行采样
                                half4 texColor = SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex,i.uv);
                                //half4 texColor = tex2D(_MainTex,i.uv);

                                clip(texColor.a - _Cutoff);

                                // if(texColor.a - _Cutoff < 0.0)
                                //         discard;

                                half3 worldNormal = normalize(i.worldNormal);
                                //世界光源方向
                                half3 worldLightDir = normalize(_MainLightPosition.xyz - i.worldPos);
                                //half3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                                half3 albedo = texColor.rgb * _Color.rgb;

                                //环境光
                                half3 ambient = half3(unity_SHAr.w,unity_SHAg.w,unity_SHAb.w) * albedo;
                                //half3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

                                //主光源(_MainLightColor)
                                half3 diffuse = _MainLightColor.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
                                //half3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
                                return half4 ( ambient + diffuse, 1.0);
                        }
                        ENDHLSL
                }

        }
}
关于Post-Processing

如果在unity中使用URP,则OnRenderImage函数将不再被支持,因此无法在URP中使用这种方式来对屏幕图像进行后处理,如果要在URP中使用自定义的后处理。则需要用到SRP相关的一些东西。
首先创建一个class并继承ScriptableRendererFeature并实现相应函数Create()和AddRenderPasses(ScriptableRender,ref RenderingData),然后是实现一个继承自ScriptableRenderPass的类型,并且让RenderFeature持有一个RenderPass的引用。

[*]RenderFeature的作用正如其名字一样,用来描述一个渲染特性,他会在管线的某个时间点增加一个或多个自定义pass到管线流程中。Create()函数会对该feature做一些初始化。
      //实例化并传参,name就是tag
      _renderPass = new CustomRenderPass( _setting._passEvent, _setting._mat, _setting._matPassIdx, name );
这里RenderPass的构造函数是自己实现的,在这里为他指定了passEvent,渲染要使用的材质,shader pass的index以及feature的name。关于passEvent,它描述了将自定义pass插入渲染管线的时机。包含如下几个类型
   
    public enum RenderPassEvent
    {
      BeforeRendering = 0,
      BeforeRenderingShadows = 50,
      AfterRenderingShadows = 100,
      BeforeRenderingPrepasses = 150,
      AfterRenderingPrePasses = 200,
      BeforeRenderingOpaques = 250,
      AfterRenderingOpaques = 300,
      BeforeRenderingSkybox = 350,
      AfterRenderingSkybox = 400,
      BeforeRenderingTransparents = 450,
      AfterRenderingTransparents = 500,
      BeforeRenderingPostProcessing = 550,
      AfterRenderingPostProcessing = 600,
      AfterRendering = 1000
    }
可以看到这里也是基于LWRP的

[*]AddRenderPass函数则会在render中插入ScriptableRenderPass,该render对每个相机都设置一次。
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
      var src = renderer.cameraColorTarget;
      //对自定义的renderPass进行一些设置,这里是自己实现的函数而非override
      _renderPass.Setup( src ,_setting);
      //将自定义pass插入到pass队列中
      renderer.EnqueuePass( _renderPass );
    }
接下来看RenderPass类型
他有如下几个流程函数:
//Configure函数会在渲染过程执行前被调用,可以在这里配置render target,初始化状态,创建临时的渲染纹理      
public override void Configure ( CommandBuffer, RenderTextureDescriptor )
//Excute是后处理的核心函数,在这里来写后处理的逻辑和图像混合,基本相当于built-in管线的OnRenderImage函数
public override void Execute(ScriptableRenderContext, ref RenderingData)
//FrameCleanup用于释放本次渲染流程创建的分配资源,他在完成渲染相机后调用
public override void FrameCleanup ( CommandBuffer)
其中比较关键的是Excute函数。这里放一段代码来说明
      public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
      {
         //这里的Comp代表一个继承自VolumeComponent并且实现了IPostProcessComponent接口的类,前者是URP提供的
         //用于做后处理的官方组件,后者表示他的状态
            if (Comp != null)
            {
                //设置pass参数值
                _passMat.SetFloat( "_Distance", Comp._distance.value );
                _passMat.SetFloat( "_Width", Comp._widht.value );
                _passMat.SetColor( "_Color", Comp._color.value );
            }

            //从命令缓存池中获取一个GL命令
            //CommandBuffer类主要用于收集一系列GL指令,然后执行
            CommandBuffer cmd = CommandBufferPool.Get(_passTag);
            RenderTextureDescriptor descriptor = renderingData.cameraData.cameraTargetDescriptor;
            descriptor.depthBufferBits = 0;

            //申请一个临时图像
            cmd.GetTemporaryRT
                (
                  _passTemplateTex.id,
                  descriptor,
                  _setting is null ? FilterMode.Bilinear : _setting._filterMode
                );

            //将source输入到对应材质的pass里进行处理,并将处理结果保存在_passTemplateTex
            //_passSource声明在RenderFeature中,RenderTargetIdentifier类型,表示源图像,
            //在AddrenderPasses时通过render.cameraColorTarget可以获取到
            Blit( cmd, _passSource, _passTemplateTex.Identifier(), _passMat, _passMatInt );
            //然后把结果放回source
            Blit( cmd, _passTemplateTex.Identifier(), _passSource );
            //执行命令缓冲区的该命令
            context.ExecuteCommandBuffer( cmd );
            //释放该命令
            CommandBufferPool.Release( cmd );
            //释放临时图像
            cmd.ReleaseTemporaryRT( _passTemplateTex.id );
      }
在这里就完成了关于后处理的流程处理,但还有一个问题那就是如何通过逻辑代码来控制它。由于要使用URP必须先创建UniversalRenerPipelineAsset并且在渲染相机中指定他,而在RenderAsset中使用新的RenderFeature需要在面板中手动添加。

[*]一种方式是对URP相机的Render属性进行设置,让其在不同时间使用不同的RenderAsset




可以使用不同的RenderAsset。

[*]另一种方式是通过脚本和逻辑代码来控,方式如下。
首先是新建一个继承自VolumComponent并且实现了IPostProcessComponent接口的类型Custom_VolumComponent

public class Custom_VolumComponent : VolumeComponent, IPostProcessComponent
{
    //这里是用于传入shader的面板参数
    private ClampedFloatParameter _distance = new ClampedFloatParameter( 0.005f, 0f, 0.01f );
    private ClampedFloatParameter _widht = new ClampedFloatParameter( 0.0001f, 0.0001f, 0.0117f );
    private ColorParameter _color = new ColorParameter( Color.white );

    public bool IsActive () => active;
    public bool IsTileCompatible () => false;
}
举个例子,想要实现一个在Update中随时间变化的Post-Processing效果。使用一个继承自MonoBehavior的脚本,并在其中持有一个Custom_VolumComponent类型的字段。把该脚本挂在相机上。并且在相机上放一个Volume组件,他表示Unity官方提供的一些后处理效果组件。通过Add Override添加自定义的后处理组件Custom_VolumComponent。
在控制脚本中,对Custom_VolumComponent字段进行初始化
      var volumComp = GetComponent<Volume>();
      if (volumComp == null)
      {
            Debug.LogError( "<color=red>failt to get volum component</color>" );
            return;
      }

      if (!volumComp.sharedProfile.TryGet( out _customComp))
      {
            Debug.LogError( "<color=red>failt to get custom component from volumComp</color>" );
            return;
      }
其实获取自定义的volume组件还有另外一种方式:VolumeManager.instance.stack.GetComponent<T>();类型T指定了自定义实现的volume类型,但这里拿到的是独立于挂在相机上的Volume组件的。因此无法在控制脚本中通过对其设置参数的方式来实现随参数实时变化的后处理效果。
至此自定义的后处理效果基本已经完成,在Update中对他进行控制就可以了
    private void Update ()
    {
      if (!_openFlag)
            return;

      _scannerComp._color.value = _color;
      _time += Time.deltaTime;
      if (_time >= _timerLmt)
      {
            _distance = 0f;
            time = 0f;
      }

      _distance += _speed * Time.deltaTime;
      _customComp._distance.value = _distance;
    }

参考文档:
Unity 中的 HLSL - Unity 手册
URP官方文档
Unity官方:URP系列教程 | 手把手教你如何用Renderer Feature
Unity官方:URP系列教程 | 如何使用Scriptable Renderer Feature来自定义后处理效果
页: [1]
查看完整版本: Unity之URP初探和自定义的PostProcessing