【ShaderLab】FoVによらずに一定の太さのアウトラインを作る
前回の記事では、カメラ距離によらずに一定の太さのアウトラインを作る方法を紹介しました。
実はこのシェーダー、カメラのFoVを変えると太さが変化します。
シェーダーコード(Outline.shader)
Shader "Unlit/Clip Space Outline"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Color", Color) = (0, 0, 0, 1)
_OutlineColor ("Outline Color", Color) = (1, 1, 1, 1)
_OutlineSize ("Outline Size", Range(0,0.1)) = 0.003
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
LOD 100
CGINCLUDE
float _OutlineSize;
float4 _OutlineColor;
float4 _Color;
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 normal : TEXCOORD1;
};
ENDCG
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.normal = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float3 L = _WorldSpaceLightPos0.xyz;
return dot(i.normal, L) * _Color;
}
ENDCG
}
Pass
{
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
v2f vert(appdata v)
{
v2f o;
// MVP座標変換
o.pos = UnityObjectToClipPos(v.vertex);
// 法線の座標変換 : モデル空間 -> カメラ空間
float3 norm = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);
// 投影
float2 offset = TransformViewToProjection(norm.xy);
// クリップ空間で頂点を動かす
o.pos.xy += offset * o.pos.w * _OutlineSize;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return _OutlineColor;
}
ENDCG
}
}
}
FoVの打ち消し
結論を先に述べると、アウトラインの頂点を押し出す量を unity_CameraProjection._m11
で割ることで、
FoVによらずに常に一定の太さのアウトラインになります。
- o.pos.xy += offset * o.pos.w * _OutlineSize;
+ o.pos.xy += offset * o.pos.w * _OutlineSize / unity_CameraProjection._m11;
シェーダーコード(Outline.shader)
Shader "Unlit/Clip Space Outline"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Color", Color) = (0, 0, 0, 1)
_OutlineColor ("Outline Color", Color) = (1, 1, 1, 1)
_OutlineSize ("Outline Size", Range(0,0.1)) = 0.003
}
SubShader
{
Tags
{
"RenderType"="Opaque"
}
LOD 100
CGINCLUDE
float _OutlineSize;
float4 _OutlineColor;
float4 _Color;
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 normal : TEXCOORD1;
};
ENDCG
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.normal = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return _Color;
}
ENDCG
}
Pass
{
Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
v2f vert(appdata v)
{
v2f o;
// MVP座標変換
o.pos = UnityObjectToClipPos(v.vertex);
// 法線の座標変換 : モデル空間 -> カメラ空間
float3 norm = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);
// 投影
float2 offset = TransformViewToProjection(norm.xy);
// クリップ空間で頂点を動かす
o.pos.xy += offset * o.pos.w * _OutlineSize / unity_CameraProjection._m11;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return _OutlineColor;
}
ENDCG
}
}
}
解説
プロジェクション座標変換
カメラが映す領域は、プロジェクション座標変換によって、一辺の長さが1となる立方体の領域へと変換されます。
スクリーン座標変換
クリップ空間は、スクリーンへと投影されます。
オブジェクトの画面上の大きさ
カメラに近くにあるオブジェクトは、大きく表示されます。
カメラから離れた位置にあるオブジェクトは、大きく表示されます。
数式で表してみる
ここで、オブジェクトの大きさを数式を使って表してみたいと思います。
以下のような変数を定義します。
図にすると、以下のようになります。
カメラが映す領域の上から下までの長さは
オブジェクトが画面全体を占める割合
上記の式は、カメラ距離
画面上のオブジェクトの大きさ
d を変化させたときの S の変化
ヨコ軸を
実験 : dを変化させる
カメラから近い場合
カメラ近くでオブジェクトを動かすと、画面上で大きく変化します。
カメラから遠い場合
カメラから離れているオブジェクトを動かした場合は、画面上での変化が小さいです。
\theta を変化させたときの S の変化
ヨコ軸を
※
実験 : FoVを変化させる
FoVを増やすとオブジェクトが小さくなり、FoVを減らすとオブジェクトが大きくなることが分かります。
FoVが180°に近づくと、オブジェクトは限りなく小さくなります。
FoVによる大きさの変化を打ち消す
ここで、FoVによるオブジェクトの大きさの変化を打ち消すことを考えたいと思います。
オブジェクトの大きさ
両辺に
右辺は、FoVに依存しない値
シェーダーによる実装
unity_CameraProjection._m11
には
クリップ空間での頂点移動量をunity_CameraProjection._m11
で割る (
- o.pos.xy += offset * o.pos.w * _OutlineSize;
+ o.pos.xy += offset * o.pos.w * _OutlineSize / unity_CameraProjection._m11;
結果
FoVを変化させても、アウトラインの太さが変化しなくなります。
Discussion