MonoGameでハードウェアインスタンシングしてみる

提供: MonoBook
ナビゲーションに移動 検索に移動

1つのポリゴンメッシュGPU側で複製することでDrawコールを減らして描画を高速化する「ハードウェアインスタンシング」という技術がある。

ただMonoGameOpenGLの組み合わせの環境では長らく「new NotImplementedException()」であった。

これがついにMonoGame 3.7でOpenGL環境でもハードウェアインスタンシングが使えるようになったそうだ。さっそくMacOS上で試してみた。ハードウェアインスタンシングが使えるとなるとボリュームレンダリングの実装が捗る可能性がある。

Macでシェーダーをコンパイルできるようにする[編集 | ソースを編集]

Mac上でHLSLをコンパイルする方法は以下を参照。

HLSL側を書く[編集 | ソースを編集]

ワールド座標(ワールド空間内での位置や傾きなど)は、

一般的なシェーダーではユニフォーム変数で受け取る。

float4x4 World; //これがワールド座標
float4x4 View;
float4x4 Projection;

一方、ハードウェアインスタンシングを使う場合はダイナミック頂点バッファとして複数の値を受け取る。

float4x4 View;
float4x4 Projection;

// これがワールド座標群を格納したダイナミック頂点バッファ。
// C#側にfloat4x4の送信手段がないので「float4が4個」で代用している。
struct VSInstance
{
	float4 w1 : BLENDWEIGHT0;
	float4 w2 : BLENDWEIGHT1;
	float4 w3 : BLENDWEIGHT2;
	float4 w4 : BLENDWEIGHT3;
};

// 「float4が4個」をシェーダー関数内でfloat4x4に再構成して利用する。
VSOutput VSFunc(VSInput input, VSInstance instance)
{
    float4x4 world = float4x4(instance.w1, instance.w2, instance.w3, instance.w4);
}

C#側を書く[編集 | ソースを編集]

まず3Dモデルのインスタンス(ワールド座標)を格納するクラスを作る。

public struct VertexDynamicInstance : IVertexType
{
    public Matrix World;

    public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration(
        new VertexElement( 0, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 0),
        new VertexElement(16, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 1),
        new VertexElement(32, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 2),
        new VertexElement(48, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 3));

    VertexDeclaration IVertexType.VertexDeclaration
    {
        get { return VertexDynamicInstance.VertexDeclaration; }
    }
}

とりあえず動作確認用に1000個のテストデータを作る。

var rand = new System.Random();
var instances = new VertexDynamicInstance[1000];
for (int i = 0; i < instances.Length; i++)
{
    instances[i].World = Matrix.CreateTranslation(
        (float)rand.NextDouble(), 
        (float)rand.NextDouble(), 
        (float)rand.NextDouble());
}

インスタンス群を格納したダイナミック頂点バッファを生成する。

// ダイナミック頂点バッファを作って
var instanceVertexBuffer = new DynamicVertexBuffer(graphicsDevice,
    VertexDynamicInstance.VertexDeclaration,
    instances.Length,
    BufferUsage.WriteOnly);

// インスタンス群を入れ込む
instanceVertexBuffer.SetData(instances);

描画する。

// 頂点バッファとダイナミック頂点バッファの2つを指定する。
graphicsDevice.SetVertexBuffers(
    new VertexBufferBinding( meshPart.VertexBuffer, meshPart.VertexOffset, 0),
    new VertexBufferBinding( instanceVertexBuffer , 0, 1));

// インデックスバッファは普通に指定する。
graphicsDevice.Indices = meshPart.IndexBuffer;

foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
    pass.Apply();

    // ドローコールはDrawInstancedPrimitivesで呼び出す。
    graphicsDevice.DrawInstancedPrimitives(
        primitiveType: PrimitiveType.TriangleList,
        baseVertex: 0,
        startIndex: 0,
        primitiveCount: meshPart.PrimitiveCount,
        instanceCount: instances.Length);
}

Mac上で動かしてみる[編集 | ソースを編集]

こんな感じ。MacでもHLSLで書いたシェーダーが使えてる。なお、OpenGL系の環境ではシェーダーモデルは3.0までだ。まあSM3.0もあれば元気があれば何でもできる。


関連項目[編集 | ソースを編集]