どーも、ぐるたか@guru_takaです。
先日、レイマーチングで板ポリにライティングありの球体を描きました。
【Unity】レイマーチング超入門チュートリアル前編。板ポリに球体を描く【ライティングあり】
ここでは、後編として平面にソフトシャドウを実装するやり方を紹介します。成果物はこちら。
レイマーチング始める方の参考になれば幸いです。
GitHubにソースをあげています。
参考
gurutaka/RaymarchingTutorialGitHub
ソース
まずはソースから公開します。
Shader "Custom/RayMarchingCircles" { Properties { _Radius("Radius", Range(0.0,1.0)) = 0.3 _BlurShadow("BlurShadow", Range(0.0,50.0)) = 16.0 _Speed("Speed", Range(0.0,10.0)) = 2.0 } SubShader { Tags{ "Queue" = "Transparent" "LightMode"="ForwardBase"} LOD 100 Pass { ZWrite On Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM #pragma vertex vert #pragma fragment frag // make fog work #pragma multi_compile_fog #include "UnityCG.cginc" #include "Lighting.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 pos : POSITION1; float4 vertex : SV_POSITION; }; v2f vert(appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.pos = mul(unity_ObjectToWorld, v.vertex); o.uv = v.uv; return o; } float _Radius,_BlurShadow,_Speed; float sphere(float3 pos) { return length(pos) - _Radius; } // 平面の距離関数 //https://qiita.com/muripo_life/items/074f69a5f0bac74e71e6 float plane(float3 pos) { float4 n = float4(0.0, 0.8, 0.0, 1); return dot(pos, n.xyz) + n.w; } float getDist(float3 pos){ return min(plane(pos), sphere(pos)); } //https://qiita.com/edo_m18/items/3d95c2309d6ad5a6ba55 float3 getNormal(float3 pos) { float d = 0.001; return normalize(float3( getDist(pos + float3(d, 0, 0)) - getDist(pos + float3(-d, 0, 0)), getDist(pos + float3(0, d, 0)) - getDist(pos + float3(0, -d, 0)), getDist(pos + float3(0, 0, d)) - getDist(pos + float3(0, 0, -d)) )); } //ソフトシャドウの算出式 //https://wgld.org/d/glsl/g020.html float genShadow(float3 pos, float3 lightDir){ float marchingDist = 0.0; float c = 0.001; float r = 1.0; float shadowCoef = 0.5; for(float t = 0.0; t < 50.0; t++){ marchingDist = getDist(pos + lightDir * c); if(marchingDist < 0.001){ return shadowCoef; } r = min(r, marchingDist * _BlurShadow / c); c += marchingDist; } return 1.0 - shadowCoef + r * shadowCoef; } fixed4 frag(v2f i) : SV_Target { // レイの初期位置 float3 pos = i.pos.xyz; // レイの進行方向 float3 rayDir = normalize(pos.xyz - _WorldSpaceCameraPos); const int StepNum = 30; for (int i = 0; i < StepNum; i++) { float marchingDist = getDist(pos); if (marchingDist < 0.001) { //ライティング float3 lightDir = _WorldSpaceLightPos0.xyz; float3 normal = getNormal(pos); float3 lightColor = _LightColor0; //ソフトシャドウ float shadow = genShadow(pos + normal * 0.001, lightDir); fixed4 col = fixed4(lightColor * max(dot(normal, lightDir), 0) * max(0.5, shadow), 1.0); col.rgb += fixed3(0.2f, 0.2f, 0.2f); return col; } pos.xyz += marchingDist * rayDir.xyz; } return 0; } ENDCG } } }
前回、公開したチュートリアル前編との差分をピックアップして説明していきます。
ザックリした解説
平面が加わった距離関数
まずは平面の距離関数を追加したものがこちらです。
// 平面の距離関数 float plane(float3 pos) { float4 n = float4(0.0, 0.8, 0.0, 1); return dot(pos, n.xyz) + n.w; } float getDist(float3 pos){ return min(plane(pos), sphere(pos)); }
平面の距離関数については、以下のリンクを参考にしてみてください。
参考
GLSLレイマーチング研究_距離関数について勉強してみた09(Planeの関数をいじる)Qiita
ここでは球体と平面をレイマーチングにて合成していきます。
引用: シェーダだけで世界を創る!three.jsによるレイマーチング
上のスライド図のようの、両方の図形を出したいときは和集合を意味するmin
関数を使います。
イメージとしては、全ての図形の距離関数の返り値を比較し、最も近い分をマーチしていけば、その図形が外側の図形が描かれる感じです。
(説明が難しい…)
ソフトシャドウの実装
ソフトシャドウは以下の部分になります。
//ソフトシャドウの算出式 //https://wgld.org/d/glsl/g020.html float genShadow(float3 pos, float3 lightDir){ float marchingDist = 0.0; float c = 0.001; float r = 1.0; float shadowCoef = 0.5; for(float t = 0.0; t < 50.0; t++){ marchingDist = getDist(pos + lightDir * c); if(marchingDist < 0.001){ return shadowCoef; } r = min(r, marchingDist * _BlurShadow / c); c += marchingDist; } return 1.0 - shadowCoef + r * shadowCoef; } fixed4 frag(v2f i) : SV_Target { //~~~省略~~~~~~~ //ソフトシャドウ float shadow = genShadow(pos + normal * 0.001, lightDir); fixed4 col = fixed4(lightColor * max(dot(normal, lightDir), 0) * max(0.5, shadow), 1.0); col.rgb += fixed3(0.2f, 0.2f, 0.2f); return col; //~~~省略~~~~~~~ }
以下の記事を参考にしました。詳細はこちらをぜひチェックしてみて下さい。
参考
レイマーチングソフトシャドウwgld.org
画像を拝借しながら、簡単にまとめると、衝突したときに衝突地点から光源に向けてレイを飛ばし、とある範囲までにオブジェクトと衝突したら影を出すイメージです。
ソフトシャドウ算出式の引数にある、法線ベクトルnormal
に0.001を乗算している理由は画像の通りです。わかりやすい解説図がありがたすぎる…。
衝突したら最も濃い影shadowCoef
、それ以外の場合、forループ内のr = min(r, marchingDist * _BlurShadow / c);
によって、柔らかいソフトシャドウが実現されています。
最後に
以上です。これにて、Unityでのレイマーチング超入門は終わりました。
参考になれば幸いです!
コメントを残す