知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【Unity Shader】Built-inのポストエフェクトをURPに変更 #117

はじめに

Bilt-inで記載されているポストエフェクトをURPに置換してみます。

今回対応させて頂くものは、以下サイト様のものになります。

light11.hatenadiary.com

ソースコード

ScriptableRendererFeature

using UnityEngine;
using UnityEngine.Rendering.Universal;

namespace Day5.Practice2
{
    public class GlareRendererFeature : ScriptableRendererFeature
    {
        [SerializeField]
        private Shader shader;

        [SerializeField, Range(0.0f, 1.0f)]
        private float threshold = 0.5f;
        [SerializeField, Range(0.5f, 0.95f)]
        private float attenuation = 0.9f;
        [SerializeField, Range(0.0f, 10.0f)]
        private float intensity = 1.0f;

        private GlarePass glarePass;

        public override void Create()
        {
            glarePass = new GlarePass(shader);
        }

        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            glarePass.SetRenderTarget(renderer.cameraColorTarget);
            glarePass.SetShaderProperty(threshold, attenuation, intensity);
            renderer.EnqueuePass(glarePass);
        }
    }
}

ScriptableRenderPass

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace Day5.Practice2
{
    public class GlarePass : ScriptableRenderPass
    {
        private const string ProfilerTag = nameof(GlarePass);

        private readonly Material material;
        private readonly int paramsPropertyId = Shader.PropertyToID("_Params");
        private readonly int thresholdPropertyId = Shader.PropertyToID("_Threshold");
        private readonly int attenuationPropertyId = Shader.PropertyToID("_Attenuation");
        private readonly int intensityPropertyId = Shader.PropertyToID("_Intensity");

        private RenderTargetHandle destRenderTargetHandle;
        private RenderTargetHandle tmpRenderTargetHandle1;
        private RenderTargetHandle tmpRenderTargetHandle2;

        private RenderTargetIdentifier cameraColorTarget;
        private float threshold;
        private float attenuation;
        private float intensity;

        public GlarePass(Shader shader)
        {
            material = CoreUtils.CreateEngineMaterial(shader);
            renderPassEvent = RenderPassEvent.AfterRenderingTransparents;
            destRenderTargetHandle.Init("_destRT");
            tmpRenderTargetHandle1.Init("_TempRT1");
            tmpRenderTargetHandle2.Init("_TempRT2");

        }

        public void SetRenderTarget(RenderTargetIdentifier target)
        {
            cameraColorTarget = target;
        }

        public void SetShaderProperty(float threshold, float attenuation, float intensity)
        {
            this.threshold = threshold;
            this.attenuation = attenuation;
            this.intensity = intensity;
        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            if (renderingData.cameraData.isSceneViewCamera)
            {
                return;
            }

            var cmd = CommandBufferPool.Get(ProfilerTag);

            var descriptor = renderingData.cameraData.cameraTargetDescriptor;

            cmd.GetTemporaryRT(destRenderTargetHandle.id, descriptor);
            cmd.GetTemporaryRT(tmpRenderTargetHandle1.id, descriptor);
            cmd.GetTemporaryRT(tmpRenderTargetHandle2.id, descriptor);

            material.SetFloat(thresholdPropertyId, threshold);
            material.SetFloat(attenuationPropertyId, attenuation);
            material.SetFloat(intensityPropertyId, intensity);

            cmd.Blit(cameraColorTarget, destRenderTargetHandle.Identifier());

            for (var i = 0; i < 4; i++)
            {
                cmd.Blit(cameraColorTarget, tmpRenderTargetHandle1.Identifier(), material, 0);

                var currentSrc = tmpRenderTargetHandle1.Identifier();
                var currentTarget = tmpRenderTargetHandle2.Identifier();
                var parameters = Vector3.zero;

                parameters.x = i is 0 or 1 ? -1 : 1;
                parameters.y = i is 0 or 2 ? -1 : 1;

                for (var j = 0; j < 4; j++)
                {
                    parameters.z = j;
                    cmd.SetGlobalVector(paramsPropertyId, parameters);
                    cmd.Blit( currentSrc, currentTarget, material, 1);
                    (currentSrc, currentTarget) = (currentTarget, currentSrc);
                }

                cmd.Blit(currentSrc, destRenderTargetHandle.Identifier(), material, 2);
            }

            cmd.Blit(destRenderTargetHandle.Identifier(), cameraColorTarget);

            cmd.ReleaseTemporaryRT(destRenderTargetHandle.id);
            cmd.ReleaseTemporaryRT(tmpRenderTargetHandle1.id);
            cmd.ReleaseTemporaryRT(tmpRenderTargetHandle2.id);

            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    }
}

シェーダー

Shader "Day5/URPGlare"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Cull Off
        ZTest Always
        ZWrite Off

        Tags
        {
            "RenderType"="Opaque"
            // レンダリングパイプラインをURPにする
            "Renderpipeline" = "UniversalPipeline"
        }

        Pass
        {
            // HLSLを記述する
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            // hlslでよく使用されるマクロをインクルード
            // #include "UnityCG.cginc"に近い
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            // appdeta -> Attributes
            struct Attributes
            {
                // vertex -> positionOS
                // OSはObject Spaceの略
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            // v2f -> Varyings
            struct Varyings
            {
                float2 uv : TEXCOORD0;
                // vertex -> positionHCS
                // HSCはHomogeneous Clip Space(等質クリップ座標)の略
                float4 positionHCS : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Threshold;
            // Texture2Dを宣言
            TEXTURE2D(_CameraDepthTexture);
            // SamplerStateを宣言
            SAMPLER(sampler_CameraDepthTexture);

            Varyings vert (Attributes IN)
            {
                Varyings OUT;
                // UnityObjectToClipPos -> TransformObjectToHClip
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = TRANSFORM_TEX(IN.uv, _MainTex);
                return OUT;
            }

            // hlslではfixedが使えないのでhalfにする
            half4 frag (Varyings IN) : SV_Target
            {
                // SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
                // textureとsamplerが必要になった
                half depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, IN.uv);
                // Linear01Depth(depth);
                // zBufferParamが必要になった
                half linear01Depth = Linear01Depth(depth, _ZBufferParams);
                half4 col = tex2D(_MainTex, IN.uv);
                half brightness = max(col.r, max(col.g, col.b));
                half contribution = max(0, brightness - _Threshold);
                contribution /= max(brightness, 0.00001);
                return col * contribution * (1 - linear01Depth);
            }

            // HLSLの記述を終える
            ENDHLSL
        }

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                float2 uv : TEXCOORD0;
                float4 positionHCS : SV_POSITION;
                half2 uvOffset : TEXCOORD1;
                half pathFactor : TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            half4 _MainTex_TexelSize;
            half3 _Params;
            float _Attenuation;

            Varyings vert (Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = TRANSFORM_TEX(IN.uv, _MainTex);
                OUT.pathFactor = pow(4, _Params.z);
                OUT.uvOffset = half2(_Params.x, _Params.y) * _MainTex_TexelSize.xy * OUT.pathFactor;
                return OUT;
            }

            half4 frag (Varyings IN) : SV_Target
            {
                half4 col = half4(0, 0, 0, 1);

                half2 uv = IN.uv;
                [unroll]
                for (int j = 0; j < 4; j++)
                {
                    col.rgb += tex2D(_MainTex, uv).rgb * pow(saturate(_Attenuation), j * IN.pathFactor);
                    uv += IN.uvOffset;
                }

                return col;
            }
            ENDHLSL
        }

        Pass
        {
            Blend One One
            ColorMask RGB

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct Varyings
            {
                float2 uv : TEXCOORD0;
                float4 positionHCS : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Intensity;

            Varyings vert (Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = TRANSFORM_TEX(IN.uv, _MainTex);
                return OUT;
            }

            half4 frag (Varyings IN) : SV_Target
            {
                return tex2D(_MainTex, IN.uv) * _Intensity;
            }
            ENDHLSL
        }
    }
}

スクリプト側について

ポストエフェクトの解説については元ブログ様を参考にしてください。
また、RendererFeatureについては以下記事で行っています。

soramamenatan.hatenablog.com

URP Assetの設定

Depth Textureの設定をオンにします。 そうすることでDepthTextureである_CameraDepthTextureが取得できます。

f:id:soramamenatan:20220402021507p:plain

シェーダーをURPに対応

シェーダー にコメントを記載しているので、そちらを参考にしてください。
以下に自分が気になったマクロの中身を記載します。

TransformObjectToHClip

// Transforms position from object space to homogenous space
float4 TransformObjectToHClip(float3 positionOS)
{
    // More efficient than computing M*VP matrix product
    return mul(GetWorldToHClipMatrix(), mul(GetObjectToWorldMatrix(), float4(positionOS, 1.0)));
}

// Transform to homogenous clip space
float4x4 GetWorldToHClipMatrix()
{
    return UNITY_MATRIX_VP;
}

// Return the PreTranslated ObjectToWorld Matrix (i.e matrix with _WorldSpaceCameraPos apply to it if we use camera relative rendering)
float4x4 GetObjectToWorldMatrix()
{
    return UNITY_MATRIX_M;
}

SAMPLE_DEPTH_TEXTURE

#define SAMPLE_DEPTH_TEXTURE(textureName, samplerName, coord2)          SAMPLE_TEXTURE2D(textureName, samplerName, coord2).r

Linear01Depth

// Z buffer to linear 0..1 depth (0 at camera position, 1 at far plane).
// Does NOT work with orthographic projections.
// Does NOT correctly handle oblique view frustums.
// zBufferParam = { (f-n)/n, 1, (f-n)/n*f, 1/f }
float Linear01Depth(float depth, float4 zBufferParam)
{
    return 1.0 / (zBufferParam.x * depth + zBufferParam.y);
}

結果

Threshold

f:id:soramamenatan:20220402030830g:plain

Attenuation

f:id:soramamenatan:20220402030853g:plain

Intensity

f:id:soramamenatan:20220402030920g:plain

不明な点

for文内でのmaterial.SetHoge()

for (var j = 0; j < 4; j++)
{
    parameters.z = j;
    cmd.SetGlobalVector(paramsPropertyId, parameters);
    cmd.Blit( currentSrc, currentTarget, material, 1);
    (currentSrc, currentTarget) = (currentTarget, currentSrc);
}

cmd.SetGlobalVector()を初めはmaterial.SetVector()で行っていたが、エフェクトが適応されないでいた。
原因としては、シェーダーにparametersが常に(1, 1, 3)で渡っていたため。

f:id:soramamenatan:20220402032509p:plainf:id:soramamenatan:20220402032515p:plainf:id:soramamenatan:20220402032521p:plainf:id:soramamenatan:20220402032526p:plain

SetGlobalVector()にすることで解決したが、詳しい原因は不明。