【Unity】カスタムパスで輪郭抽出して前面表示する

投稿者: | 2024-01-16

HDRPカスタムパスを使って、アウトラインと斜線を前面表示してみます。遮蔽部分は斜線を薄くします。

参考 : https://github.com/alelievr/HDRP-Custom-Passes#outline-effect

カスタムパス

このエフェクトには、2つのカスタムパスが使われています。

1つ目のカスタムパスで指定のレイヤーのオブジェクトのみをカスタムバッファに描画します。

はじめのカスタムパスは「Draw renderers Custom Pass」です。カメラを原点としたワールド座標の長さが大きいときはdiscardしています。

if( length(fragInputs.positionRWS) > _MaxDistance ) discard;

SurfaceDataなどの初期化後、不透明度1で、指定の一色で描画しています。

                builtinData.opacity = 1;
                builtinData.emissiveColor = float3(0, 0, 0);
                surfaceData.color = float3(1,1,1) * _SelectionColor.rgb;

色は対象のオブジェクトに付けたスクリプトでMaterialPropertyBlockにセットしています。

フルスクリーンカスタムパス

2つ目のカスタムパスでカスタムバッファを使って、カメラカラーバッファに輪郭と斜線を描画します。

フルスクリーンパスでは、まずバッファからカラーとデプスを読み込みます。PositionInputs等も取得します。

        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);
        
        float d = LoadCustomDepth(posInput.positionSS);
        float db = LoadCameraDepth(posInput.positionSS);

デプスはカメラに近いほうが値が大きくなります。

カスタムデプスとカメラデプスを比較してアルファ係数を決定します。カメラデプスが大きいと遮蔽されているので指定の値、遮蔽されていないときは1にします。

        float alphaFactor = (db>d)?_BehindFactor:1;

カスタムカラーバッファを読み込み、アルファを変数に入れています。

		float4 c = LoadCustomColor(posInput.positionSS);

        float obj = c.a;

シルエットを大きくする

まずアウトラインを描画します。描画するには、対象のオブジェクトのシルエットを少し大きくして、元のシルエット部分を隠します。

シルエットを大きくするには、周囲のピクセルの色をサンプリングして、シルエットの色がついたピクセルがあれば、中心のピクセルもその色で描画します。

周囲のピクセルでサンプリングするときのサンプル数を、サンプル密度のプロパティを基に決定します。

        uint sampleCount = min( 2 * pow(2, _SamplePrecision ), MAXSAMPLES ) ;

サンプル密度は、マテリアルのスライダーで1~3の値を指定できます。

サンプル密度が1のときサンプル数は4で、2のとき8、3のとき16です。min関数で、最大値が16に制限されています。

周囲のピクセルをサンプルするために、中心(0, 0)で半径1ピクセルの円周上にある、等間隔の16個の点の座標が定義されています。

    #define c45 0.707107
    #define c225 0.9238795
    #define s225 0.3826834
    
    #define MAXSAMPLES 16
    static float2 offsets[MAXSAMPLES] = {
        float2( 1, 0 ),
        float2( -1, 0 ),
        float2( 0, 1 ),
        float2( 0, -1 ),
        
        float2( c45, c45 ),
        float2( c45, -c45 ),
        float2( -c45, c45 ),
        float2( -c45, -c45 ),
        
        float2( c225, s225 ),
        float2( c225, -s225 ),
        float2( -c225, s225 ),
        float2( -c225, -s225 ),
        float2( s225, c225 ),
        float2( s225, -c225 ),
        float2( -s225, c225 ),
        float2( -s225, -c225 )
    };

サンプル数が4のとき、上下左右のピクセル、サンプル数が8のときはその中間、16のときはさらにその中間の点を使います。サンプル数が少ないと、円の中に元のシルエットがあっても色が描画されない確率が上がります。

点の座標は三角関数で求められます。

サンプル数の数だけカスタムカラーバッファへのアクセスを繰り返します。

        float4 outline = float4(0,0,0,0);
        
        float2 uvOffsetPerPixel = 1.0/_ScreenSize .xy;
        
        for (uint i=0 ; i<sampleCount ; ++i )
        {
            outline =  max( SampleCustomColor( posInput.positionNDC + uvOffsetPerPixel * _OutlineWidth * offsets[i] ), outline );
        }

スクリーンサイズの逆数でオフセットを正規化して、アウトラインの幅をかけたスクリーン座標の色をサンプリングします。

どれかの座標にシルエットの色があれば、max関数でその色が選択されます。これによって元のシルエットの近くのピクセルにも色がついて、シルエットが大きくなります。

カスタムカラーバッファ
アウトライン幅 : 10

斜線

テクスチャの色を取得します。

        float4 o = float4(0,0,0,0);
        
        float4 innerColor = SAMPLE_TEXTURE2D( _Texture, s_trilinear_repeat_sampler, posInput.positionSS / _TextureSize) * _InnerColor;
斜線のテクスチャ

アルファにアルファ係数をかけます。オブジェクトの遮蔽されていない部分以外は斜線が薄くなります。

アウトラインを描画

lerp関数で、大きくしたシルエットの部分は_OuterColorをかけた色、それ以外の部分は黒にしています。

        o = lerp(o, _OuterColor * float4(outline.rgb, 1), outline.a);
アウトラインの色を変更

そして、元のシルエットの部分を、カスタムカラーバッファと斜線をかけた色にします。

        o = lerp( o, innerColor * float4(c.rgb, 1), obj);

        return o;
    }
アウトライン幅 : 10
アウトライン幅 : 1.5
参考:
https://github.com/alelievr/HDRP-Custom-Passes
https://github.com/alelievr/HDRP-Custom-Passes/blob/master/LICENSE

コメントを残す

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