URP でビルボードシェーダ [ゲーム開発ログ 2020-11-07]
2020-11-07
(Updated : 2020-11-15)
URP シェーダにビルボード機能を組み込む
- ビルボードとは 3D ゲームの開発で昔から使われている、常に画面の正面を向くようなオブジェクトのこと
- キャラの上に出すアイコンなんかにビルボードを使いたかったので用意することにした
- GPU レベルで(シェーダで)ビルボードを描画する方法って Unity 標準では用意されてなさそうなので、自分で実装する必要がある
実装
- Unity の URP 7.3.1 のシェーダの以下のところを置き換える:
input.positionCS = TransformWorldToHClip(input.positionWS);
※ URP 7.3.1 のカスタムシェーダを手で書く際のテンプレートを以前作ったので、
Unity 組み込みの SimpleLit シェーダをいじったシェーダを書きたくなった時は参考にされたい:
Unity 組み込みの SimpleLit シェーダをいじったシェーダを書きたくなった時は参考にされたい:
- 自分の実装:
UNITY_BRANCH
if (_BillboardOn > 0)
{
float2 scale = float2(
length(float3(UNITY_MATRIX_M[0].x, UNITY_MATRIX_M[1].x, UNITY_MATRIX_M[2].x)),
length(float3(UNITY_MATRIX_M[0].y, UNITY_MATRIX_M[1].y, UNITY_MATRIX_M[2].y))
);
input.positionCS = mul(
UNITY_MATRIX_P,
mul(UNITY_MATRIX_MV, float4(0, 0, 0, 1))
+ float4(positionOS.xy, 0, 0) * float4(scale.x, scale.y, 1, 1)
);
}
else
{
input.positionCS = TransformWorldToHClip(input.positionWS);
}
説明
座標変換はオブジェクトのローカル座標に 3 つの行列をかけることで行われる。
まずの元のコードの TransformObjectToHClip()
、
これは旧来の Builtin-RP では UnityObjectToClipPos()
に相当する処理だが、実装は以下のようになっている:
// Transforms position from object space to homogenous space
float4 TransformObjectToHClip(float3 positionOS)
{
// More efficient than computing M*VP matrix product
return mul(GetWorldToHClipMatrix(), mul(GetObjectToWorldMatrix(), float4(positionOS, 1.0)));
}
これは噛み砕くと次のようなコードになる:
return mul(
UNITY_MATRIX_V, mul(
UNITY_MATRIX_P, mul(
UNITY_MATRIX_M, float4(positionOS, 1.0)
)
)
);
positionOS
は Object Space における位置、つまりオブジェクトのローカル座標。
これに M と V と P の行列をかけて座標変換しているわけだ。
M / V / P の行列の意味は以下:
定数 | 内容 | 説明 |
---|---|---|
UNITY_MATRIX_M | モデル変換の行列 | オブジェクトのローカル座標からワールド座標への変換 |
UNITY_MATRIX_V | ビュー変換の行列 | ワールド座標からカメラ基準の座標系へ変換 |
UNITY_MATRIX_P | プロジェクション変換の行列 | カメラ基準の座標からスクリーン座標に変換 |
で、ビルボード変換のコードだが
input.positionCS = mul(
UNITY_MATRIX_P,
mul(UNITY_MATRIX_MV, float4(0, 0, 0, 1))
+ float4(positionOS.xy, 0, 0)
);
本来オブジェクトの頂点座標に対して MVP 変換をかけるところを、 原点座標 (0, 0, 0) にかけることで回転を無効化している。 これだけだと原点座標のままなのでその後別途 xy 座標を足してやる。
この時点でビルボードらしい見た目にはなるのだが、これだと GameObject の Transform で指定した Scale 値が反映されない。 原点座標でモデル変換を行っている関係で、スケールの変換も無視されているからだ。
そこで、xy 座標を足すときにスケール値も考慮してやる。 Transform の Scale 値は(自分の知る限り)直接シェーダには渡っていないと思うが、 モデル行列の要素から計算できるので計算してやる。
計算した scale をかけてやって、最終的には以下のようなコードとなった:
float2 scale = float2(
length(float3(UNITY_MATRIX_M[0].x, UNITY_MATRIX_M[1].x, UNITY_MATRIX_M[2].x)),
length(float3(UNITY_MATRIX_M[0].y, UNITY_MATRIX_M[1].y, UNITY_MATRIX_M[2].y))
);
input.positionCS = mul(
UNITY_MATRIX_P,
mul(UNITY_MATRIX_MV, float4(0, 0, 0, 1))
+ float4(positionOS.xy, 0, 0) * float4(scale.x, scale.y, 1, 1)
);