【UnityShader】雪を積もらせる #37
前回の成果
レイマーチングでモーフィングを実装した。
今回やること
今回は雪を実装していきます。
- 積もる表現
- 雪が降る表現
の2つを実装します。
先に積もる表現から行っていきます。
事前準備
下記のサイトから家とタルのアセットをダウンロードして、Scene上に配置してください。
Hierarchyのキャプチャ
Sceneのキャプチャ
また、今回制作したmaterialのTextureに、家のTextureをアタッチしてください。
樽も同じように制作してください。
ソースコード
Shader "Unlit/snowPile" { Properties { _MainTex ("Texture", 2D) = "white" {} _SnowPileValue("SnowPileValue", Range(0, 3)) = 0.0 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal : NORMAL; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float3 normal : NORMAL; }; sampler2D _MainTex; float4 _MainTex_ST; half _SnowPileValue; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); o.normal = UnityObjectToWorldNormal(v.normal); return o; } fixed4 frag (v2f i) : SV_Target { float d = dot(i.normal, fixed3(0, 1, 0)); fixed4 col = tex2D(_MainTex, i.uv); fixed4 white = fixed4(1, 1, 1, 1); col = lerp(col, white, d * _SnowPileValue); return col; } ENDCG } Pass { Name "CastShadow" Tags { "LightMode" = "ShadowCaster" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_shadowcaster #include "UnityCG.cginc" struct v2f { V2F_SHADOW_CASTER; }; v2f vert( appdata_base v ) { v2f o; TRANSFER_SHADOW_CASTER(o) return o; } float4 frag( v2f i ) : COLOR { SHADOW_CASTER_FRAGMENT(i) } ENDCG } } }
1Pass目は法線を使っているのがわかると思います。
しかし、2Pass目が不明です。
雪の表現
今回制作している雪ですが、正確には積もるではなくの上の方が白くなるものです。
手法としては簡単で、
float d = dot(i.normal, fixed3(0, 1, 0));
上記のソースで、法線方向と、上ベクトルの内積でTextureの色を判断しています。
2Pass目の意味
解説に入る前に2Pass目で具体的に何をしているのかを説明します。
今使用しているUnlitShaderはライトの影響を受けないShaderなので、不自然になってしまいます。
SurfaceShader
UnlitShader
上の2枚の画像からわかるように、ライトの影響を受けないのでオブジェクトに影が落ちません。
しかし、2Pass目の処理を追加することによって影が落ちるようになります。
UnlitShaderに2Pass目の処理を追加
家には影が落ちていませんがまだましになります。
Name
Name "CastShadow"
パスに名前をつけています。
これをすることによって、他のShaderから参照できるようになります。
ShadowCaster
Tags { "LightMode" = "ShadowCaster" }
オブジェクトの深度をシャドウマップや深度テクスチャに描画することができます。
#pragma multi_compile_shadowcaster
シャドウマップに書き込む際に必要となるpragmaです。
V2F_SHADOW_CASTER
Unityの内部的にfloat4 pos : SV_POSITIONを指定しているものです。
下記のソースはV2F_SHADOW_CASTERの定義となります。
#define V2F_SHADOW_CASTER V2F_SHADOW_CASTER_NOPOS float4 pos : SV_POSITION
TRANSFER_SHADOW_CASTER(o)
オブジェクトのワールド座標をむにゃむにゃしています。
自分では理解できなかったので、わかる情報だけ載せておきます。
こちらは内部的に、
ポイントライトの場合
#define TRANSFER_SHADOW_CASTER(o) o.vec = mul( _Object2World, v.vertex ).xyz - _LightPositionRange.xyz; o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
スポットライトとディレクショナルライトの場合
#define TRANSFER_SHADOW_CASTER(o) o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.pos.z += unity_LightShadowBias.x; \ float clamped = max(o.pos.z, o.pos.w*UNITY_NEAR_CLIP_VALUE); o.pos.z = lerp(o.pos.z, clamped, unity_LightShadowBias.y);
となっているので今回は下の定義を利用しています。
unity_LightShadowBias
この関数に関してはかなり曖昧なので、あまり参考にしないでください。
ディレクショナルライトのBiasの情報を集約しているもの
Bias
赤枠で囲ってあるものがBias。
Biasが0(最小値)
Biasが2(最大値)
unity_LightShadowBias.x
min(カメラのファークリップ, 影の距離 - 影のnear planeのオフセット)
の線形関係
unity_LightShadowBias.y
ポイントライトとスポットライトの場合は0。
ディレクショナルライトの場合は1。
unity_LightShadowBias.z
ユーザーが指定している通常のオフセット量を含む、ワールド空間のテクセルサイズによってスケーリングされるもの。
normal Biasのこと。
参考サイト様
Unity-Built-in-Shaders/UnityCG.cginc at master · TwoTailsGames/Unity-Built-in-Shaders · GitHub
SHADOW_CASTER_FRAGMENT(i)
これは視点空間(view)の深度を表しているものです。
ポイントライトの場合
#define SHADOW_CASTER_FRAGMENT(i) return UnityEncodeCubeShadowDepth ((length(i.vec) + unity_LightShadowBias.x) * _LightPositionRange.w);
スポットライトとディレクショナルライトの場合
#define SHADOW_CASTER_FRAGMENT(i) UNITY_OUTPUT_DEPTH(i.hpos.zw);
UNITY_OUTPUT_DEPTH
Eye Space Depth(視点空間の深度)を返すものです。
デプステクスチャのあるプラットフォームでは、Zバッファの値が暗黙的にレンダリングされるため常に0を返します。
今回の場合は0を返しています。
参考サイト様
結果
下記の画像のように、オブジェクトの上の部分が白くなれば成功です。
画像では、SnowPileValueの値を3に設定しています。
また、このようなScriptを用意して各オブジェクトにアタッチすれば
using UnityEngine; public class PileSnow : MonoBehaviour { private float _snow; void Update() { _snow += 0.005f; var renderer = GetComponent<Renderer>(); _snow = Mathf.Clamp(_snow, 0, 3); renderer.material.SetFloat("_SnowPileValue", _snow); } }
徐々に積もっていくような表現ができます。
ソースコードにコメントを付与
Shader "Unlit/snowPile" { Properties { _MainTex ("Texture", 2D) = "white" {} _SnowPileValue("SnowPileValue", Range(0, 3)) = 0.0 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal : NORMAL; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float3 normal : NORMAL; }; sampler2D _MainTex; float4 _MainTex_ST; half _SnowPileValue; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); o.normal = UnityObjectToWorldNormal(v.normal); return o; } fixed4 frag (v2f i) : SV_Target { // オブジェクトの法線ベクトルと上方向のベクトルでの内積で積もる場所を計算 float d = dot(i.normal, fixed3(0, 1, 0)); fixed4 col = tex2D(_MainTex, i.uv); fixed4 white = fixed4(1, 1, 1, 1); col = lerp(col, white, d * _SnowPileValue); return col; } ENDCG } Pass { Name "CastShadow" // シャドウマップや深度テクスチャに描画 Tags { "LightMode" = "ShadowCaster" } CGPROGRAM #pragma vertex vert #pragma fragment frag // シャドウマップに書き込む #pragma multi_compile_shadowcaster #include "UnityCG.cginc" struct v2f { // SV_POSITION V2F_SHADOW_CASTER; }; v2f vert( appdata_base v ) { v2f o; // lightのbiasをワールド座標と計算 TRANSFER_SHADOW_CASTER(o) return o; } float4 frag( v2f i ) : COLOR { // viewの深度 SHADOW_CASTER_FRAGMENT(i) } ENDCG } } }
今回はここまでとなります。
ここまでご視聴ありがとうございました。