初めに
DepthFadeでこんなの作れます
シェーダーをアタッチした半透明オブジェクトが、不透明オブジェクトに触れている時、その境界線に色が描画される
DepthFadeとは
元々はUE4のマテリアルノードの一つ
これから描画しようとするオブジェクトの深度と、既に描画されている深度バッファの差を算出して、値に応じて透過処理を行う
これにより半透明オブジェクトが不透明オブジェクトに対して、めり込んだり刺さっているように見える状態を緩和してくれるメリットがある
名前の由来はその名の通り、深度値を利用してフェード処理を行っている所から付けられている
因みに全く同じ手法をパーティクルに当て嵌めたものを**「ソフトパーティクル」**と言い、主に煙のパーティクルなどでめり込みを防いだり重なりを緩和する目的で使われている
DepthFadeの機能を使って交差位置に線を描画する
今回はDepthFadeの**「これから描画しようとするオブジェクトの深度と、既に描画されている深度バッファの差を算出して、値に応じて透過処理を行う」部分を利用して「透過処理」を「指定した色の描画に置き換える」**という事をする
こうすることにより、オブジェクトの交差部分に境界線を描画する事が出来る
※バリアの境界線や、空間スキャン、水際の境界線などで良く見る表現
シェーダーコード概要
UnityのUnlitシェーダーを改造して実装。以下の流れで処理を行っている
※シェーダーコードの一部を抜粋
※シェーダー内で使われている定義関数(ComputeScreenPosとか)については、ページ下部の参考サイトで詳しく説明されているのでそちらを参照
#####「頂点シェーダー」
v2f vert (appdata v)
{
v2f o;
// 3次元座標をクリップ空間座標に変換
o.vertex = UnityObjectToClipPos(v.vertex);
// テクスチャスケールとオフセットを考慮した値を格納
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// ワールド座標系のカメラ方向のベクトルを取得する
o.viewDir = normalize(WorldSpaceViewDir(v.vertex));
// ワールド空間上の法線を取得
o.normal = UnityObjectToWorldNormal(v.normal);
// クリップ空間座標を元にスクリーンスペースでの位置を求める(xyが0~wの値になる)
// プラットフォームごとのY座標上下反転問題も修正
o.screenPosition = ComputeScreenPos(o.vertex);
// ビュー空間におけるZ値(深度値)をscreenPosition.zに格納
COMPUTE_EYEDEPTH(o.screenPosition.z);
return o;
}
#####「フラグメントシェーダー(リムライトの処理は無くても問題無し)」
// リム値の取得
inline fixed CalculateRim(half3 viewDir, half3 normal, half fPow, half fMul)
{
// 視線ベクトルと法線ベクトルの内積を求める
half inverse = 1 - abs(dot(viewDir, normal));
// 減衰させるため乗算を行い0~1に丸め込んで返す
return saturate(pow(inverse, fPow) * fMul);
}
fixed4 frag (v2f i) : SV_Target
{
// メインテクスチャ読み込み
fixed4 texColor = tex2D(_MainTex, i.uv);
// リムライト計算
fixed rim = CalculateRim(i.viewDir, i.normal, _FresnelPow, _FresnelMul);
// デプスフェード計算
float depth = abs(LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPosition))) - i.screenPosition.z);
fixed depthIntersect = saturate(depth / _DepthFadeMul);
fixed4 depthIntersectColor = (1 - depthIntersect) * _DepthIntersectColor;
/*
// 以下はデプスフェード計算をもう少し分かりやすく行ったもの
// このシェーダーが実行される前のscreenPos上のカメラ深度値を取得する
float depthSample = tex2Dproj(_CameraDepthTexture, i.screenPosition).r;
// 深度値をカメラからのワールド空間における距離として取得する(線形化)
float depth = LinearEyeDepth(depthSample);
// 頂点シェーダーで求めた深度値を引いて、交差部分の値を求める(0になっていると完全に交差している)
float screenDepth = abs(depth - i.screenPosition.z);
// パラメーターに定義した値で交差値を調整して0~1の範囲に丸める
fixed depthIntersect = saturate(screenDepth / _DepthFadeMul);
// 1が交差、0が非交差という形にするため1から引いている。その結果に指定した色を反映させている
fixed4 depthIntersectColor = (1 - depthIntersect) * _DepthIntersectColor;
*/
// 出力色設定
fixed4 col = texColor * _FresnelColor * rim + depthIntersectColor;
return col;
}
サンプルプロジェクト
コード全文は以下のプロジェクトを参照してください
※Windows10 + Unity2019.4.11f1で動作確認済み。
※URPプロジェクトを使用していますが、DepthTextureの取得をONにすればStandardプロジェクトやHDRPプロジェクトでも動作すると思います
(逆に言うとDepthTextureをONにしないとURP環境下でも動きません)
https://github.com/madoramu/DepthFade_URP
実行結果(UnlitDepthFade.shader, UnlitDepthFade.mat)
ノーマルマップ、マスクテクスチャ付きUVアニメーションを追加したバージョン(Barrier.shader, Barrier.mat)
余談
DepthFadeとソフトパーティクルって手法は全く同じですがどっちが通じやすいんでしょうね・・・?
参考サイト
Depth Expressions
https://docs.unrealengine.com/en-US/Engine/Rendering/Materials/ExpressionReference/Depth/index.html
[UE4] マテリアルノードの解説 その3
http://monsho.blog63.fc2.com/blog-entry-136.html
Unityで水際シェーダー
https://makingt.hatenablog.com/entry/2018/08/31/154247
Shield shader
https://medium.com/@aarhed/shield-shader-85cdaf903db7
[Tutorial] Hexagon Barrier
https://darkcatgame.tistory.com/87
【Unity】Shaderでオブジェクトの交差位置に色をつける
https://www.wwwmaplesyrup-cs6.work/entry/2020/08/11/172850
【Unity】【シェーダ】カメラから見た深度を描画する
https://light11.hatenadiary.com/entry/2018/05/08/012149
ソフトパーティクルの仕組みを応用した表現
https://edom18.hateblo.jp/entry/2019/07/30/091403
Unity のソフトパーティクルのシェーダについて調べてみた
http://tips.hecomi.com/entry/2018/09/15/014050