【Unity】遮蔽部分が透けて見えるカスタムパス

投稿者: | 2024-02-28

オブジェクトの遮蔽部分が透けて見えるカスタムパスのサンプルエフェクトを詳しく見てみました。

参考:https://github.com/alelievr/HDRP-Custom-Passes?tab=readme-ov-file#see-through-effect

ステンシルバッファを使って、遮蔽された部分のみを別のマテリアルで描画します。

カスタムパス

Custom Pass Volumeコンポーネントでは独自のカスタムパスが追加されています。レイヤーやマテリアルが設定されています。

スクリプト

カスタムパスのスクリプトでは、対象のオブジェクトが二回に分けて描画されます。一度目はステンシルバッファに書き込みます。二度目はステンシルテストによって、遮蔽されている部分のみ描画します。

まずSetUpメソッドでシェーダーを検索してマテリアルを作成しています。

  1. if (stencilShader == null)
  2. stencilShader = Shader.Find("Hidden/Renderers/SeeThroughStencil");
  3.  
  4. stencilMaterial = CoreUtils.CreateEngineMaterial(stencilShader);

Executeメソッドでは、マテリアルにステンシルのWriteMaskを設定しています。SetUpメソッドで作成したマテリアルで描画します。

  1. stencilMaterial.SetInt("_StencilWriteMask", (int)UserStencilUsage.UserBit0);
  2.  
  3. RenderObjects(ctx.renderContext, ctx.cmd, stencilMaterial, 0, CompareFunction.LessEqual, ctx.cullingResults, ctx.hdCamera);

シェーダーでは、Stencilコマンドでステンシルバッファに関する設定が行われます。Refが -1 なので、WriteMaskに設定されたビットが書き込み操作に含まれます。

  1. Stencil
  2. {
  3. Ref -1
  4. Comp Always
  5. WriteMask [_StencilWriteMask]
  6. Pass replace
  7. }

対象オブジェクトの遮蔽されていない部分のステンシルバッファに、「User Bit 0」のビットパターンが適用され、該当するビットが更新されます。

二回目の描画のためのステンシルステートを作成します。Read Maskが「User Bit 0」になっています。リファレンス値がステンシルバッファの値と同じときにピクセルをレンダリングします。

  1. StencilState seeThroughStencil = new StencilState(
  2. enabled: true,
  3. readMask: (byte)UserStencilUsage.UserBit0,
  4. compareFunction: CompareFunction.Equal
  5. );

インスペクタにアタッチされたマテリアルで2回目のレンダリングが行われます。

  1. RenderObjects(ctx.renderContext, ctx.cmd, seeThroughMaterial, seeThroughMaterial.FindPass("ForwardOnly"), CompareFunction.GreaterEqual, ctx.cullingResults, ctx.hdCamera, seeThroughStencil);

2回ともCoreUtils.DrawRendererListメソッドでRendererListが描画されます。RendererListはレンダリング可能なゲームオブジェクトの集合を表します。

RendererListを作成するには、まずRendererListDesc変数を定義します。そして、ScriptableRenderContext.CreateRendererListメソッドの引数に渡します。

  1. var result = new UnityEngine.Rendering.RendererUtils.RendererListDesc(shaderTags, cullingResult, hdCamera.camera)
  2. {
  3. rendererConfiguration = PerObjectData.None,
  4. renderQueueRange = RenderQueueRange.all,
  5. sortingCriteria = SortingCriteria.BackToFront,
  6. excludeObjectMotionVectors = false,
  7. overrideMaterial = overrideMaterial,
  8. overrideMaterialPassIndex = passIndex,
  9. layerMask = seeThroughLayer,
  10. stateBlock = new RenderStateBlock(RenderStateMask.Depth) { depthState = new DepthState(true, depthCompare) },
  11. };

オブジェクトごとに設定するデータの種類やレンダリングするオブジェクトの範囲を設定しています。RenderStateBlockのインスタンスも作られています。RenderStateBlockの値でGPUのレンダリングステートをオーバーライドできます。

2回目のレンダリングの前にステンシルステート変数が定義されています。RendererListDescのステートブロックにこの変数の値を設定します。

  1. if (overrideStencil != null)
  2. {
  3. var block = result.stateBlock.Value;
  4. block.mask |= RenderStateMask.Stencil;
  5. block.stencilState = overrideStencil.Value;
  6. result.stateBlock = block;
  7. }

ステンシルステートをオーバーライドする場合、RenderStateBlock.maskにRenderStateMask.Stencilを含めます。

それによってステンシルのリファレンス値もオーバーライドされます。RenderStateBlock.stencilReferenceのデフォルト値は0なので、Read Maskをあわせても0になります。

比較演算はEqualに設定されています。一回目のレンダリングでステンシルバッファに書き込まれていない場合にピクセルがレンダリングされると思います。

CoreUtils.DrawRendererListメソッドの引数に、RendererListを渡して描画します。

  1. CoreUtils.DrawRendererList(renderContext, cmd, renderContext.CreateRendererList(result));

シェーダーグラフ

インスペクタでアタッチされたマテリアルにはUnlitシェーダーグラフが設定されています。遮蔽された部分のレンダリングで使われています。

「Fresnel Effect」ノードの出力は、法線と視線の作る角度が大きいほど値が大きくなります。

マテリアルで設定する色とかけられます。

ディザリングのノイズがAlphaに入力されています。アルファクリッピングが適用されて、半透明な見た目になっています。

これで遮蔽された部分が透けて見えるようになります。

参考:
https://github.com/alelievr/HDRP-Custom-Passes?tab=readme-ov-file#see-through-effect
https://docs.unity3d.com/ScriptReference/Rendering.RenderStateBlock.html
https://docs.unity3d.com/ScriptReference/Rendering.RenderStateBlock-stencilReference.html
https://docs.unity3d.com/ja/2021.3/ScriptReference/Rendering.ScriptableRenderContext.CreateRendererList.html

コメントを残す

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