Shader "StudioMaron/RayMachingSpotLight" { Properties { [Header(_____________________________________________________________________________________________)] [Header(Shape)] [Space(10)] _Length(" Length", Range(0, 100)) = 10 _Angle(" Angle", Range(0, 179.9)) = 30 [Header(_____________________________________________________________________________________________)] [Header(Color)] [Space(10)] [HDR]_EmissionColor(" Emission Color", Color) = (1, 1, 1, 1) _ColorIntensity(" Color Intensity", Range(0, 10)) = 1 _AirDensity(" Fall off", Range(0, 10)) = 2 [Header(_____________________________________________________________________________________________)] [Header(RayMarching)] [Space(10)] _StepNum(" Sampling Count", Range(8, 512)) = 64 _StepDist(" Sampling Step Distance", Range(0.001, 0.2)) = 0.1 [Header(_____________________________________________________________________________________________)] [Header(Noise)] [Space(10)] _NoiseStrength(" Strength", Range(0, 2)) = 1 _NoiseSpeed(" Scroll Speed", Range(0, 0.01)) = 0.002 _NoiseTex(" Noise Texture", 2D) = "white"{} [Header(_____________________________________________________________________________________________)] [Header(DepthTexture)] [Space(10)] [KeywordEnum(OFF, ON)]_Depth(" Depth (HDRP/URP not work. Need real-time shadowed directional light)", Range(0, 1)) = 0 } SubShader { Tags { "Queue"="Transparent" } LOD 100 Pass { ZWrite Off Blend One One Cull Off CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #define PI 3.14159265 // Shape float _Length; float _Angle; // Color float4 _EmissionColor; float _ColorIntensity; float _AirDensity; // RayMarching float _StepDist; float _StepNum; // Noise sampler2D _NoiseTex; float4 _NoiseTex_ST; float _NoiseStrength; float _NoiseSpeed; // Depth float _Depth; sampler2D _CameraDepthTexture; // コーン型の距離関数 float cone(float3 pos, float theta, float h) { float radius = pos.y * tan(2 * PI * theta / 360); return max(length(pos.xz) - radius, pos.y - h); } // オブジェクトのスケールを取得 float3 GetObjectScale() { // _Object2World の各列ベクトルの長さが X/Y/Z スケール float3 scale; scale.x = length(unity_ObjectToWorld._m00_m01_m02); // X軸ベクトルの長さ scale.y = length(unity_ObjectToWorld._m10_m11_m12); // Y軸ベクトルの長さ scale.z = length(unity_ObjectToWorld._m20_m21_m22); // Z軸ベクトルの長さ return scale; } // ワールド座標をローカル座標に変換 float3 WorldToLocal(float3 pos) { return mul(unity_WorldToObject, float4(pos, 1.0)).xyz * GetObjectScale(); } // 距離関数 float DistanceFunction(float3 pos) { pos = WorldToLocal(pos); float angle = _Angle * 0.5; float length = _Length; return cone(pos, angle, length); } // エネルギー関数 float EnergyFunction(float3 pos) { pos = WorldToLocal(pos); float angle = _Angle * 0.5; // 円の半径 float r = pos.y * tan(2 * 3.14159265 * angle / 360); // エネルギー密度は中心から遠いほど弱める float rp = 1 - (length(pos.xz) / r); // 空気による指数減衰 float den = exp(-_AirDensity * pos.y / _Length); // 1回のサンプリングで進む距離を考慮 float step = _StepDist; // 円の面積が大きいほどエネルギー密度が減少 float s = PI * r * r; float en = rp * rp * den * step * sqrt(1 / s); return saturate(en); } // 距離関数の法線を取得 float3 GetSDFNormal(float3 p) { float eps = 0.001; float dx = DistanceFunction(p + float3(eps,0,0)) - DistanceFunction(p - float3(eps,0,0)); float dy = DistanceFunction(p + float3(0,eps,0)) - DistanceFunction(p - float3(0,eps,0)); float dz = DistanceFunction(p + float3(0,0,eps)) - DistanceFunction(p - float3(0,0,eps)); return normalize(float3(dx, dy, dz)); } struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 vertex : SV_POSITION; // 頂点座標 float2 uv : TEXCOORD0; // UV float4 worldPos : POSITION1; // ワールド座標 float4 screenPos : POSITION2; // 深度 }; v2f vert (appdata v) { v2f o; float extend = _Length; float theta = PI * _Angle / 360; // 0 to PI/2 float phi = v.uv.x * 2 * PI; // 0 to 2PI v.vertex.x = extend * tan(theta) * cos(phi) * v.uv.y; v.vertex.y = extend * v.uv.y; v.vertex.z = extend * tan(theta) * sin(phi) * v.uv.y; float4 worldPos = mul(unity_ObjectToWorld, v.vertex); // ローカル空間 → ワールド空間 float4 viewPos = mul(UNITY_MATRIX_V, worldPos); // ワールド空間 → ビュー空間 float4 clipPos = mul(UNITY_MATRIX_P, viewPos); // ビュー空間 → クリップ空間 o.vertex = clipPos; o.worldPos = worldPos; o.uv = TRANSFORM_TEX(v.uv, _NoiseTex); o.screenPos = ComputeScreenPos(clipPos); return o; } float4 frag (v2f i) : SV_Target { // UVの取得 float2 uv = i.uv; // 頂点のワールド座標 float3 worldPos = i.worldPos.xyz; // カメラのワールド座標 float3 camPos = _WorldSpaceCameraPos; // カメラから頂点方向のベクトル float3 rayDir = normalize(worldPos - camPos); // オブジェクトのTransformの座標 float3 offsetPos = mul(unity_ObjectToWorld, float4(0,0,0,1)).xyz; // レイの現在位置 float3 rayPos = worldPos; // レイマーチングのステップ回数 int stepNum = _StepNum; // カラーの初期化 float4 color = float4(0, 0, 0, 0); // 深度取得(Built-in) float4 screenPos = i.screenPos; float rawDepth = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, screenPos); // 深度のメートル変換 0.01m~1000mなど float depth = LinearEyeDepth(rawDepth) * _Depth + 1000 * (1 - _Depth); // Depthを使ってレイの最大値を決める float3 camForward = -UNITY_MATRIX_V[2].xyz; float maxLen = depth / dot(rayDir, camForward); // 変数の初期化 float dist = 0; float energy = 0; float hit = 0; float inside = 0; float3 col = _EmissionColor.rgb; float len = length(camPos - worldPos); // 距離関数の法線を取得 float3 normal = GetSDFNormal(rayPos); float3 viewDir = normalize(camPos - worldPos); float flipFlag = dot(viewDir, normal) > 0 ? 1 : -1; rayDir *= flipFlag; for (int i = 0; i < stepNum; i++) { // Distanceを計算 dist = DistanceFunction(rayPos); // エネルギー(光量)を計算 energy = EnergyFunction(rayPos); // フラグ管理 hit += dist < 0.0001 ? 1 : 0; // hit済みフラグ inside = dist < 0.0001 ? 1 : 0; // 領域内フラグ // カラー計算 if(inside) { // エネルギー関数に応じて色を足す color.rgb += col * _ColorIntensity * energy; } else if(hit > 0) { // 領域内から領域外に出たら終了 break; } // レイを進める rayPos += max(_StepDist, dist) * rayDir; // 進んだ距離を計算 len += max(_StepDist, dist); // 深度テクスチャにぶつかったら終了 if(len > maxLen) break; } // ノイズのサンプリング float noise = tex2D(_NoiseTex, frac(uv + float2(frac(_Time.y * _NoiseSpeed),0))); // 原点に近いほどノイズを弱める noise = lerp(1, noise, sqrt(uv.y)); // ノイズを合成 color.rgb = lerp(color.rgb, color.rgb * noise, _NoiseStrength); // 視点に近過ぎる場合はフェードする(メッシュの境界をまたぐ時) color *= saturate(length(worldPos - camPos) * length(worldPos - camPos)); color.a = 1; return color; } ENDCG } } }