テクニカルアーティストのインターン生がコライダー可視化ツールを作った話
はじめに
株式会社QualiArtsのテクニカルアーティスト室(以下TA室)でインターンシップをしていた松原です。今回はそこで開発したIDOLY PRIDE(以下アイプラ)のツールについて紹介します。
さて、アイプラにはお仕事撮影というモードがあります。 お仕事撮影では、プレイヤーがフィールド上のアイドルを好きな位置、好きな角度から撮影できます。 そのシーンには大量のコライダーがあり、開発が想定していない場所に入らないよう、コライダーの抜けをチェックする作業が必要になります。
しかし、2023年2月現在、Unityのゲームビュー上にコライダーを表示する方法はありません。 シーンビューではギズモで表示できますが、表示するには対象のゲームオブ ジェクトを選択する必要があり、効率的に確認できるとは言えません。 そこで、シーンビュー、ゲームビュー、実機のすべてにおいて簡単にコライダーを可視化する機能を提供し、コライダーの抜けをチェックする作業を効率化しようということになりました。
本記事では、コライダー可視化機能をどのように作成したかをご紹介します。
コライダー可視化機能
コライダー可視化機能を有効にすると、シーン上に存在するアクティブなコライダーがすべて描画されます。 一度シーンから検出されたコライダーは追跡されるため、キャラクターが移動するとコライダーの表示も移動します。 また、シーンビューだけでなくゲームビューや実機上でもコライダーを表示できます。
描画
描画にはRendererFeatureを用いています。 RendererFeatureはScriptableRenderPipelineによる描画をカスタムできる機能です。RendererFeatureを使うことで、描画順の変更や別のScriptableRenderPipelineへの移植が簡単に行えるようになります。Editorのメニューや実機のデバッグ画面からコライダー可視化機能を有効にすると、RendererFeature内で描画が開始されるようになっています。設定画面には描画順の他にも表示するコライダーの色やシェーダーを変えるオプションが含まれています。
頂点の計算
コライダーを表示するにあたって、ギズモのように線を使った表現より、面を使って表現することが求められました。 線で表現した場合、同じ場所にコライダーが組み合わさっていた時に、どちらが奥にあるかが把握しにくいためです。 よって、コライダーの形状をメッシュで再現する必要があります。 ただ、UnityはMeshCollider以外のコライダーの形状をメッシュで管理していませんので、それぞれのコライダーに合う頂点を計算する処理を作りました。
MeshCollider
MeshColliderの場合、割り当てられているメッシュを流用できるため頂点を計算する必要はありません。
メモリ消費を増やさないためにも、MeshCollider.sharedMeshで取得したMeshをそのまま利用します。
BoxCollider
BoxColliderの表示には正方形のメッシュを6枚使用しています。辺の長さを1に、中心は原点になるように生成すると後で変形しやすくなります。
SphereCollider
SphereColliderの場合、球面座標系から直交座標系に変換する式を用いて頂点の座標を計算しています。
こちらも後で変形するので、表面までの距離を1に、中心を原点にしておきます。
CapsuleCollider
CapsuleColliderは、2つの半球と1つの円錐を組み合わせて生成しています。変形のために3つのメッシュはそれぞれ原点に置いたままサブメッシュとして統合します。
パラメーターを適用する
コライダーのsize値やゲームオブジェクトの位置を変更されても正しい表示にするために、パラメーターにしたがって行列を計算します。計算した行列は最終 的にCommandBuffer.DrawMeshに渡され、描画に利用されます。
以下はBoxColliderの行列を生成する部分です。
protected override IReadOnlyList<Matrix4x4> GetMatrices(BoxCollider collider)
{
_matrices.Clear();
Matrix4x4 matrix = collider.transform.localToWorldMatrix
* Matrix4x4.TRS(collider.center, Quaternion.identity, collider.size);
_matrices.Add(matrix);
return _matrices;
}
TransformのlocalToWorldMatrixに、コライダーのcenter値とsize値から作成した行列を乗算しています。
Listに値を入れているのは、サブメッシュごとに使用する行列を変えたいからです。 上記のBoxColliderの例では1つの行列でパラメーターの変更を表現できました。
しかし、複雑な変形をするコライダーの場合は複数のサブメッシュ、複数の行列が必要になります。 たとえば、CapsuleColliderでは上部の半球、中間の円錐、下部の半球をそれぞれサブメッシュとして格納し、それぞれ使用する行列を変えることでHeight値やRadius値の変更の表現しています。表示されているメッシュを出力してインスペクターで確認すると、別々の色で示されているサブメッシュが確認できます。
変形して描画
計算した行列を用いて描画します。
public class ColliderMeshAndMatrix
{
public readonly Mesh ColliderMesh;
public readonly IReadOnlyList<Matrix4x4> MatrixList;
public readonly bool IsColliderActive;
}
private void DrawColliders(CommandBuffer cmd)
{
if (_colliders == null) return;
foreach (var collider in _colliders)
{
ColliderMeshAndMatrix current = GetColliderMeshAndMatrix(collider);
if (!current.IsColliderActive) continue;
for (int i = 0; i < current.Mesh.subMeshCount; i++)
{
cmd.DrawMesh(
current.Mesh,
current.MatrixList[i],
_material,
i);
}
}
}
ColliderMeshAndMatrixにはメッシュ、行列のリスト、コライダーがアクティブかどうかの情報が入っています。 生成したColliderMeshAndMatrixを使って、CommandBuffer.DrawMeshを(コライダーの数 x サブメッシュの個数)回実行し、メッシュを表示しています。
シェーダー
よりコライダーが認識しやすくなるように、シェーダーでリムライトを追加しました。
Varyings vert(Attributes input)
{
Varyings output;
output.uv = TRANSFORM_TEX(input.uv, _MainTex);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
const VertexPositionInputs vertex_inputs = GetVertexPositionInputs(input.positionOS.xyz);
output.positionCS = vertex_inputs.positionCS;
output.viewDir = normalize(_WorldSpaceCameraPos - vertex_inputs.positionWS.xyz);
return output;
}
half4 frag(Varyings input) : SV_Target
{
const half rim = 1.0 - abs(dot(input.viewDir, input.normalWS));
half4 col = lerp(_Color, _Color.aaaa, rim);
return col;
}
パッケージ化
このコライダー可視化機能は直接プロジェクトのリポジトリで管理するのではなく、独立したリポジトリを作成する形としました。 さらにUnityのPackageManagerで利用できるパッケージ化を行ったので、社内のアイプラ以外のプロジェクトでも簡単に導入できるようになりました。
おわりに
アイプラのお仕事撮影のチェック作業を効率化するコライダー可視化機能作成について紹介しました。 この機能を作成するにあたって、描画APIの選定やデザインパターンの導入など様々な問題に悩まされたものの、TA室の方々にサポートしてもらった結果、機能を完成させることができました。