この記事は KLab Advent Calendar 2019 22日目の記事です。
はじめに
こんにちは、クライアントエンジニアのnorm81です。
禍つヴァールハイトではクリスマスイベントが絶賛開催中です。
今回はクリスマスイベントで実装された雪シェーダーを紹介します。
紹介する動作確認済み環境は Unity 2017.4.29f1 になります。
実装されたシェーダー
・地面に雪が積もるシェーダー
・天井に雪が積もるシェーダー
・スクリーンベースの雪が降るシェーダー
norm81が主に担当したのは地面に雪が積もるシェーダーですが
それ以外も各項で紹介します。
地面に雪が積もるシェーダー
専用のマスクテスクチャを使ってマスクの白い部分から、
マテリアルに設定されたレンジ値を基に、順々に表示されています。
頂点ごとに接線と角度計算して屋根にも使っています。
関連箇所を抜き取ったシェーダー
sampler2D _HeightmapTex; // マスクテクスチャ
half _Snow; // Snow Level: レンジ域
half _SnowOffset; // Snow Offset: レンジ基準値オフセット
half _SnowSmooth; // Snow Smooth: 境界値付近のアルファ値
half4 _SnowColor; // Snow Color: 対象を上書きする色
half4 _SnowDirection; // Snow Ditection: 角度差の基準とする法線
v2f vert(appdata_t v)
{
v2f f;
// 省略
float3 worldSpaceNormal = mul(unity_ObjectToWorld, v.normal).xyz;
worldSpaceNormal = normalize(worldSpaceNormal);
// NOTE: fragで計算した方がレンジの反映精度が高いが、計算回数の省略目的とstep境界値付近のぼかしにv2fの補間を使っている。
float theta = dot(worldSpaceNormal, _SnowDirection.xyz);
float threshold = (0.5 - _Snow) * 2.0;
f.snowalpha = _SnowColor.a * (1 - step(theta, threshold));
// 省略
return f;
}
half4 frag(v2f f) : COLOR
{
half3 color = tex2D(_MainTex, f.uv);
// 省略
half3 Heightmap = tex2D(_HeightmapTex, f.uv.xy);
float snowHeight = (1.0 - Heightmap.r) + _Snow + _SnowOffset;
float snowAlpha = f.snowalpha * saturate((snowHeight - 1.0 + 0.0001) / (_SnowSmooth + 0.0001)); // 0.0001: Check division by zero.
color = lerp(color, _SnowColor.rgb, snowAlpha * step(1.0, snowHeight));
// 省略
return half4(color, 1.0);
}
天井に雪が積もるシェーダー
天井と記載しているのは、上面図のマスクテクスチャを使っているためです。
メリットとして可視範囲を上手くフォローできればテクスチャ最小1枚ですむので
メモリが節約できます。
スクリーンベースの雪が降るシェーダー
下位互換のため、GPU インスタンシングではなく
必要なパーティクル数に分割した格子メッシュを用意します。
用意したメッシュの頂点入力について下記セマンテイクスを流用する形でベイクします。
・法線:座標変換前の基点座標
・カラー:アニメーション座標
あとは、自前シェーダーでパーティクル要件として座標変換・描画以外に
ビルボード処理などを行なっています。
おわりに
雪の足跡を作るとか、キャラクター形状に雪が積もるとか
某ステルスアクションゲームのような実装みたいなことをやってみたいです。