254 lines
10 KiB
GLSL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.01m1000mなど
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
}
}
}