254 lines
10 KiB
GLSL
254 lines
10 KiB
GLSL
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
|
||
}
|
||
}
|
||
} |