はじめに
前回、前々回と Unlit シェーダを読んでみましたので今回は Lit シェーダを読んでみます。
モチベーションとしては、HDRP の理解に加え、HDRP でのシェーダは基本的にはシェーダグラフで構築するのですが、uRaymarching の HDRP サポートをしたい関係で、コードを手で書かないとならないからです。なので Unlit の記事と同じく、ビルトインの HDRP/Lit
シェーダをどのように使うかという観点からではなく、各パスの役割の外観を見る目的でコードを読んでいきます。
環境
- Unity 2019.4.9f1
- HDRP 7.5.1
バージョンは前回の記事と合わせてますが、Unity 2020.x + HDRP 10.x で調べればよかったかな、と若干後悔中です...
アウトライン
まずインスペクタを見てみましょう。
ビルトインパイプラインの Standard シェーダから比べると設定項目が増えています。また、設定に応じて GUI の表示内容も変化するようになっています。実際には隠されているものも含め膨大なパラメタが Property として定義されており、それを CustomEditor
で必要なもののみ表示されるようにしています。
では HDRP/Lit
シェーダを見てきましょう。まず外観はこんな感じです。
Shader "HDRP/Lit" { Properties { ... } HLSLINCLUDE #pragma target 4.5 // シェーダバリアント #pragma shader_feature_local _ALPHATEST_ON #pragma shader_feature_local _DEPTHOFFSET_ON #pragma shader_feature_local _DOUBLESIDED_ON ... // キーワード #define HAVE_VERTEX_MODIFICATION ... // インクルード #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/FragInputs.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/ShaderPass.cs.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/LitProperties.hlsl" ... ENDHLSL SubShader { Tags { "RenderPipeline"="HDRenderPipeline" "RenderType" = "HDLitShader" } Pass { Name "SceneSelectionPass" Tags { "LightMode" = "SceneSelectionPass" } ... } Pass { Name "GBuffer" Tags { "LightMode" = "GBuffer" } ... } Pass { Name "META" Tags { "LightMode" = "META" } ... } Pass { Name "ShadowCaster" Tags { "LightMode" = "ShadowCaster" } ... } Pass { Name "DepthOnly" Tags { "LightMode" = "DepthOnly" } ... } Pass { Name "MotionVectors" Tags { "LightMode" = "MotionVectors" } ... } Pass { Name "DistortionVectors" Tags { "LightMode" = "DistortionVectors" } ... } Pass { Name "TransparentDepthPrepass" Tags { "LightMode" = "TransparentDepthPrepass" } ... } Pass { Name "TransparentBackface" Tags { "LightMode" = "TransparentBackface" } ... } Pass { Name "Forward" Tags { "LightMode" = "Forward" } ... } Pass { Name "TransparentDepthPostpass" Tags { "LightMode" = "TransparentDepthPostpass" } ... } } // レイトレ用 SubShader { Tags { "RenderPipeline"="HDRenderPipeline" } Pass { Name "IndirectDXR" Tags { "LightMode" = "IndirectDXR" } ... } Pass { Name "ForwardDXR" Tags { "LightMode" = "ForwardDXR" } ... } Pass { Name "GBufferDXR" Tags { "LightMode" = "GBufferDXR" } ... } Pass { Name "VisibilityDXR" Tags { "LightMode" = "VisibilityDXR" } ... } Pass { Name "SubSurfaceDXR" Tags { "LightMode" = "SubSurfaceDXR" } ... } Pass { Name "PathTracingDXR" Tags { "LightMode" = "PathTracingDXR" } ... } } CustomEditor "Rendering.HighDefinition.LitGUI" }
SceneSelectionPass
など、いくつか HDRP/Unlit
と共通するところもあります。前回と同じく、レイトレ用のパスは今回はスキップします。パスを列挙すると以下のような感じです(太字が Unlit と重複していないものです)。
- SceneSelectionPass
- GBuffer
- META
- ShadowCaster
- DepthOnly
- MotionVectors
- DistortionVectors
- TransparentDepthPrepass
- TransparentBackface
- Forward
- TransparentDepthPostpass
では新しく出てきたパスを見ていきましょう。
GBuffer
HDRP アセットのインスペクタから Lit Shader Mode
を Deferred Only
にした場合は、不透明オブジェクトの描画に本パスを使って GBuffer へ情報が書き出されます。Forward Only
の場合や透過オブジェクトは Forward
パスを使って描画されます。なお、Both
を選択した場合はカメラやリフレクションプローブ単位で選択が可能なようです。
さて、本パスの実態は ShaderPassGBuffer.hlsl にて定義されています。頂点シェーダは以下のような形で他のパスと同じです(前回 / 前々回の記事を参照)。
PackedVaryingsType Vert(AttributesMesh inputMesh)
{
VaryingsType varyingsType;
varyingsType.vmesh = VertMesh(inputMesh);
return PackVaryingsType(varyingsType);
}
フラグメントシェーダも大筋は以前と同じです。
void Frag( PackedVaryingsToPS packedInput, OUTPUT_GBUFFER(outGBuffer) #ifdef _DEPTHOFFSET_ON , out float outputDepth : SV_Depth #endif ) { // XR 向けシングルパスステレオ用 UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(packedInput); // パックされた情報を FragInputs へ変換 FragInputs input = UnpackVaryingsMeshToFragInputs(packedInput.vmesh); // 位置情報を取り出す(ワールド座標やデプスなど) // input.positionSS が SV_Position PositionInputs posInput = GetPositionInput( input.positionSS.xy, _ScreenSize.zw, input.positionSS.z, input.positionSS.w, input.positionRWS); // ワールドスペースのビューの方向、中では Cemare-relative Rendering かどうか // Persepective か Orthographic かなどを場合分けして値を返してくれる #ifdef VARYINGS_NEED_POSITION_WS float3 V = GetWorldSpaceNormalizeViewDir(input.positionRWS); #else // 0 割を避けるためにセットしておく float3 V = float3(1.0, 1.0, 1.0); #endif // SurfaceData / BuiltinData に情報を詰める SurfaceData surfaceData; BuiltinData builtinData; GetSurfaceAndBuiltinData(input, V, posInput, surfaceData, builtinData); // GBuffer に情報を書き込む ENCODE_INTO_GBUFFER(surfaceData, builtinData, posInput.positionSS, outGBuffer); // ピクセル単位のディスプレースメントが指定されているときはデプスを上書き #ifdef _DEPTHOFFSET_ON outputDepth = posInput.deviceDepth; #endif }
重要なのは、ENCODE_INTO_GBUFFER()
で、このマクロの中で GBuffer への書き出しが行われます。中を見てみましょう。
... #ifdef LIGHT_LAYERS #define GBUFFERMATERIAL_LIGHT_LAYERS 1 #else #define GBUFFERMATERIAL_LIGHT_LAYERS 0 #endif #ifdef SHADOWS_SHADOWMASK #define GBUFFERMATERIAL_SHADOWMASK 1 #else #define GBUFFERMATERIAL_SHADOWMASK 0 #endif ... // 必要な GBuffer の枚数 #define GBUFFERMATERIAL_COUNT (4 + GBUFFERMATERIAL_LIGHT_LAYERS + GBUFFERMATERIAL_SHADOWMASK) // 枚数に応じて EncodeIntoGBuffer へ与える引数の数を変える ... #if GBUFFERMATERIAL_COUNT == 2 ... #elif GBUFFERMATERIAL_COUNT == 3 ... #elif GBUFFERMATERIAL_COUNT == 4 #define OUTPUT_GBUFFER(NAME) \ out GBufferType0 MERGE_NAME(NAME, 0) : SV_Target0, \ out GBufferType1 MERGE_NAME(NAME, 1) : SV_Target1, \ out GBufferType2 MERGE_NAME(NAME, 2) : SV_Target2, \ out GBufferType3 MERGE_NAME(NAME, 3) : SV_Target3 #define ENCODE_INTO_GBUFFER(SURFACE_DATA, BUILTIN_DATA, UNPOSITIONSS, NAME) \ EncodeIntoGBuffer(\ SURFACE_DATA, \ BUILTIN_DATA, \ UNPOSITIONSS, \ MERGE_NAME(NAME, 0), \ MERGE_NAME(NAME, 1), \ MERGE_NAME(NAME, 2), \ MERGE_NAME(NAME, 3)) #elif GBUFFERMATERIAL_COUNT == 5 #define OUTPUT_GBUFFER(NAME) \ out GBufferType0 MERGE_NAME(NAME, 0) : SV_Target0, \ out GBufferType1 MERGE_NAME(NAME, 1) : SV_Target1, \ out GBufferType2 MERGE_NAME(NAME, 2) : SV_Target2, \ out GBufferType3 MERGE_NAME(NAME, 3) : SV_Target3, \ out GBufferType4 MERGE_NAME(NAME, 4) : SV_Target4 #define ENCODE_INTO_GBUFFER(SURFACE_DATA, BUILTIN_DATA, UNPOSITIONSS, NAME) \ EncodeIntoGBuffer(\ SURFACE_DATA, \ BUILTIN_DATA, \ UNPOSITIONSS, \ MERGE_NAME(NAME, 0), \ MERGE_NAME(NAME, 1), \ MERGE_NAME(NAME, 2), \ MERGE_NAME(NAME, 3), \ MERGE_NAME(NAME, 4)) #elif GBUFFERMATERIAL_COUNT == 6 ... #elif GBUFFERMATERIAL_COUNT == 7 ... #elif GBUFFERMATERIAL_COUNT == 8 ... #endif
必要な GBuffer の数をキーワードから計算して EncodeIntoGBuffer()
へ流し込める実装になっています。今の実装を見ると Light Layers と Shadowmasks を使わなければ 4 枚のようです(本記事では詳しく見ません)。
docs.unity3d.com docs.unity3d.com
4 枚の使われ方としては次のようにマテリアルによって異なるようです。
- Standard
- GBuffer0 : baseColor.r, baseColor.g, baseColor.b, specularOcclusion
- GBuffer1 : normal.xy (1212), perceptualRoughness
- GBuffer2 : fresnel0.r, fresnel0.g, fresnel0.b, featureID(3) / coatMask(5)
- GBuffer3 : bakedDiffuseLighting.rgb
- Subsurface Scattering + Transmission
- GBuffer0 : baseColor.r, baseColor.g, baseColor.b, diffusionProfile(4) / subsurfaceMask(4)
- GBuffer1 : normal.xy (1212), perceptualRoughness
- GBuffer2 : specularOcclusion, thickness, diffusionProfile(4) / subsurfaceMask(4), featureID(3) / coatMask(5)
- GBuffer3 : bakedDiffuseLighting.rgb
- Anisotropic
- GBuffer0 : baseColor.r, baseColor.g, baseColor.b, specularOcclusion
- GBuffer1 : normal.xy (1212), perceptualRoughness
- GBuffer2 : anisotropy, tangent.x, tangent.y(3) / metallic(5), featureID(3) / coatMask(5)
- GBuffer3 : bakedDiffuseLighting.rgb
- Irridescence
- GBuffer0 : baseColor.r, baseColor.g, baseColor.b, specularOcclusion
- GBuffer1 : normal.xy (1212), perceptualRoughness
- GBuffer2 : IOR, thickness, unused(3bit) / metallic(5), featureID(3) / coatMask(5)
- GBuffer3 : bakedDiffuseLighting.rgb
ではこれらに書き込む EncodeIntoGBuffer()
を見てみましょう。
void EncodeIntoGBuffer( SurfaceData surfaceData , BuiltinData builtinData , uint2 positionSS , out GBufferType0 outGBuffer0 // 8:8:8:8 sRGB , out GBufferType1 outGBuffer1 // 8:8:8:8 , out GBufferType2 outGBuffer2 // 8:8:8:8 , out GBufferType3 outGBuffer3 // 8:8:8:8 #if GBUFFERMATERIAL_COUNT > 4 , out GBufferType4 outGBuffer4 #endif #if GBUFFERMATERIAL_COUNT > 5 , out GBufferType5 outGBuffer5 #endif ) { // 1 枚目に書き込む // R: baseColor.r // G: baseColor.g // B: baseColor.b // A: specularOcclusion (ただし SSS + Transmission は異なる) // Standard は RGB を、 SSS + Transmission は A を後で上書き outGBuffer0 = float4(surfaceData.baseColor, surfaceData.specularOcclusion); // 2 枚目に法線とスムースネスを書き込む // RGB: normal.xy (12-12) // A: perceptualRoughness EncodeIntoNormalBuffer( ConvertSurfaceDataToNormalData(surfaceData), positionSS, outGBuffer1); // 3 枚目用のマテリアルタイプを用意 uint materialFeatureId; // SSS または Transmission の場合 if (HasFlag( surfaceData.materialFeatures, MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING | MATERIALFEATUREFLAGS_LIT_TRANSMISSION)) { // マテリアルタイプを場合分け if (( surfaceData.materialFeatures & ( MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING | MATERIALFEATUREFLAGS_LIT_TRANSMISSION) ) == ( MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING | MATERIALFEATUREFLAGS_LIT_TRANSMISSION )) { materialFeatureId = GBUFFER_LIT_TRANSMISSION_SSS; } else if (( surfaceData.materialFeatures & MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING ) == ( MATERIALFEATUREFLAGS_LIT_SUBSURFACE_SCATTERING )) { materialFeatureId = GBUFFER_LIT_SSS; } else { materialFeatureId = GBUFFER_LIT_TRANSMISSION; } // SSS + Transmission の場合はアルファチャネルを書き換え EncodeIntoSSSBuffer(ConvertSurfaceDataToSSSData(surfaceData), positionSS, outGBuffer0); // SSS + Transmission 用に 3 枚目に情報を詰める // R: specularOcclusion // G: thickness // B: diffusionProfile(4) / subsurfaceMask(4) outGBuffer2.rgb = float3(surfaceData.specularOcclusion, surfaceData.thickness, outGBuffer0.a); } // 異方性の場合 else if (HasFlag( surfaceData.materialFeatures, MATERIALFEATUREFLAGS_LIT_ANISOTROPY)) { materialFeatureId = GBUFFER_LIT_ANISOTROPIC; float3x3 frame = GetLocalFrame(surfaceData.normalWS); float sinFrame = dot(surfaceData.tangentWS, frame[1]); float cosFrame = dot(surfaceData.tangentWS, frame[0]); uint storeSin = abs(sinFrame) < abs(cosFrame) ? 4 : 0; uint quadrant = ((sinFrame < 0) ? 1 : 0) | ((cosFrame < 0) ? 2 : 0); float sinOrCos = min(abs(sinFrame), abs(cosFrame)) * sqrt(2); // 異方性用に 3 枚目に情報を詰める // R: anisotropy // G: tangent.x // B: tangent.y(3) / metallic(5) outGBuffer2.rgb = float3( surfaceData.anisotropy * 0.5 + 0.5, sinOrCos, PackFloatInt8bit(surfaceData.metallic, storeSin | quadrant, 8)); } // Iridescence(玉虫色)の場合 else if (HasFlag( surfaceData.materialFeatures, MATERIALFEATUREFLAGS_LIT_IRIDESCENCE)) { materialFeatureId = GBUFFER_LIT_IRIDESCENCE; // Iridescence 用に 3 枚目に情報を詰める // R: IOR // G: thickness // B: unused(3bit) / metallic(5) outGBuffer2.rgb = float3( surfaceData.iridescenceMask, surfaceData.iridescenceThickness, PackFloatInt8bit(surfaceData.metallic, 0, 8)); } // Standard の場合 else { materialFeatureId = GBUFFER_LIT_STANDARD; float3 diffuseColor = surfaceData.baseColor; float3 fresnel0 = surfaceData.specularColor; if (!HasFlag( surfaceData.materialFeatures, MATERIALFEATUREFLAGS_LIT_SPECULAR_COLOR)) { // メタリックから変換 diffuseColor = ComputeDiffuseColor(surfaceData.baseColor, surfaceData.metallic); fresnel0 = ComputeFresnel0(surfaceData.baseColor, surfaceData.metallic, DEFAULT_SPECULAR_VALUE); } // 1 枚目のカラー情報を上書き outGBuffer0.rgb = diffuseColor; // Standard 用に 3 枚目に情報を詰める // RGB: Fresnel.rgb outGBuffer2.rgb = FastLinearToSRGB(fresnel0); } // クリアコートの情報 float coatMask = HasFlag( surfaceData.materialFeatures, MATERIALFEATUREFLAGS_LIT_CLEAR_COAT) ? surfaceData.coatMask : 0.0; // 3 枚目のアルファチャネルにはマテリアルフィーチャー ID とクリアコートをエンコード outGBuffer2.a = PackFloatInt8bit(coatMask, materialFeatureId, 8); #ifdef DEBUG_DISPLAY ... // デバッグ表示用処理 #endif // 4 枚目に bakeDiffuseLighting を書き込む outGBuffer3 = float4( builtinData.bakeDiffuseLighting * surfaceData.ambientOcclusion + builtinData.emissiveColor, 0.0); outGBuffer3 *= GetCurrentExposureMultiplier(); // 必要であればライトレイヤを書き込む #ifdef LIGHT_LAYERS OUT_GBUFFER_LIGHT_LAYERS = float4( 0.0, 0.0, 0.0, (builtinData.renderingLayers & 0x000000FF) / 255.0); #endif // 必要であればシャドウマスクを書き込む #ifdef SHADOWS_SHADOWMASK OUT_GBUFFER_SHADOWMASK = BUILTIN_DATA_SHADOW_MASK; #endif }
マテリアルタイプごとに異なるエンコードを行い GBuffer に情報を詰めているのが見て取れます。ここでエンコードした情報は Deferred の名の通り後でデコードされ、最終的な画へと変換されます。これで GBuffer パスの外観がわかりました。
DepthOnly
次は DepthOnly
パスを見ていきましょう。本パスは HDRP の Lit Shader Mode
が Forward Only
ではデプスプリパス時に、Deferred Only
ではデカール向けのデプスプリパスのステージとして実行されます。
Forward Only 時
Deferred Only 時
なので、Deferred では HDRP のアセットの設定から Decal の Enable のチェックを外すとこのパスは使われなくなります(デプスの計算は GBuffer
パスのみ)。デプス周りのパスは複雑で、Unlit の記事の方で見た ForwardOnly
に付随する DepthForwardOnly
パスなどもあります。シェーダコード自体は Unlit の記事でも見ました、SceneSelectionPass
や DepthForwardOnly
と同じく ShaderPassDepthOnly.hlsl
を使っていますのでコードを見るのはスキップします。
Forward
順番を少し飛ばして Forward を見てみましょう。Forward パスは Lit Shader Mode
が Deferred Only
の場合は Transparent なオブジェクトで使われ、Forward Only
ではそれに加えて Opaque なオブジェクトでも使われます。
Deferred Only & Transparent
Transparent のために使われています。
Forward Only & Opaque
こちらは Opaque / Transparent 両方で使われています。
コード
ShaderPassForward.hlsl を見てみます。
void Frag( PackedVaryingsToPS packedInput, #ifdef OUTPUT_SPLIT_LIGHTING // SSS がオン且つ半透明でないとき out float4 outColor : SV_Target0, out float4 outDiffuseLighting : SV_Target1, OUTPUT_SSSBUFFER(outSSSBuffer) #else out float4 outColor : SV_Target0 #ifdef _WRITE_TRANSPARENT_MOTION_VECTOR , out float4 outMotionVec : SV_Target1 #endif #endif #ifdef _DEPTHOFFSET_ON , out float outputDepth : SV_Depth #endif ) { #ifdef _WRITE_TRANSPARENT_MOTION_VECTOR // コンパイルエラーを避けるために初期化 outMotionVec = float4(2.0, 0.0, 0.0, 0.0); #endif UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(packedInput); FragInputs input = UnpackVaryingsMeshToFragInputs(packedInput.vmesh); // 低解像度バッファ用にスクリーンスペース座標を調整 input.positionSS.xy = _OffScreenRendering > 0 ? (input.positionSS.xy * _OffScreenDownsampleFactor) : input.positionSS.xy; // 各種座標を取得 uint2 tileIndex = uint2(input.positionSS.xy) / GetTileSize(); PositionInputs posInput = GetPositionInput( input.positionSS.xy, _ScreenSize.zw, input.positionSS.z, input.positionSS.w, input.positionRWS.xyz, tileIndex); // ワールドスペースのビュー方向 #ifdef VARYINGS_NEED_POSITION_WS float3 V = GetWorldSpaceNormalizeViewDir(input.positionRWS); #else float3 V = float3(1.0, 1.0, 1.0); #endif // SurfaceData / BuiltinData に情報を詰める SurfaceData surfaceData; BuiltinData builtinData; GetSurfaceAndBuiltinData(input, V, posInput, surfaceData, builtinData); // BSDF 用のデータをつめる BSDFData bsdfData = ConvertSurfaceDataToBSDFData(input.positionSS.xy, surfaceData); PreLightData preLightData = GetPreLightData(V, posInput, bsdfData); outColor = float4(0.0, 0.0, 0.0, 0.0); // デバッグ表示用コード #ifdef DEBUG_DISPLAY ... #endif // フィーチャーフラグをサーフェスタイプによって設定 #ifdef _SURFACE_TYPE_TRANSPARENT uint featureFlags = LIGHT_FEATURE_MASK_FLAGS_TRANSPARENT; #else uint featureFlags = LIGHT_FEATURE_MASK_FLAGS_OPAQUE; #endif // ライティングの処理を行う float3 diffuseLighting; float3 specularLighting; LightLoop(V, posInput, preLightData, bsdfData, builtinData, featureFlags, diffuseLighting, specularLighting); // 露出の乗算 diffuseLighting *= GetCurrentExposureMultiplier(); specularLighting *= GetCurrentExposureMultiplier(); // 出力するカラーを計算 // Split-Lighting の詳細は http://uniteseoul.com/2019/PDF/D1T2S4.pdf を参照 #ifdef OUTPUT_SPLIT_LIGHTING if (_EnableSubsurfaceScattering != 0 && ShouldOutputSplitLighting(bsdfData)) { outColor = float4(specularLighting, 1.0); outDiffuseLighting = float4(TagLightingForSSS(diffuseLighting), 1.0); } else { outColor = float4(diffuseLighting + specularLighting, 1.0); outDiffuseLighting = 0; } ENCODE_INTO_SSSBUFFER(surfaceData, posInput.positionSS, outSSSBuffer); #else outColor = ApplyBlendMode(diffuseLighting, specularLighting, builtinData.opacity); outColor = EvaluateAtmosphericScattering(posInput, V, outColor); #endif // モーションベクターの書き出し(インスペクタで選択) #ifdef _WRITE_TRANSPARENT_MOTION_VECTOR VaryingsPassToPS inputPass = UnpackVaryingsPassToPS(packedInput.vpass); bool forceNoMotion = any(unity_MotionVectorsParams.yw == 0.0); if (!forceNoMotion) { float2 motionVec = CalculateMotionVector(inputPass.positionCS, inputPass.previousPositionCS); EncodeMotionVector(motionVec * 0.5, outMotionVec); outMotionVec.zw = 1.0; } #endif // ピクセル単位のディスプレースメントが指定されているときはデプスを上書き #ifdef _DEPTHOFFSET_ON outputDepth = posInput.deviceDepth; #endif }
深堀りすると長くなりそうなので Forward はこのフラグメントシェーダのアウトラインまでにしておきます。。全体としては、BSDF 用にデータを集めてきてタイプに応じてライティングを行い、SSS やブレンドモードを考慮しながら出力色を決定しています。
TransparentDepthPrepass
Lit シェーダのインスペクタから Transparent Depth Prepass
をチェックすると次のように半透明用オブジェクトのデプスプリパスのステージが追加されます。
髪の毛のようなほとんど不透明なオブジェクトを適切に描画するときに便利なようです。
使わないとき
使ったとき
他のデプス関連パスと同じく ShaderPassDepthOnly.hlsl を使用しています。
TransparentBackface
Back Then Front Rendering
にチェックを入れると、Forward の直前に追加されるパスです。その名の通り裏面を最初に書いてくれるパスです。
指定なし
指定有り
シェーダとしては Forward と同じ ShaderPassForward.hlsl が使われ、Cull Front
が指定され、またキーワードも幾つか変更されています。
TransparentDepthPostpass
インスペクタから Transparent Depth Postpass
を選択すると使われるようになります。本パスは DoF のような深度を使うポストエフェクトに対して、適切なデプスを伝える役割を果たします。従来は DoF 適用時は Opaque で出力された深度を見て DoF をかけてしまうと手前にある半透明オブジェクトがボヤケてしまうのですが、半透明オブジェクト描画後かつポストエフェクトの前に本パスでデプスを上書きすることによって、より自然なポストエフェクトをかけることができます(公式ブログ中の動画がわかりやすいです)。
パスとしては、こちらも他のデプス関連パスと同じく ShaderPassDepthOnly.hlsl を使用しています。
おわりに
HDRP の Lit シェーダの外観を見てきました。細かいところの処理や更に関数を潜った先で何をしているのかといったことはまだ把握できていませんが、自作のシェーダを書き始めるには良い調査になったと思います。少なくとも調べる前に感じていたパス多すぎなるほどわからん状態は脱せたかなと考えています。。次回は調査をもとに簡単なレイマーチングシェーダを作るところをやっていきたいと思います。