■ 2023年11月15日 [Unity][RealSense] RealSense (3) メッシュ化した点群を Geometry Instansing で描いてみる
Geometry Instansing してみる
1つの RealSense で取得した点群は整列しているので、それをもとに作った三角形メッシュも三角形が規則正しく並んだものになっています。そのため、このメッシュのインデックスを作っている CreateTriangleMeshIndex() は、頂点番号を等間隔に生成しています。このように同じ図形を多数描く場合は、1つ1つを独立したデータとして描くより、一つの図形を GPU 内で複製して描いた方が効率が良くなります。GPU のこの機能を Geometry Instancing と呼びます。
スクリプトの修正
いま描いているメッシュは、縦横に並んだ点群の隣接する 4 点が作る四角形を2つの三角形で描いています。したがって、1つの四角形を GPU 内で複製して描きます。四角形を1つしか使わないので、インデックスは必要ありません。そのためインデックスを格納する indexBuffer は削除して、代わりに複製する四角形の数を記録するメンバ変数 instances を追加します。
//[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
//public class RsPointCloudRenderer : MonoBehaviour
public class TriangleMeshRenderer : MonoBehaviour
{
public RsFrameProvider Source;
//private Mesh mesh;
private GraphicsBuffer vertexBuffer = null;
//private GraphicsBuffer indexBuffer = null;
private int instances = 0;
[SerializeField]
private Material material;
private Texture2D uvmap;
indexBuffer を削除したので、それにインデックスを格納する処理も削除します。CreateTriangleMeshIndex() も使わないので、この定義も削除しても構いません。代わりに四角形の数を instances に求めておきます。
private void ResetMesh(int width, int height)
{
(中略)
//var indices = new int[vertices.Length];
//for (int i = 0; i < vertices.Length; i++)
// indices[i] = i;
//var indices = CreateTriangleMeshIndex(width - 1, height - 1);
//if (indexBuffer != null)
// indexBuffer.Release();
//indexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Index,
// indices.Length, sizeof(int));
//indexBuffer.SetData(indices);
instances = (width - 1) * (height - 1);
//mesh.MarkDynamic();
//mesh.vertices = vertices;
indexBuffer は削除したので、破棄する必要もなくなります。
void OnDestroy()
{
if (q != null)
{
q.Dispose();
q = null;
}
//if (mesh != null)
// Destroy(null);
//if (indexBuffer != null)
// indexBuffer.Release();
if (vertexBuffer != null)
{
vertexBuffer.Release();
Destroy(null);
}
}
Graphics.DrawProceduralNow() では三角形 MeshTopology.Triangles ではなく四角形 MeshTopology.Quads を描きます。四角形1つなので、頂点の数は 4 です。それを instances 個複製して描きます。MeshTopology.Quads は、マニュアルには
Note that quad topology is emulated on many platforms, so it's more efficient to use a triangular mesh.
とか書かれていてあまり使う気がしないのですけど、これを三角形2つで表したりするとシェーダ内で頂点番号を求めるときに面倒なので、これを使います。
void OnRenderObject()
{
//if (indexBuffer != null)
if (instances > 0)
{
material.SetPass(0);
//Graphics.DrawProceduralNow(MeshTopology.Triangles, indexBuffer, indexBuffer.count);
Graphics.DrawProceduralNow(MeshTopology.Quads, 4, instances);
}
}
シェーダの修正
Graphics.DrawProceduralNow() は1つの四角形を instances 個複製して描画するので、バーテックスシェーダに渡される頂点番号 SV_VertexID は 0~3 の範囲になります。そこでバーテックスシェーダの引数にインスタンスの番号 SV_InstanceID を追加し、これと SV_VertexID を組み合わせて実際の頂点番号を求めます。四角形の最初の頂点番号は SV_InstanceID ですから、SV_VertexID と実際の頂点番号との対応は次のようになります。_UVMap_TexelSize.z は点群の横方向の点の数です。
SV_VertexID | 実際の頂点番号 |
|---|---|
| 0 | SV_InstanceID |
| 1 | SV_InstanceID + 1 |
| 2 | SV_InstanceID + _UVMap_TexelSize.z + 1 |
| 3 | SV_InstanceID + _UVMap_TexelSize.z |
//v2f vert(appdata v)
v2f vert(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceID)
{
// vertex_id は 0~3 なので instance_id と組み合わせて実際の頂点番号を求める
uint b0 = vertex_id & 1;
uint b1 = vertex_id >> 1;
vertex_id = instance_id + b1 * _UVMap_TexelSize.z + (b0 ^ b1);
v2f v;
v.vertex = float4(_Vertex[vertex_id], 1.0);