はじめに
こんばんは、代表の堂前です!
Unityも5.4系がリリースされてしばらく経ち、最近は5.5ベータも出てきました。
そんな折「Unity 5.4 & 5.5 新機能キャッチアップ講座」を読んでいまして、その中で自分が気になったところ(主に描画系)を深く掘り下げていくのを数回に分けて行っていこうと思います。
※検証したのはMacのUnity5.4.1系になります。
※推定事項も含まれますので、その点ご了承下さい。
Motion Vectors
今回は「Motion Vectors」について取り上げます。
主にブラー系の用途で使われるもので、Mesh Renderer等に該当する項目が含まれるようになりました。
(5.5系では項目種類が増えますが、今回は5.4系で話を進めます。)
ここにチェックを入れると、ベロシティマップ(ピクセルごとの移動量)にそのメッシュの分を書き込むようになります。
ゲームなどでたまに見られるモーションブラー等は、そのベロシティマップを元にポスト処理されるのですが、興味を持たれた方は各々調べてみてください。(詳細は割愛します。)
描画状況
今回もシンプルなシーンを用意しました。
では、内部ではどういう動作になっているかを見ていきましょう。
の前に前提として、Cameraに以下の処理を含むスクリプトを付与する必要がありそうです。
GetComponent<Camera>().depthTextureMode = DepthTextureMode.MotionVectors;
これは端的に言うと、「DepthTextureMode」に「MotionVectors」が追加されました。
デプスマップを作るのと同様、ベロシティマップを作成するのも上記のようなルールが必要になっている感じです。
その上で、FrameDebuggerでの状況を見てみましょう。
ベロシティマップはデプスマップと異なり、通常描画の後で作成されるようです。
(ブラー処理はポスト処理が前提となっているはずなので、これはこれで問題ないでしょう。)
で、このベロシティマップはRGHalfとなっています。
RGHalfをサポートしていないハードは実現が不可ということになっています。
個人的には「DepthTextureMode.MotionVectors」を指定するとデプスマップも作られてしまうのが気になっています。
ブラー処理にはデプスマップが必ずしも必須ではないのですので・・・。
と思ったんですが、このデプスマップは用途があるようなので、それは後で説明します。
シェーダの中身を見てみる
更にベロシティマップの部分を突っ込んでみます。
どんなシェーダで書かれているか調査したところ、「Hidden/Internal-MotionVectors」を利用していました。
そしてその中のPassを指定してそれぞれ描画しているようです。
ではいつも通り、ビルトインシェーダを落として該当シェーダを見てみます。
(5.4.1ので確認)
それぞれのPassの役割は以下の通りになっていました。
Pass番号 | 役割 | 備考 |
#0 | ・Renderer毎のピクセル移動量を描画。 | |
#1 | ・カメラブラー用にデプスマップから移動量を計算して描画。 | デプス書き込み無し |
#2 | デプス書き込み有り |
上記でも利用されていた#2は全画面描画で、デプスマップを利用してのカメラブラー用の移動量を書き込むものです。
#1も#2とほぼ同じですが、ベロシティマップ用のデプスに書き込むかどうかの違いになります。
もしカメラブラーしか行わない場合は#1でもいいように思えますが、今回はその確認が取れませんでした。
(その時はデプスマップが生成されないようになっていて欲しいとは思います。)
ポイントは#0になります。
ベロシティの基本的な考えになりますが、Renderer(オブジェクト)の移動量を計算するには「前フレームの情報」というのが必要になってきます。
頂点シェーダがキモになるので転載します。
MotionVectorData VertMotionVectors(MotionVertexInput v)
{
MotionVectorData o;
o.pos = UnityObjectToClipPos(v.vertex);
// this works around an issue with dynamic batching
// potentially remove in 5.4 when we use instancing
#if defined(UNITY_REVERSED_Z)
o.pos.z -= _MotionVectorDepthBias * o.pos.w;
#else
o.pos.z += _MotionVectorDepthBias * o.pos.w;
#endif
o.transferPos = mul(_NonJitteredVP, mul(unity_ObjectToWorld, v.vertex));
o.transferPosOld = mul(_PreviousVP, mul(_PreviousM, _HasLastPositionData ? float4(v.oldPos, 1) : v.vertex));
return o;
}
雰囲気で読むと良いのですが、現フレームの座標(o.transferPos)と前フレームの座標(o.transferPosOld)を計算し、利用しています。
そのために必要な情報は以下になっているようです。
// Object rendering things
float4x4 _NonJitteredVP;
float4x4 _PreviousVP;
float4x4 _PreviousM;
bool _HasLastPositionData;
float _MotionVectorDepthBias;
・
・
・
struct MotionVertexInput
{
float4 vertex : POSITION;
float3 oldPos : NORMAL;
};
特にo.transferPosOldの計算ではこれらの情報がキモになっています。
中身はおそらく↓のような感じだと思われます。
変数名 | 内容 |
_NonJitteredVP | ・現フレームのViewProjectionマトリクス? (UNITY_MATRIX_VPとは違う?) |
_PreviousVP | ・前フレームのViewProjectionマトリクス。 |
_PreviousM | ・前フレームのWorldマトリクス。 |
_HasLastPositionData | ・頂点情報に前フレーム座標が入っているか?(MotionVertexInput.oldPos) ・(恐らく)SkinnedMeshRendererの時は1、MeshRendererの時は0。 |
_MotionVectorDepthBias | (割愛) |
MotionVertexInput.oldPos | ・前フレームのスキニング後の座標。 |
これらの情報があれば前フレームの座標は作れます。
ポイントは「MotionVertexInput.oldPos」で、スキンメッシュ(SkinnedMeshRenderer)は形状が変わるので頂点毎に座標が付随されます。
リジッドメッシュ(MeshRenderer)は形状が変わらないので、前フレームのマトリクス(_PreviousM)があれば計算できる、という仕組みです。
それを判断するための「_HasLastPositionData」となっているようです。
以上がシェーダの内部挙動になります。
個人的にはベロシティやブラー以外でも前フレームの情報というのが使えると面白い表現ができるかなと思いますし、ブラー処理を自作したい時も有用と考えるので、上記変数(_PreviousMなど)が利用できるといいなと思っています。
それも調査しました。
どうもUnity内でのベロシティマップ描画処理内でしかこれらは利用できないようです。
特にMotionVertexInput.oldPosはセマンティックがNORMALなので、Unity内部で特殊な描画を行っていそうです。
例えばそれも「TEXCOORD4」あたりで実現して、タイミング関わらず「_PreviousM」などが利用できればと思いますが、それは今後に期待という形でしょうか。
【免責事項】
本サイトでの情報を利用することによる損害等に対し、株式会社ロジカルビートは一切の責任を負いません。
「【Unity】5.4&5.5新機能を深掘り(Motion Vectors編)」への1件のフィードバック
コメントは受け付けていません。