URPで窓から差し込む光の筋を描画

2023/06/09 : 初稿
Unity : 2021.3.15f1
マシン : M1Mac(Metal)

# やりたいこと

窓から差し込む光。レイマーチング？
ポストエフェクトで。

例えば下記のように右に隙間がある室内のシーン。
隙間から光が差し込んでいる時に
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072360/f9b1f089-1537-85ea-7d61-638789693303.png)

こんな感じで光の筋を描画したい。荒いのは置いといて。
![スクリーンショット 2023-06-09 15.37.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3072360/e847cf3b-9658-d47b-0a90-1eb6e316236d.png)

今回はとにかく動かすことを目標に。
マルチプラットフォームとか処理負荷、実用性は置いときます。

# 実現方法

手順は以下の通り。
1. 窓の外にある光源から平行投影でDepthをテクスチャにレンダリング。
2. メインカメラでポストエフェクト。

ポストエフェクトでの処理はこんな感じ。
1. メインカメラからみた最も奥にあるピクセル（ZがDepthになる)をワールド座標に変換
2. そこからカメラまでの線分上にある点について、光源から見えているか（光源との間に遮蔽物がないかどうか）をみて、見えているならそこに光の筋があるということでそのピクセルを光らせる。

当然、メインカメラから各ピクセルまでの線分上の全ての点について
光源から見えているかどうかを調べることはできないので、
一定距離間隔などでサンプリングすることになるけど、
このサンプリング数を大きくとらないと綺麗な結果にはならない。
もちろん大きくすればそれだけ重い。

光源から見えているかどうかの判定は、最初に光源からレンダリングしたテクスチャのDepthを使う。

まずは準備として、コールバックするだけの汎用的なRendererFeatureを用意しました。
こいつをメインカメラに使うRenderer(UniversalRendererData)に設定します。
（別にこれ使わなくても実際の処理をRendererFeatureに書けばいいだけですが好みで）

```CallbackRendererFeature.cs
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using System.Collections.Generic;

namespace Utils
{
    public class CallbackRendererFeature : ScriptableRendererFeature
    {
        // コールバック
        public interface ICallback
        {
            void Callback(ScriptableRenderContext context, SharedData data, RenderPassEvent ev);
        }
        public static List<ICallback> Callbacks { get; private set; } = new List<ICallback>();

        // pass
        RendererPass BeginRenderPass;
        RendererPass EndRenderPass;

        // Create
        public override void Create()
        {
            Setup();
        }

        // OnDestroy
        void OnDestroy()
        {
            Cleanup();
        }

        // Setup
        void Setup()
        {
            if (Shared == null) {
                Shared = new SharedData();
                Shared.Setup();
            }
            if (BeginRenderPass == null) {
                BeginRenderPass = new RendererPass();
                BeginRenderPass.Setup(Shared, RenderPassEvent.BeforeRendering);
            }
            if (EndRenderPass == null) {
                EndRenderPass = new RendererPass();
                EndRenderPass.Setup(Shared, RenderPassEvent.AfterRendering);
            }
        }

        // Cleanup
        void Cleanup()
        {
            if (BeginRenderPass != null) {
                BeginRenderPass.Cleanup();
                BeginRenderPass = null;
            }
            if (EndRenderPass != null) {
                EndRenderPass.Cleanup();
                EndRenderPass = null;
            }
            if (Shared != null) {
                Shared.Cleanup();
                Shared = null;
            }
        }

        // OnEnable
        void OnEnable()
        {
            Setup();
        }

        // OnDisable
        void OnDisable()
        {
            Cleanup();
        }

        // Dispose
        protected override void Dispose(bool disposing)
        {
            // Cleanup
            Cleanup();

            // base
            base.Dispose(disposing);
        }

        // AddRenderPasses : 毎フレーム呼ばれる
        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            // プレイ中のみ
            if (!Application.isPlaying) {
                return;
            }

            // パスを登録
            if (BeginRenderPass != null) {
                renderer.EnqueuePass(BeginRenderPass);
            }
            if (EndRenderPass != null) {
                renderer.EnqueuePass(EndRenderPass);
            }
        }

        // shared
        public class SharedData
        {
            // カメラ
            public Camera CurrentCamera;
            public bool IsSceneViewCamera;

            // オリジナルカラーバッファ
            public RenderTargetIdentifier OriginalColorIdentifier;
            public RenderTargetIdentifier OriginalDepthIdentifier;
            public RenderTextureDescriptor OriginalDesc;

            // デストラクタ
            ~SharedData()
            {
                Cleanup();
            }

            // Setup
            public void Setup()
            {
            }

            // Cleanup
            public void Cleanup()
            {
                CurrentCamera = null;
            }

            // カメラの設定
            public void SetRenderingData(ref RenderingData renderingData)
            {
                CurrentCamera = renderingData.cameraData.camera;
                IsSceneViewCamera = renderingData.cameraData.isSceneViewCamera;
                OriginalDesc = renderingData.cameraData.cameraTargetDescriptor;
                OriginalColorIdentifier = renderingData.cameraData.renderer.cameraColorTarget;
                OriginalDepthIdentifier = renderingData.cameraData.renderer.cameraDepthTarget;
            }
        }
        SharedData Shared;

        // pass
        public class RendererPass : ScriptableRenderPass
        {
            // Setup
            bool HasSetup;

            // shared
            SharedData Shared;

            // Setup
            public void Setup(SharedData shared, RenderPassEvent ev)
            {
                // null check
                if (shared == null) {
                    return;
                }

                // store
                Shared = shared;
                renderPassEvent = ev;
                
                // Setupしました
                HasSetup = true;
            }

            // Cleanup
            public void Cleanup()
            {
                HasSetup = false;
            }

            // Execute
            public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
            {
                // check
                if (!HasSetup) {
                    return;
                }

                // shared
                Shared.SetRenderingData(ref renderingData);

                // シーンビューはおかしい？
                if (Shared.IsSceneViewCamera) {
                    return;
                }

                // コールバック呼び出し
                Callbacks.RemoveAll((c) => c == null);
                foreach (var callback in Callbacks) {
                    if (callback == null) {
                        continue;
                    }
                    callback.Callback(context, Shared, renderPassEvent);
                }
            }
        }
    }
}
```

これを使って、光源から見たDepthをテクスチャにして、メインカメラ描画後にポストエフェクトをかけます。
下記のスクリプトを、窓の外に置いた光源のGameObjectあたりにAdd。このGameObjectは室内を見るよう、位置と向きを設定しておきます。

スクリプトは、動的に光源から見たDepthをテクスチャに描画するカメラを生成し、メインカメラより先に処理させます。さらに、メインカメラ描画後に専用シェーダーで画面全体に一枚のポリゴンを描いて、その中で光の筋を加算描画します。

```RayMarchingManager.cs
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEditor;

namespace Utils
{
    public class RayMarchingManager : BaseBehaviour, CallbackRendererFeature.ICallback
    {
        // inspector
        [SerializeField] bool AutoStart;
        [SerializeField] int RenderTextureSize = 256;
        [SerializeField] Shader MainShader;
        [SerializeField] Shader AddShader;
        [SerializeField] int AddDiv = 16;
        [SerializeField] int RayRendererIndex;
        [SerializeField] float RayCameraOrthoSize = 20;
        [SerializeField] float RayCameraNear = 0.3f;
        [SerializeField] float RayCameraFar = 20;
        [SerializeField] int SamplingCount = 20;
        [SerializeField] float SamplingDisMin = 0.3f;
        [SerializeField] float SamplingDisMax = 20;
        [SerializeField] float SamplingDisOffset = 0.01f;
        [SerializeField] float AlphaScale = 0.1f;
        [SerializeField] Color Color = Color.white;

        // MainCamera
        Camera MainCamera;

        // OrthoCamera
        protected Camera OrthoCamera;
        RenderTexture RenderTexture;

        // ポストエフェクト
        protected Material MainMaterial;
        Material AddMaterial;
        Mesh Mesh;

        // propId
        protected int PropId_SamplingCount;
        protected int PropId_SamplingDisOffset;
        protected int PropId_SamplingDisMin;
        protected int PropId_SamplingDisMax;
        protected int PropId_AlphaScale;
        protected int PropId_Color;
        protected int PropId_RayMarchingDepth;
        protected int PropId_MainCameraWorldPos;
        protected int PropId_MainCameraWorldDir;
        protected int PropId_MainCameraWorldUp;
        protected int PropId_MainCameraWorldRight;
        protected int PropId_RayCameraWorldToLocal;
        protected int PropId_RayCameraParam;

        // 縮小バッファ
        int TempTexId;

        // Start
        protected virtual void Start()
        {
            if (AutoStart) {
                Setup(Camera.main);
            }
        }

        // OnDestroy
        protected virtual void OnDestroy()
        {
            // 解除
            CallbackRendererFeature.Callbacks.Remove(this);

            // RenderTexture破棄
            RenderTexture = Tools.DestroyObject(RenderTexture);
            AddMaterial = Tools.DestroyObject(AddMaterial);
            MainMaterial = Tools.DestroyObject(MainMaterial);
            Mesh = Tools.DestroyObject(Mesh);

            // 参照カット
            MainCamera = null;
        }

        // Setup
        public virtual void Setup(Camera mainCamera)
        {
            // store
            MainCamera = mainCamera;

            // propIds
            PropId_SamplingCount = Shader.PropertyToID("_SamplingCount");
            PropId_SamplingDisOffset = Shader.PropertyToID("_SamplingDisOffset");
            PropId_SamplingDisMin = Shader.PropertyToID("_SamplingDisMin");
            PropId_SamplingDisMax = Shader.PropertyToID("_SamplingDisMax");
            PropId_AlphaScale = Shader.PropertyToID("_AlphaScale");
            PropId_Color = Shader.PropertyToID("_Color");
            PropId_RayMarchingDepth = Shader.PropertyToID("_RayMarchingDepth");
            PropId_MainCameraWorldPos = Shader.PropertyToID("_MainCameraWorldPos");
            PropId_MainCameraWorldDir = Shader.PropertyToID("_MainCameraWorldDir");
            PropId_MainCameraWorldUp = Shader.PropertyToID("_MainCameraWorldUp");
            PropId_MainCameraWorldRight = Shader.PropertyToID("_MainCameraWorldRight");
            PropId_RayCameraWorldToLocal = Shader.PropertyToID("_RayCameraWorldToLocal");
            PropId_RayCameraParam = Shader.PropertyToID("_RayCameraParam");

            // 光源からのデプスを描画するレンダーテクスチャ
            RenderTexture = new RenderTexture(RenderTextureSize, RenderTextureSize, 24, RenderTextureFormat.Depth);
            RenderTexture.wrapMode = TextureWrapMode.Clamp;
            //RenderTexture.filterMode = FilterMode.Point;

            // ポストエフェクト用メッシュとマテリアル
            MainMaterial = new Material(MainShader);
            ApplyInspectorParam();
            AddMaterial = new Material(AddShader);
            Mesh = CreateMesh();

            // 光源カメラを作成
            var go = new GameObject("RayCamera");
            go.transform.SetParent(transform);
            go.transform.SetLocalPositionAndRotation(default, default);
            go.transform.localScale = Vector3.one;
            OrthoCamera = go.AddComponent<Camera>();
            OrthoCamera.orthographic = true;
            OrthoCamera.orthographicSize = RayCameraOrthoSize;
            OrthoCamera.nearClipPlane = RayCameraNear;
            OrthoCamera.farClipPlane = RayCameraFar;
            OrthoCamera.depth = MainCamera.depth - 1;
            OrthoCamera.cullingMask = mainCamera.cullingMask;
            OrthoCamera.clearFlags = CameraClearFlags.SolidColor;
            OrthoCamera.backgroundColor = Color.clear;
            OrthoCamera.targetTexture = RenderTexture;
            OrthoCamera.depthTextureMode = DepthTextureMode.Depth;

            // camData
            var camData = go.AddComponent<UniversalAdditionalCameraData>();
            camData.SetRenderer(RayRendererIndex);
            camData.renderShadows = false;

            // 縮小バッファ
            TempTexId = Shader.PropertyToID("_TempTex");

            // callback
            CallbackRendererFeature.Callbacks.Add(this);
        }

        // SetColor
        public void SetColor(Color color)
        {
            if (MainMaterial == null) {
                return;
            }
            MainMaterial.SetColor(PropId_Color, color);
        }

        // SetAlphaScale
        public void SetAlphaScale(float value)
        {
            if (MainMaterial == null) {
                return;
            }
            MainMaterial.SetFloat(PropId_AlphaScale, value);
        }

        // ApplyInspectorParam
        void ApplyInspectorParam()
        {
            MainMaterial.SetFloat(PropId_SamplingCount, SamplingCount);
            MainMaterial.SetFloat(PropId_SamplingDisMin, Mathf.Max(SamplingDisMin, MainCamera.nearClipPlane));
            MainMaterial.SetFloat(PropId_SamplingDisMax, Mathf.Min(SamplingDisMax, MainCamera.farClipPlane));
            MainMaterial.SetFloat(PropId_SamplingDisOffset, SamplingDisOffset);
            MainMaterial.SetFloat(PropId_AlphaScale, AlphaScale);
            MainMaterial.SetColor(PropId_Color, Color);
        }

        // CreateMesh
        Mesh CreateMesh()
        {
            // mesh
            var mesh = new Mesh();
            mesh.name = "RayMarchingMesh";

            // 頂点
            Vector3[] vertices = new Vector3[]
            {
                new Vector3(0, 0, 0),
                new Vector3(1, 0, 0),
                new Vector3(0, 1, 0),
                new Vector3(1, 1, 0),
            };
            Vector2[] uvs = new Vector2[]
            {
                new Vector2(0, 0),
                new Vector2(1, 0),
                new Vector2(0, 1),
                new Vector2(1, 1),
            };
            int[] tris = new int[]
            {
                0, 2, 1,
                1, 2, 3,
            };

            // メッシュ構築
            mesh.vertices = vertices;
            mesh.uv = uvs;
            mesh.triangles = tris;
            mesh.RecalculateNormals();
            mesh.RecalculateTangents();
            mesh.RecalculateBounds();
            return mesh;
        }

        // Callback
        public virtual void Callback(ScriptableRenderContext context, CallbackRendererFeature.SharedData data, RenderPassEvent ev)
        {
            // check
            if (!isActiveAndEnabled) {
                return;
            }

            // メインカメラでのポストエフェクト
            // 各ピクセルからzがdepthバッファになるワールド座標を計算。
            // カメラからその座標までの間を何点かサンプリングして、
            // その座標が光源から見えているなら光の筋として光らせる
            if (ev == RenderPassEvent.AfterRendering && data.CurrentCamera == MainCamera) {
                // コマンドバッファを取得
                CommandBuffer cmd = CommandBufferPool.Get("RayMarchingMain");

                // set matrix
                cmd.SetViewMatrix(Matrix4x4.identity);
                cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.Ortho(0, 1, 0, 1, -100, 100));

                // 光源から見たデプスを一時テクスチャに
                MainMaterial.SetTexture(PropId_RayMarchingDepth, RenderTexture);

                // メインカメラ情報
                var mainCamTrans = MainCamera.transform;
                Vector4 mainCamWorldPos = mainCamTrans.position;
                mainCamWorldPos.w = MainCamera.nearClipPlane;
                MainMaterial.SetVector(PropId_MainCameraWorldPos, mainCamWorldPos);
                Vector4 mainCamWorldDir = mainCamTrans.forward;
                mainCamWorldDir.w = MainCamera.farClipPlane;
                MainMaterial.SetVector(PropId_MainCameraWorldDir, mainCamWorldDir);
                var fovy = MainCamera.fieldOfView;
                var tanY = Mathf.Tan(Mathf.Deg2Rad * fovy * 0.5f);
                var tanX = tanY * data.OriginalDesc.width / data.OriginalDesc.height;
                Vector3 mainCamWorldUp = mainCamTrans.up * tanY;
                Vector3 mainCamWorldRight = mainCamTrans.right * tanX;
                MainMaterial.SetVector(PropId_MainCameraWorldUp, mainCamWorldUp);
                MainMaterial.SetVector(PropId_MainCameraWorldRight, mainCamWorldRight);

                // 光源情報
                MainMaterial.SetMatrix(PropId_RayCameraWorldToLocal, OrthoCamera.worldToCameraMatrix);
                var orthoY = OrthoCamera.orthographicSize;
                var orthoX = orthoY;
                Vector4 rayCameraParam = new Vector4(OrthoCamera.nearClipPlane, OrthoCamera.farClipPlane, 1.0f / orthoX, 1.0f / orthoY);
                MainMaterial.SetVector(PropId_RayCameraParam, rayCameraParam);

                // 一時テクスチャ生成
                var desc = data.OriginalDesc;
                desc.width = desc.width / AddDiv;
                desc.height = desc.height / AddDiv;
                cmd.GetTemporaryRT(TempTexId, desc, FilterMode.Bilinear);

                // レンダリング先をそちらへ
                cmd.SetRenderTarget(TempTexId);
                cmd.ClearRenderTarget(false, true, Color.clear);

                // 画面全体の描画
                cmd.DrawMesh(Mesh, Matrix4x4.identity, MainMaterial);

                // フレームバッファに合成
                cmd.SetRenderTarget(data.OriginalColorIdentifier);
                cmd.Blit(TempTexId, data.OriginalColorIdentifier, AddMaterial);

                // restore matrix
                cmd.SetViewMatrix(data.CurrentCamera.worldToCameraMatrix);
                cmd.SetProjectionMatrix(data.CurrentCamera.projectionMatrix);

                // CommandBuffer実行
                context.ExecuteCommandBuffer(cmd);

                // 一時テクスチャ解放
                cmd.ReleaseTemporaryRT(TempTexId);

                // CommandBuffer解放
                CommandBufferPool.Release(cmd);
            }
        }

#if UNITY_EDITOR
        [CustomEditor(typeof(RayMarchingManager))]
        protected class RayMarchingManagerEditor : Editor
        {
            // OnInspectorGUI
            public override void OnInspectorGUI()
            {
                // base
                base.OnInspectorGUI();

                // apply
                if (GUILayout.Button("Apply")) {
                    var main = target as RayMarchingManager;
                    if (main != null) {
                        main.ApplyInspectorParam();
                    }
                }
            }
        }
#endif
    }
}

```
RendererIndexにはメインカメラ用ではない（CallbackRendererFeatureを適用していない）レンダラーを用意して指定します。
で、肝心のポストエフェクト用シェーダーはこんな感じ。

```RayMarhing.shader
Shader "Utils/RayMarching/Main"
{
    Properties
    {
        _SamplingCount("SamplingCount", Float) = 100
        _SamplingDisOffset("SamplingDisOffset", Float) = 0.01
        _SamplingDisMin("SamplingDisMin", Float) = 1
        _SamplingDisMax("SamplingDisMax", Float) = 20
        _AlphaScale("AlphaScale", Float) = 0.1
        _Color("Color", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        
        Pass
        {
            ZTest Always
            ZWrite Off
            Blend SrcAlpha One

            HLSLPROGRAM
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #pragma vertex vert
            #pragma fragment frag

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            TEXTURE2D(_CameraDepthTexture);
            SAMPLER(sampler_CameraDepthTexture);
            TEXTURE2D(_RayMarchingDepth);
            SAMPLER(sampler_RayMarchingDepth);

            // メインカメラ
            float4 _MainCameraWorldPos; // wはnearまでの距離
            float4 _MainCameraWorldDir; // normalized。wはfarまでの距離
            float4 _MainCameraWorldUp; // up vector * tanY
            float4 _MainCameraWorldRight; // right vector * tanX

            // 光源カメラ
            float4x4 _RayCameraWorldToLocal;
            float4 _RayCameraParam; // x:near y:far z:1/orhtoX w:1/orthoY

            // サンプリング
            float _SamplingCount;
            float _SamplingDisOffset;
            float _SamplingDisMax;
            float _SamplingDisMin;

            // 光の濃さ
            float _AlphaScale;
            float4 _Color;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex.xyz);
                o.uv = v.uv;//TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            // サンプリング位置が光源カメラから見えているか
            float3 CalcRay(float3 worldPos)
            {
                // その座標を光源カメラから見たUV
                float3 localPos = mul(_RayCameraWorldToLocal, float4(worldPos, 1)).xyz;
                float2 sampleUV = float2(localPos.x * _RayCameraParam.z, localPos.y * _RayCameraParam.w);
                sampleUV.x = sampleUV.x * 0.5 + 0.5;
                sampleUV.y = sampleUV.y * 0.5 + 0.5;
                if (sampleUV.x < 0 | sampleUV.x > 1 || sampleUV.y < 0 || sampleUV.y > 1) {
                    return float3(0, 0, 0);
                }

                // 光源カメラからのZ距離
                float sampleRayDis = -localPos.z;
                if (sampleRayDis < _RayCameraParam.x || sampleRayDis > _RayCameraParam.y) {
                    return float3(0, 0, 0);
                }

                // 光源カメラのdepth : 平行投影なのでLinear01Depthは使えないというか、使わなくてもリニア。
                // ループ内部なのでSAMPLE_DEPTH_TEXTUREを使うとwarningが出る
                float rayDepth = SAMPLE_DEPTH_TEXTURE_LOD(_RayMarchingDepth, sampler_RayMarchingDepth, sampleUV, 0);
                #if defined(UNITY_REVERSED_Z)
                rayDepth = 1 - rayDepth;
                #endif

                // 光源カメラからこの場所の方向にレイを飛ばして何かにぶつかるまでの距離
                float rayDis = (_RayCameraParam.y - _RayCameraParam.x) * rayDepth + _RayCameraParam.x;

                // サンプリング位置は光源カメラから見えているか？ 
                float visible = (sampleRayDis > rayDis - _SamplingDisOffset) ? 0 : 1;
                return float3(visible, rayDis, sampleRayDis);
            }

            float4 frag (v2f i) : SV_Target
            {
                // メインカメラのdepth : Liner01/EyeDepthはnear-farではなく0-far
                float mainZDis = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, sampler_CameraDepthTexture, i.uv), _ZBufferParams);

                // それをメインカメラからみたZ距離とするワールド座標に変換
                float3 worldPos = _MainCameraWorldPos.xyz + _MainCameraWorldDir.xyz * mainZDis;
                worldPos += _MainCameraWorldUp.xyz * mainZDis * (i.uv.y * 2 - 1);
                worldPos += _MainCameraWorldRight.xyz * mainZDis * (i.uv.x * 2 - 1);

                // メインカメラからこの場所へ向かうカメラZ方向距離を1とするベクトル
                float3 vec = worldPos - _MainCameraWorldPos.xyz;
                vec /= mainZDis;

                // メインカメラからこの場所までの間の座標をいくつかサンプリングして光のかかり度合いを決める
                float a = 0;
                float addDis = (_SamplingDisMax - _SamplingDisMin) / _SamplingCount;
                [loop]
                for (float l = 0; l < _SamplingCount + 1; l++) {
                    // サンプリングするワールド座標 : メインカメラのnearからスタートしてmaxDisを越えないところまで
                    float dis = addDis * l + _SamplingDisMin;
                    if (dis > mainZDis) {
                        break;
                    }
                    float3 sampleWorldPos = _MainCameraWorldPos.xyz + vec * dis;

                    // そこは光源カメラから見えているか・
                    float3 r = CalcRay(sampleWorldPos);

                    // 見えていれば光らせる
                    a += r.x;
                }
                a *= _AlphaScale * _Color.a;
                a = max(a, 0);
                a = min(a, 1);
                return float4(_Color.r, _Color.g, _Color.b, a);
            }
            ENDHLSL
        }
    }
}
```
```Add.shader
Shader "Utils/Add"
{
    Properties
    {
        [MainTexture] _MainTex ("MainTex", 2D) = "black" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        
        Pass
        {
            ZTest Always
            ZWrite Off
            Blend One One

            HLSLPROGRAM
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #pragma vertex vert
            #pragma fragment frag

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex.xyz);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            float4 frag (v2f i) : SV_Target
            {
                return tex2D(_MainTex, i.uv);
            }
            ENDHLSL
        }
    }
}
```

これらのシェーダーはRayMarchingManager.csのインスペクタでMainShader/AddShaderにセットしておきます。なお、Utils/Addは画面に加算合成するだけなので、ガウシアンブラーかけるなりなんなりお好きに。

とりあえずこれで冒頭のスクショのように動きました。
一応光の筋をもっと小さいバッファに描画してぼかしながら画面に合成したけど
とても実用的な速度が出るとは思えない・・・。

光源からなら描画しなくてもシャドウマップ使えるんじゃないかとか、
Windowsマシンで動かないんだけどとか多分色々あるでしょうけど、
最初の一歩としてということでご容赦を。

RendererFeature使わなくてもOnEndCameraRenderingとかのコールバックでもできるのか？


　追記。コンパイルに足りないコードがあったので念の為。
```BaseBehaviour.cs
namespace Utils
{
    public class BaseBehaviour : MonoBehaviour
    {
        // Transform
        Transform _ThisTransform;
        public Transform ThisTransform
        {
            get
            {
                if (_ThisTransform == null) {
                    _ThisTransform = transform;
                }
                return _ThisTransform;
            }
        }

        // RectTransform
        RectTransform _ThisRectTransform;
        public RectTransform ThisRectTransform
        {
            get
            {
                if (_ThisRectTransform == null) {
                    _ThisRectTransform = GetComponent<RectTransform>();
                }
                return _ThisRectTransform;
            }
        }
    }
    
    // Tools
    public class Tools
    {
        // DestroyObject
        public static T DestroyObject<T>(T obj) where T : UnityEngine.Object
        {
            if (obj == null) {
                return null;
            }
            if (Application.isPlaying) {
                UnityEngine.Object.Destroy(obj);
            } else {
                UnityEngine.Object.DestroyImmediate(obj);
            }
            return null;
        }
    }
}
```