Unity 6(URP 17)で作る Planar Reflection 水面
– Skybox とオブジェクトが正確に映る動的反射 –
この記事では、Unity 6(6000.2.x)+ URP17 環境で
- Skybox
- シーン内のオブジェクト
が鏡のように水面へ映り込む Planar Reflection を実装します。
波・Foam・深度などのリッチ表現は一切入れず、「反射だけを最小構成で正しく動かす」ことに集中したチュートリアルです。
ここを押さえておくと、後で高機能な水シェーダーに差し替えるのが非常に楽になります。

0. 動作環境
- Unity 6.2(6000.2.xx 系)
- Universal Render Pipeline 17.x(3D URP テンプレートプロジェクト)
- テスト OS:Windows / macOS
URP テンプレートから作った新規プロジェクトを前提にします。
この場合、すでに URP アセット・カメラ設定・DepthTexture などはよしなに設定されているので、本記事では追加の URP 設定は行いません。
1. Fantasy Skybox FREE をインポートする
水面に映す「空」が欲しいので、Asset Store から Skybox を追加します。
- Window → Package Manager
- 左上のプルダウンを My Assets
- 検索欄に Fantasy Skybox FREE と入力
- Download → Import
- すべてのファイルをインポート
インポート後、Project 内に
Assets/Fantasy Skybox FREE/
フォルダが出来ていれば OK です。
2. シーンを用意する
2-1. 新規シーン作成
- File → New Scene
- URP 用 3D シーンを選択(テンプレートに依存)
PlanarReflectionScene などの名前で保存しておきます。
2-2. Skybox を設定する
- Window → Rendering → Lighting を開く
- Environment タブ
- Skybox Material に Fantasy Skybox のマテリアルを指定(例:FS000_Day_01)
Scene ビューの空がファンタジーなスカイボックスに変われば OK です。

3. 水面(Plane)とレイヤー設定
3-1. 水面を作成
- Hierarchy → 右クリック3D Object → Plane
- 名前を WaterPlane に変更
- Transform を次のように設定
- Position:(0, 0, 0)
- Rotation:(0, 0, 0)
- Scale:(10, 1, 10) (サイズはお好みで)
3-2. Water レイヤーを確認 / 設定
目的:反射カメラで “水面自身” を描画しないようにする。
URP テンプレートでは多くの場合、Layer 4 に “Water” が既に定義されています。
- WaterPlane を選択
- Inspector → Layer
- プルダウンから Water を選択
もし Water が無ければ:
- Layer → Add Layer…
- 使っていないユーザレイヤー(User Layer)に Water と入力
- WaterPlane の Layer に Water を設定
4. メインカメラの調整(Main Camera)
- Hierarchy から Main Camera を選択
- Transform を適当に調整(例)
- Position:(0, 3, -10)
- Rotation:(10, 0, 0)
Game ビューで水面と空が良い感じに映る位置にしておきます。
注意
Main Camera の Tag は 必ず MainCamera のままにしておきます
(ここが MainCamera でないと Camera.main が機能しません)
5. PlanarReflectionCamera を作成(反射専用カメラ)
反射用に、Main Camera をコピーしてカメラを 1 台追加します。
5-1. 複製して作る
- Main Camera を選択
- Ctrl + D(Duplicate)
- 名前を PlanarReflectionCamera に変更
5-2. Tag と Audio Listener に注意
このステップが一番ハマりやすいポイントです。
1. Tag を Untagged に変更
複製すると Tag もそのままコピーされるため、PlanarReflectionCamera にも MainCamera タグが付いている場合があります。
Camera.main は「MainCamera タグの付いた最初のカメラ」を返すため、2 台とも MainCamera だと、どちらが main になるか分からずバグの元になります。
- PlanarReflectionCamera を選択
- Inspector → Tag → Untagged を選択
これで Camera.main は必ず Main Camera の方だけを指します。
2. Audio Listener を削除
複製したカメラにも Audio Listener が付いています。
Unity は「Audio Listener はシーンに 1 つだけ」を前提としており、2 つあると警告が出ます。
- PlanarReflectionCamera の Inspector でAudio Listener コンポーネントの右上「…」→ Remove Component
Main Camera の Audio Listener は残したままにします。
5-3. PlanarReflectionCamera の設定
PlanarReflectionCamera は画面には描画せず、RenderTexture にだけ描画する“隠しカメラ”として使います。
- PlanarReflectionCamera を選択
- Inspector → Camera コンポーネント
- Enabled:OFF→ Render() をスクリプトから手動で呼び出すため
- Culling Mask:Everything から→ チェックを外して Water レイヤーを OFF にする(これを忘れると水面が水面に映り込み続けて無限ループになります)
- それ以外の設定(Projection/FOV など)は Main Camera と同じで OK
6. RenderTexture を作成する
反射カメラの描画先となる RenderTexture を用意します。
- Project ウィンドウでフォルダを右クリックCreate → Render Texture
- 名前を RT_PlanarReflection にする
- Inspector でパラメータを確認(デフォルトでほぼ OK)
- Size:1024 x 1024
- Anti-aliasing:None(必要なら 2x, 4x)
- Color Format:R8G8B8A8_UNorm
- Depth Buffer:D32_SFLOAT_S8_UINT(デフォルト)
- Mip Maps:OFF でも可
- Filter:Bilinear
- Wrap:Clamp
6-1. PlanarReflectionCamera に割り当て
- PlanarReflectionCamera を選択
- Camera コンポーネントの Output セクション
- Output Texture に RT_PlanarReflection をドラッグ
これで PlanarReflectionCamera → RT_PlanarReflection への描画ルートが確立しました。
7. 水面用マテリアルと Shader Graph
ここでは、反射テクスチャをそのまま貼るだけのシンプルな水面を作ります。
(波・Foam などは入れません)
7-1. Shader Graph を作成
- Project で Assets/PlanarReflection/Water/ShaderGraph/ などのフォルダを作成
- そのフォルダ上で右クリック →Create → Shader Graph → URP → Lit Shader Graph
- 名前:Water_PlanarDebug_SG
7-2. PlanarReflectionTex プロパティを追加
- Shader Graph をダブルクリックして開く
- 左の Blackboard で + → Texture2D を追加
- 名前:PlanarReflectionTex
- Reference:_PlanarReflectionTex(自動で入る名前で OK)
- デフォルトは white のままで良い
7-3. グラフのノード構成(超シンプル版)
- グラフの空いているところで Space キー → Sample Texture 2D を作成
- そのノードの Texture スロットに、Blackboard の PlanarReflectionTex をドラッグ
- Sample Texture 2D の RGBA 出力 をFragment → Base Color に接続
- Smoothness や Metallic はお好みで固定値を入れても良いですが、まずはデフォルトのままで OK

これで「_PlanarReflectionTex をそのまま貼るだけ」の水面シェーダーができました。
7-4. マテリアルを作成して WaterPlane に適用
- Shader Graph を右クリック → Create → Material
- 名前:M_Water_PlanarDebug
- WaterPlane を選択 → Mesh Renderer の Materials にM_Water_PlanarDebug を割り当て
8. PlanarReflectionController スクリプト
最後に、PlanarReflectionCamera を制御するスクリプトを作成します。
8-1. スクリプトファイルを作成
- Assets/PlanarReflection/Water/Scripts/ フォルダを作成
- その中で右クリック → Create → C# Script
- 名前:PlanarReflectionController
- 中身を次のコードで上書き保存します。
using UnityEngine;
[RequireComponent(typeof(Renderer))]
public class PlanarReflectionController : MonoBehaviour
{
[Header("Targets")]
public Camera mainCamera;
public Camera reflectionCamera;
public RenderTexture reflectionTexture;
[Header("Water Plane")]
public Vector3 planeNormal = Vector3.up;
public float planeHeight = 0f; // 水面のY座標
Material _material;
static readonly int PlanarTexId = Shader.PropertyToID("_PlanarReflectionTex");
void Awake()
{
_material = GetComponent<Renderer>().sharedMaterial;
// Inspectorで指定されていなければ Main Camera を自動取得
if (mainCamera == null)
mainCamera = Camera.main;
}
void LateUpdate()
{
if (mainCamera == null || reflectionCamera == null || reflectionTexture == null)
return;
// 1. 水面の平面(法線と一点)を定義
var n = planeNormal.normalized;
var planePoint = new Vector3(0f, planeHeight, 0f);
// 2. メインカメラの位置と向きを反転
Vector3 camPos = mainCamera.transform.position;
Vector3 camForward = mainCamera.transform.forward;
Vector3 camUp = mainCamera.transform.up;
// 平面までの距離
float d = Vector3.Dot(n, camPos - planePoint);
Vector3 reflPos = camPos - 2f * d * n;
Vector3 reflForward = Vector3.Reflect(camForward, n);
Vector3 reflUp = Vector3.Reflect(camUp, n);
reflectionCamera.transform.position = reflPos;
reflectionCamera.transform.rotation = Quaternion.LookRotation(reflForward, reflUp);
// 3. クリッププレーンを調整(簡易版だが十分実用的)
Vector4 plane = new Vector4(n.x, n.y, n.z, -Vector3.Dot(n, planePoint));
var reflectionMat = CalculateReflectionMatrix(plane);
reflectionCamera.worldToCameraMatrix = mainCamera.worldToCameraMatrix * reflectionMat;
// 投影行列はメインカメラと揃える
reflectionCamera.projectionMatrix = mainCamera.projectionMatrix;
// 4. 描画
reflectionCamera.targetTexture = reflectionTexture;
reflectionCamera.enabled = false; // 念のためOFF
reflectionCamera.Render();
// 5. マテリアルにテクスチャを渡す
_material.SetTexture(PlanarTexId, reflectionTexture);
}
// 平面に対する反射行列(Unity標準実装と同等の簡易版)
Matrix4x4 CalculateReflectionMatrix(Vector4 plane)
{
Matrix4x4 m = Matrix4x4.identity;
m.m00 = 1f - 2f * plane[0] * plane[0];
m.m01 = -2f * plane[0] * plane[1];
m.m02 = -2f * plane[0] * plane[2];
m.m03 = -2f * plane[3] * plane[0];
m.m10 = -2f * plane[1] * plane[0];
m.m11 = 1f - 2f * plane[1] * plane[1];
m.m12 = -2f * plane[1] * plane[2];
m.m13 = -2f * plane[3] * plane[1];
m.m20 = -2f * plane[2] * plane[0];
m.m21 = -2f * plane[2] * plane[1];
m.m22 = 1f - 2f * plane[2] * plane[2];
m.m23 = -2f * plane[3] * plane[2];
m.m30 = 0f;
m.m31 = 0f;
m.m32 = 0f;
m.m33 = 1f;
return m;
}
}
8-2. WaterPlane にアタッチし、参照を設定
- Hierarchy → WaterPlane を選択
- Inspector → Add Component → PlanarReflectionController
- フィールドを次のように設定
- Main Camera:Main Camera をドラッグ(空の場合は Awake で Camera.main が自動設定されます)
- Reflection Camera:PlanarReflectionCamera
- Reflection Texture:RT_PlanarReflection
- Plane Normal:(0, 1, 0)(デフォルト)
- Plane Height:水面の Y 座標(今回なら 0)
9. オブジェクトを配置して動作確認
- Hierarchy で 3D Object → Cube や Capsule を追加
- Position を (0, 1, 0) などにして水面の上に置く
- 再生(Play)
期待する結果:
- 水面に 空(Skybox)と Cube/Capsule が上下反転で映る
- カメラを動かすと映り込みもリアルタイムで変化する
- 無限反射やチラつきは発生しない
もし水面が自分自身を映しているような挙動になった場合は:
- PlanarReflectionCamera の Culling Mask から Water レイヤーが外れているか
- WaterPlane の Layer が Water になっているか
をもう一度確認してください。
10. ここまでで得られた構成

フォルダ構成の一例:
Assets/
PlanarReflection/
Water/
Materials/
M_Water_PlanarDebug.mat
RenderTextures/
RT_PlanarReflection.renderTexture
Scripts/
PlanarReflectionController.cs
ShaderGraph/
Water_PlanarDebug_SG.shadergraph
Fantasy Skybox FREE/
Scenes/
PlanarReflectionScene.unity
シーン内オブジェクト:
- Main Camera(Tag: MainCamera)
- PlanarReflectionCamera(Tag: Untagged, Audio Listener 無し)
- Directional Light
- Global Volume(URP テンプレの既定)
- WaterPlane(Layer: Water, PlanarReflectionController attached)
- Cube / Capsule など
11. まとめと発展
このチュートリアルでは、Unity 6 / URP17 で
- Planar ReflectionCamera + RenderTexture
- 水面用 ShaderGraph
- 制御スクリプト PlanarReflectionController
を組み合わせることで、
「シンプルだが正しく動く動的水面反射」
を構築しました。
この構成の上に、
- Gerstner 波(頂点アニメーション)
- 深度グラデーション(浅瀬/深部)
- Foam(白波)
- Fresnel 強調
- ReflectionProbe の Cubemap を加えたハイブリッド反射
などを載せていくと、原神風〜リアル系まで自由に拡張できます。


ディスカッション
コメント一覧
まだ、コメントがありません