作り方
デプスバッファ、つまりカメラからの深度値を使用します。
交差するオブジェクト(今回のイメージ図でいうウサギ)のカメラからの深度値とプレーンの深度値とを比較して、その距離に応じて色をつけます。
準備
まずシーンにプレーンとオブジェクトを用意し、交差するように配置します。
ヒエラルキーからCreate> C# Scriptでスクリプトを作成し、DepthTexture
以下を記述します。
using UnityEngine;
[ExecuteInEditMode]
public class DepthTexture : MonoBehaviour
{
private Camera cam;
void Start ()
{
cam = GetComponent<Camera>();
cam.depthTextureMode = DepthTextureMode.Depth;
}
}
このスクリプトをカメラにセットします。
すると、Cameraコンポーネントの一番下のところに以下のような表示が現れます。
これによりカメラが深度テクスチャを生成します。
このとき交差対象のオブジェクトのマテリアルに適応するシェーダーは影を扱えるものでないといけません(つまりUnlitはだめ)。Unityのドキュメントによると、
デプステクスチャは、シャドウキャスターのレンダリングに使用するのと同じシェーダー内パスを使用してレンダリングされます(“ShadowCaster” pass type)。したがって拡張機能により、シェーダーがシャドウキャスティング(影付け)をサポートしていない場合(例えば、シェーダーかフォールバックのいずれにおいてもシャドウキャスターのパスがない)、そのシェーダーを使用するオブジェクトにデプステクスチャは表示されません。
参照元:
カメラの深度テクスチャ - Unity マニュアル
という理由があるからです。
シェーダーを書く
つづいてシェーダーを書いていきます。
プロジェクトウィンドウから Create>Shader>Unlit Shader でShaderをつくります。
Shader "Custom/DepthFade" { Properties { _Color("Color", Color) = (1, 1, 1, 1) _EdgeColor("EdgeColor", Color) = (1, 1, 1, 1) _DepthFactor("Depth Factor", float) = 1.0 } SubShader { Tags { "RenderType"="Transparent" "Queue" = "Transparent" } Blend SrcAlpha OneMinusSrcAlpha LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; float4 screenPos : TEXCOORD1; }; uniform sampler2D _CameraDepthTexture; fixed4 _Color; fixed4 _EdgeColor; float _DepthFactor; v2f vert (appdata v) { v2f o; o.vertex = UnityObjectToClipPos(v.vertex); o.screenPos = ComputeScreenPos(o.vertex); return o; } fixed4 frag (v2f i) : SV_Target { float4 depthSample = SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)); half depth = LinearEyeDepth(depthSample); half screenDepth = depth - i.screenPos.w; float foamLine = 1 - saturate(_DepthFactor * screenDepth); fixed4 col = lerp(_Color, _EdgeColor, foamLine); // fixed4 col = _Color + foamLine * _EdgeColor; //EdgeをEmissionにしたいばあいはこっち return col; } ENDCG } } }
シェーダーが書けたら、シェーダーの上で右クリックからCreate>Materialとするとそのシェーダーが適用されたマテリアル が作成されます。
それをプレーンに貼り付けて、インスペクタから設定します。
Color
:プレーンのベースの色です。
_EdgeColor
:エッジの色です。
Depth Facor
:エッジの色の範囲の調整
です。
シェーダーの解説
uniform sampler2D _CameraDepthTexture;
ここでデプスバッファのテクスチャを宣言しています。
o.screenPos = ComputeScreenPos(o.vertex);
ComputeScreenPos()
はUnity.cgincに定義されている関数で、xyが0〜wに変換されるらしいです。
SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos));
Z方向の膨らみを正規化することで2D平面(ディスプレイ)のどの位置に該当オブジェクトのピクセルがくるのかを計算しています。深度テクスチャをレンダリングしていると考えると良さそうです。プレーンはTransparentなので、ウサギより後にレンダリングされます。つまりこの時点ではウサギのみがレンダリングされている状態です。そのウサギのカメラ深度値をスクリーンポジションに変換しています。
half depth = LinearEyeDepth(depthSample);
LinearEyeDepth()
はZ バッファ値をリニア化するために使用します。
先ほどのレンダリングされた深度テクスチャを渡すと各ピクセルのカメラからの深度を計算します。これで、ウサギのディスプレイ深度が算出されました。
half screenDepth = depth - i.screenPos.w;
depth
がウサギの深度値なので、そこからi.screenPos.w
(平面の深度値)を引くことで、プレーンとウサギとの距離がでます。距離がわかれば、交差している部分(つまり値が0の部分)もわかりますよね。
float foamLine = 1 - saturate(_DepthFactor * screenDepth);
上記の流れを経て算出されたscreenDepth
を_DepthFactor
を掛けて調整しつつ、それを0から1になるようにクランプします。そしてこのままだと値がオブジェクトの交差部分に近くにつれて0になっていくのですが、扱いづらいのでオブジェクトと交差している部分を1、離れるほど値が0に近づくようにしたいので、1から引いています。
そして最後に出した値を色に反映させています。
fixed4 col = lerp(_Color, _EdgeColor, foamLine);
// fixed4 col = _Color + foamLine * _EdgeColor; //EdgeをEmissionにしたいばあいはこっち
lerp
で線形補完させれば以下のように滑らかにエッジの色をつけることができます。
ベースの色である_Color
に加算した場合はEmissionとしてエッジの色をくっきりつけることができます。
どっちもかっこいいですね!
以上です!