【Unity】ノーマルバッファから法線とラフネスデータを読み込む

投稿者: | 2024-01-20

Custom FullScreen Pass シェーダーで、ノーマルバッファから法線とラフネスのデータを読み込んでみました。

カスタムパス

Projectウィンドウで「Custom FullScreen Pass シェーダー」を新規作成し、マテリアルに設定します。

Custom Pass VolumeコンポーネントにFullScrreenCustomPassを追加し、マテリアルをアタッチします。

Custom FullScreen Pass シェーダー

シェーダーでは、includeディレクティブでNormalBuffer.hlslを読み込みます。

    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/NormalBuffer.hlsl"

DecodeFromNormalBuffer関数で法線とラフネスのデータを読み込みます。

    float4 FullScreenPass(Varyings varyings) : SV_Target
    {
        UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(varyings);

        // 深度
        float depth = LoadCameraDepth(varyings.positionCS.xy);

        NormalData normalData;

        // 法線とラフネスのデータを読み込む
        DecodeFromNormalBuffer(varyings.positionCS.xy, normalData);

NormalDataに法線とラフネスの値が含まれています。

        // ファークリッププレーンを除外
        float isNotFarClip = depth != UNITY_RAW_FAR_CLIP_VALUE;

        // float roug = normalData.perceptualRoughness;
        // return float4(roug, roug, roug, 1) * isNotFarClip;
        return float4(normalData.normalWS * isNotFarClip, 1);    

    }

ファークリッププレーンを除外しないと、エフェクトが残ります。

法線エッジ抽出

ノーマルデータを使って法線エッジ抽出をしてみました。

    float4 FullScreenPass(Varyings varyings) : SV_Target
    {
        UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(varyings);
        float depth = LoadCameraDepth(varyings.positionCS.xy);
        PositionInputs posInput = GetPositionInput(varyings.positionCS.xy, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, UNITY_MATRIX_V);
        float3 viewDirection = GetWorldSpaceNormalizeViewDir(posInput.positionWS);
        float4 color = float4(0.0, 0.0, 0.0, 0.0);


        // Load the camera color buffer at the mip 0 if we're not at the before rendering injection point
        if (_CustomPassInjectionPoint != CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING)
            color = float4(CustomPassLoadCameraColor(varyings.positionCS.xy, 0), 1);
        
        // 4点のUV座標を作成
        float2 rightTopUV = posInput.positionNDC.xy + _ScreenSize.zw * _EdgeRadius;
        float2 leftButtomUV = posInput.positionNDC.xy - _ScreenSize.zw * _EdgeRadius;
        float2 leftTopUV = posInput.positionNDC.xy + _ScreenSize.zw * float2(-1,1) * _EdgeRadius;
        float2 rightButtomUV = posInput.positionNDC.xy - _ScreenSize.zw * float2(-1, 1) * _EdgeRadius;

        // ノーマルデータを読み込み
        NormalData normalData0, normalData1, normalData2, normalData3;

        DecodeFromNormalBuffer(_ScreenSize.xy * rightTopUV, normalData0);
        DecodeFromNormalBuffer(_ScreenSize.xy * leftButtomUV, normalData1);
        DecodeFromNormalBuffer(_ScreenSize.xy * leftTopUV, normalData2);
        DecodeFromNormalBuffer(_ScreenSize.xy * rightButtomUV, normalData3);

        // 対角線の差
        float3 normalDiff0 = normalData0.normalWS - normalData1.normalWS;
        float3 normalDiff1 = normalData2.normalWS - normalData3.normalWS;

        // 長さを計算
        float edgeStrength = sqrt(dot(normalDiff0, normalDiff0) + dot(normalDiff1, normalDiff1));

        // ファークリッププレーンを除外
        float isNotFarClip = depth != UNITY_RAW_FAR_CLIP_VALUE;

        // 閾値を適用
        edgeStrength = step(_EdgeTreshold, edgeStrength)* isNotFarClip;

        // カスタムカラーバッファを読み込み
        float customColor = CustomPassLoadCustomColor(varyings.positionCS.xy);

        // 輪郭は指定の色、それ以外はカメラカラーにする
        return lerp(color,_EdgeColor, edgeStrength * customColor);
    }

深度やカメラカラーを読み込みます。

    float4 FullScreenPass(Varyings varyings) : SV_Target
    {
        UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(varyings);
        float depth = LoadCameraDepth(varyings.positionCS.xy);
        PositionInputs posInput = GetPositionInput(varyings.positionCS.xy, _ScreenSize.zw, depth, UNITY_MATRIX_I_VP, UNITY_MATRIX_V);
        float3 viewDirection = GetWorldSpaceNormalizeViewDir(posInput.positionWS);
        float4 color = float4(0.0, 0.0, 0.0, 0.0);


        // Load the camera color buffer at the mip 0 if we're not at the before rendering injection point
        if (_CustomPassInjectionPoint != CUSTOMPASSINJECTIONPOINT_BEFORE_RENDERING)
            color = float4(CustomPassLoadCameraColor(varyings.positionCS.xy, 0), 1);

一辺がエッジ半径*2の正方形の4つの角にあるピクセルの座標を計算します。

        // 4点のUV座標を作成
        float2 rightTopUV = posInput.positionNDC.xy + _ScreenSize.zw * _EdgeRadius;
        float2 leftButtomUV = posInput.positionNDC.xy - _ScreenSize.zw * _EdgeRadius;
        float2 leftTopUV = posInput.positionNDC.xy + _ScreenSize.zw * float2(-1,1) * _EdgeRadius;
        float2 rightButtomUV = posInput.positionNDC.xy - _ScreenSize.zw * float2(-1, 1) * _EdgeRadius;

ノーマルデータを読み込みます。

        // ノーマルデータを読み込み
        NormalData normalData0, normalData1, normalData2, normalData3;

        DecodeFromNormalBuffer(_ScreenSize.xy * rightTopUV, normalData0);
        DecodeFromNormalBuffer(_ScreenSize.xy * leftButtomUV, normalData1);
        DecodeFromNormalBuffer(_ScreenSize.xy * leftTopUV, normalData2);
        DecodeFromNormalBuffer(_ScreenSize.xy * rightButtomUV, normalData3);

対角線同士の法線の差のベクトルを計算します。

        // 対角線の差
        float3 normalDiff0 = normalData0.normalWS - normalData1.normalWS;
        float3 normalDiff1 = normalData2.normalWS - normalData3.normalWS;

ドット積を足して平方根にします。差が大きいと大きな値になります。

        // 長さを計算
        float edgeStrength = sqrt(dot(normalDiff0, normalDiff0) + dot(normalDiff1, normalDiff1));

step関数を使って、閾値で0と1に分けます。ファークリッププレーンは0にしています。

        // ファークリッププレーンを除外
        float isNotFarClip = depth != UNITY_RAW_FAR_CLIP_VALUE;

        // 閾値を適用
        edgeStrength = step(_EdgeTreshold, edgeStrength)* isNotFarClip;

カスタムカラーバッファに白く描画した部分だけエフェクトを適用します。lerp関数で、エッジはインスペクタで指定する色、それ以外はカメラカラーバッファの色にします。

        // カスタムカラーバッファを読み込み
        float customColor = CustomPassLoadCustomColor(varyings.positionCS.xy);

        // 輪郭は指定の色、それ以外はカメラカラーにする
        return lerp(color,_EdgeColor, edgeStrength * customColor);
    }

Draw Renderersカスタムパス

フルスクリーンカスタムパスの前にDraw Renderersカスタムパスを使って、対象のレイヤーのみ白でカスタムカラーバッファに描画しています。

            void GetSurfaceAndBuiltinData(FragInputs fragInputs, float3 viewDirection, inout PositionInputs posInput, out SurfaceData surfaceData, out BuiltinData builtinData)
            {

                // Write back the data to the output structures
                ZERO_BUILTIN_INITIALIZE(builtinData); // No call to InitBuiltinData as we don't have any lighting
                ZERO_INITIALIZE(SurfaceData, surfaceData);
                builtinData.opacity = 1;
                builtinData.emissiveColor = float3(0, 0, 0);
                surfaceData.color = float3(1,1,1);
            }

コメントを残す

メールアドレスが公開されることはありません。