Shader Graphにおいて、画面全体の描画結果を取得して加工できる「Scene Color」ノード(HLSLの _CameraOpaqueTexture に相当)は、ガラスの歪み、水の屈折、陽炎(熱気による空気の揺らぎ)、水中の気泡などを表現する上で不可欠な機能です。しかし、これを使って屈折シェーダーを作成し、マテリアルの Surface Type を Transparent(半透明) に設定した際、以下のような重大なバグに直面したことはないでしょうか。

  • 「Scene Colorを繋いでいるのに、ただの透明マテリアルになってしまい全く歪まない」
  • 「マテリアルをアタッチしたオブジェクト自身の形状や、その手前にあるはずの別の半透明オブジェクトが不自然に写り込む」
  • 「カメラを動かすと、屈折した絵がチカチカと明滅したり、一瞬だけ描画が消えたりする」

本記事では、URPのレンダリングパイプラインにおける画面キャプチャテクスチャの生成タイミングと、Scene Colorを使用した半透明マテリアルにおける「正しい描画順(Queue Offset)制御」および設定手順を分かりやすく解説します。

1. 根本原因:URPにおける『_CameraOpaqueTexture』の生成タイミング

このバグが発生する理由は、「画面キャプチャテクスチャ(_CameraOpaqueTexture)が作成されるタイミング」にあります。

Unityのユニバーサルレンダーパイプライン(URP)では、パフォーマンスを最適化するため、毎フレーム画面をキャプチャするのではなく、以下の非常に限定されたタイミングで現在の画面をテクスチャにコピーします。

  1. 不透明オブジェクト(Opaque)の描画: 不透明なオブジェクト(地面、建物、キャラクターの体など)をすべてカラーバッファに描画します。
  2. 画面のコピー(Scene Colorの生成): この瞬間、不透明描画が完了したカラーバッファの内容を _CameraOpaqueTexture にコピーします。
  3. 半透明オブジェクト(Transparent)の描画: コピー完了後、半透明のオブジェクト(パーティクル、水、ガラス、そして作成した屈折オブジェクト)を描画します。

このフローからわかる通り、「Scene Colorには不透明なオブジェクトしか描画されておらず、半透明なオブジェクトの情報は一切含まれていない」のがURPの基本仕様です。

そのため、作成した屈折マテリアルが「通常の半透明(Transparent)キュー」で描画される場合、タイミング的には _CameraOpaqueTexture のコピーが作成されたに実行されます。しかし、描画順が同一の半透明グループ内でソートされる際、もしカメラからの距離やソート順の都合で、**「自分自身が描画された後に、別の半透明オブジェクトがサンプリングを行おうとする」**、あるいはその逆が発生すると、バッファへの書き込みとサンプリングの順番が前後し、自分自身が変な形で写り込んだり、背景の不透明オブジェクトしか映らずに手前の半透明オブジェクトが丸ごと消え去る現象(ソートバグ)が発生します。

2. 解決へのステップ:3つの必須設定

この問題を解決し、美しく安定した屈折表現を実装するためには、以下の3つの設定を正しく行う必要があります。

設定①:URP Assetでの『Opaque Texture』有効化

大前提として、プロジェクトのURP Assetで画面キャプチャ機能がオンになっていなければ、Scene Colorは常に真っ黒、あるいは前フレームのゴミが残った状態になります。

  1. プロジェクトの Assets/Settings/ にある Universal Render Pipeline Asset(および各画質設定用の追加アセット)を選択します。
  2. インスペクターで General > Opaque Texture にチェックが入っていることを確認します。
  3. 同様に Depth Texture も必要に応じてオンにしてください(屈折の深度差によるフェードを実装する場合に必須となります)。

設定②:Shader Graph側の基本設定

Shader Graphを開き、Graph Settings(あるいはGraph Inspector)で以下のように設定します。

  • Material: Universal
  • Workflow Mode: Metallic または Specular
  • Surface Type: Transparent
  • Render Face: Front(屈折の場合、裏面も描画すると二重にサンプリングされて歪みが崩れるため、基本は前面のみ推奨)
  • Cast Shadows / Receive Shadows: 屈折オブジェクトの場合は通常オフにします。

設定③:Queue Offset(描画キューオフセット)の調整(最重要)

ここが自分自身の写り込みや描画順崩れを防ぐための最も重要な設定です。

半透明オブジェクト同士の描画順は、通常カメラからの距離によって自動的に決定されますが、屈折オブジェクトは「他のすべての半透明オブジェクト(通常のパーティクルや他の半透明水など)の描画が終わった一番最後」に描画されるのが理想的です。そうしないと、屈折ガラスの向こう側にある半透明のエフェクトが屈折せずにガラスの手前に浮き出て見えたり、ガラスの中に写り込まなくなってしまいます。

  1. Shader Graphの Graph Settings 内の Queue Offset の値を 0 から 100250 程度の正の値 に変更します。
  2. これにより、このシェーダーを使用したマテリアルの描画順(Render Queue)が、通常の半透明(3000)から 31003250 にシフトします。
  3. 結果として、「通常の半透明オブジェクトがすべて描き終わった画面」をバックバッファから安全にサンプリングして歪ませることができるようになります。

3. 【応用】深度を用いた「水面・ガラスの屈折カットオフ」HLSL実装

Scene Colorを単にUVスクロールなどで歪ませるだけだと、「手前にあるオブジェクトまで屈折してガラスの中に吸い込まれてしまう」という不自然な描画(キャラクターの足元が水面の手前にあるのに、水の中で歪んで見えるなど)が発生します。これを防ぐには、サンプリングするピクセルの深度(Scene Depth)と、屈折面自体の深度(Screen PositionのW値)を比較し、手前にあるピクセルは歪ませない(屈折をキャンセルする)コードが必要です。

以下は、Shader Graphの Custom Function で使用できる、屈折カットオフ適用のためのHLSL関数です。

void ApplyRefractionCutoff_float(
    float2 screenUV,
    float2 distortion,
    float3 positionWS, // オブジェクトのワールド座標
    float surfaceDepth, // 屈折面自身の深度(Screen Position (Raw) の W 成分)
    out float2 finalUV)
{
    // 1. 歪ませたUVで深度を仮サンプリング
    float2 distortedUV = screenUV + distortion;
    float rawDepth = SHADERGRAPH_SAMPLE_SCENE_DEPTH(distortedUV);
    
    // 2. サンプリング先のワールド空間におけるリニア深度を算出
    float sceneDepth = LinearEyeDepth(rawDepth, _ZBufferParams);
    
    // 3. 屈折先の深度が、屈折面(自分)よりも「手前」にあるか判定
    // sceneDepth < surfaceDepth の場合は、ガラスより手前にあるオブジェクトなので歪ませない
    if (sceneDepth < surfaceDepth)
    {
        finalUV = screenUV; // 歪みのない元のUVを使用する
    }
    else
    {
        finalUV = distortedUV; // 歪ませたUVを採用する
    }
}

このロジックをShader GraphのCustom Functionノードに組み込み、出力された finalUVScene Color ノードの UV 入力に接続することで、手前にあるキャラクターやオブジェクトの輪郭が不自然に歪むのを完全に防ぐ、プロクオリティの屈折シェーダーが実現できます。