メインメニューを開く

差分

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

2,867 バイト除去, 2020年4月15日 (水) 05:58
1つの[[ポリゴンメッシュ]]を[[GPU]]側で複製することでDrawコールを減らして描画を高速化する「側で複製することで[[Drawコール]]を減らして描画を高速化する「[[ハードウェアインスタンシング]]」という技術がある。
ただ[[MonoGame]]と[[OpenGL]]の環境では長らく「new の組み合わせの環境では長らく「new NotImplementedException()」であった。
これがついにMonoGame 3.7で[[OpenGL]]環境でも[[ハードウェアインスタンシング]]が使えるようになったそうだ。さっそく[[MacOS]]上で試してみた。[[ハードウェアインスタンシング]]が使えるとなると[[ボリュームレンダリング]]の実装が捗る可能性がある。
== HLSLを書くMacでシェーダーをコンパイルできるようにする ==[[HLSL]]はこんな感じ。今回は移動だけで回転はしていない。 [[Mac]]上で[[HLSL]]を[[コンパイル]]する方法は「 Mac上でHLSLをコンパイルする方法は以下を参照。* [[MonoGameでプログラマブルシェーダーを使う]]」および「* [[InfinitespaceStudios.Pipeline]]」の項目を参照。
<syntaxhighlight lang="text">= HLSL側を書く ==// file: effect4.fx[[ワールド座標]](ワールド空間内での位置や傾きなど)は、
// ----------------------------------------------------------------一般的な[[シェーダー]]では[[ユニフォーム変数]]で受け取る。// MonoGame PipelineでOpenGL環境の場合は「OPENGL」シンボルが立っている。<source lang="hlsl">float4x4 World; // 以下は定型文だと思ってコピペしとけ。これがワールド座標#if OPENGLfloat4x4 View; #define SV_POSITION POSITIONfloat4x4 Projection; #define VS_SHADERMODEL vs_3_0 #define PS_SHADERMODEL ps_3_0#else #define VS_SHADERMODEL vs_4_0_level_9_1 #define PS_SHADERMODEL ps_4_0_level_9_1#endif</source>
// ----------------------------------------------------------------一方、[[ハードウェアインスタンシング]]を使う場合はダイナミック頂点バッファとして複数の値を受け取る。// 呼び出し側(C#)から設定されるグローバル変数(シェーダー内では実質定数)<source lang="hlsl">float4x4 myView : VIEWView;float4x4 myProjection : PROJECTIONProjection;
// ----------------------------------------------------------------これがワールド座標群を格納したダイナミック頂点バッファ。// 入出力用の構造体C#側にfloat4x4の送信手段がないので「float4が4個」で代用している。struct VertexPositionColorVSInstance
{
float4 Position w1 : POSITION0BLENDWEIGHT0; float4 Color w2 : COLORBLENDWEIGHT1; float4 w3 : BLENDWEIGHT2; float4 w4 : BLENDWEIGHT3;
};
// ----------------------------------------------------------------「float4が4個」をシェーダー関数内でfloat4x4に再構成して利用する。// バーテックスシェーダーVertexPositionColor MyVertexShaderVSOutput VSFunc( VertexPositionColor VSInput input, float3 position : POSITION1 VSInstance instance)
{
// inputの位置をpositionほど移動させる。 // 今回は移動だけ。 // 回転したい場合なども同じ要領でここで変形させる。 input.Position.xyz +float4x4 world = position;  // 頂点をカメラから見た座標に変換する。 inputfloat4x4(instance.Position = mul( inputw1, instance.Positionw2, mul(myViewinstance.w3, myProjection)instance.w4); return input;
}
<// ----------------------------------------------------------------// ピクセルシェーダーfloat4 MyPixelShader(float4 color : COLOR) : COLOR0{ return color;}source>
technique HardwareInstancing== C#側を書く ==まず3Dモデルのインスタンス([[ワールド座標]])を格納する[[クラス]]を作る。<source lang="csharp">public struct VertexDynamicInstance : IVertexType
{
pass Pass1 { VertexShader = compile VS_SHADERMODEL MyVertexShader() public Matrix World; PixelShader = compile PS_SHADERMODEL MyPixelShader(); }}</syntaxhighlight>== C#で呼び出してみる==この[[コード]]は「[[MonoGameのカメラを作る]]」で作ったカメラを使っている。Cameraクラスは[[MonoGame]]標準物ではないので注意。
<syntaxhighlight lang public static readonly VertexDeclaration VertexDeclaration ="csharp"> using System;new VertexDeclaration( using System new VertexElement( 0, VertexElementFormat.Collections.Generic; using SystemVector4, VertexElementUsage.Text;BlendWeight, 0), using Microsoft new VertexElement(16, VertexElementFormat.XnaVector4, VertexElementUsage.Framework;BlendWeight, 1), using Microsoft.Xna new VertexElement(32, VertexElementFormat.FrameworkVector4, VertexElementUsage.Graphics;BlendWeight, 2), using Microsoft.Xna new VertexElement(48, VertexElementFormat.FrameworkVector4, VertexElementUsage.InputBlendWeight, 3));
public class Game1 : GameVertexDeclaration IVertexType.VertexDeclaration
{
GraphicsDeviceManager graphicsget { return VertexDynamicInstance.VertexDeclaration;} }}</source>
Camera cameraとりあえず動作確認用に1000個のテストデータを作る。<source lang="csharp">var rand = new System.Random();var instances = new VertexDynamicInstance[1000]; Effect effectfor (int i = 0; i < instances.Length;i++){ instances[i].World = Matrix.CreateTranslation( VertexBuffer triangleVertexBuffer;(float)rand.NextDouble(), IndexBuffer indexBuffer;(float)rand.NextDouble(), DynamicVertexBuffer instanceVertexBuffer(float)rand.NextDouble());}</source>
VertexPositionColor[] vertices インスタンス群を格納したダイナミック頂点バッファを生成する。<source lang= {"csharp">// ダイナミック頂点バッファを作って var instanceVertexBuffer = new VertexPositionColorDynamicVertexBuffer(new Vector3(-1graphicsDevice, 1, 0), Color VertexDynamicInstance.Blue )VertexDeclaration, new VertexPositionColor(new Vector3( 1, 1, 0), Color instances.White)Length, new VertexPositionColor(new Vector3( 1, -1, 0), Color BufferUsage.Red WriteOnly) };
Vector3[] instances = new Vector3[] {// インスタンス群を入れ込む new Vector3instanceVertexBuffer.SetData(0f, 0f, 0finstances), new Vector3(1f, 1f, 0f),; new Vector3(2f, 2f, 0f) };</source>
public Game1描画する。<source lang="csharp">// 頂点バッファとダイナミック頂点バッファの2つを指定する。graphicsDevice.SetVertexBuffers( new VertexBufferBinding( meshPart.VertexBuffer, meshPart.VertexOffset, 0), { graphics = new GraphicsDeviceManagerVertexBufferBinding(thisinstanceVertexBuffer , 0, 1)); Content.RootDirectory = "Content"; }
protected override void Initialize()// インデックスバッファは普通に指定する。 { IsMouseVisible graphicsDevice.Indices = true; basemeshPart.Initialize()IndexBuffer; }
protected override void LoadContentforeach (EffectPass pass in effect.CurrentTechnique.Passes) { // 即席カメラ camera = new Camera(this); camera.Position = new Vector3(0, 0, 10); camera pass.Target = new Vector3Apply(0, 0, 0);
// エフェクト読み込みドローコールはDrawInstancedPrimitivesで呼び出す。 effect = Content graphicsDevice.Load<Effect>DrawInstancedPrimitives("effect4" primitiveType: PrimitiveType.TriangleList, baseVertex: 0, startIndex: 0, primitiveCount: meshPart.PrimitiveCount, instanceCount: instances.Length);}</source>
// バーテックスバッファーを生成 triangleVertexBuffer = new VertexBuffer( GraphicsDevice, VertexPositionColor.VertexDeclaration,= Mac上で動かしてみる == verticesこんな感じ。[[Mac]]でも[[HLSL]]で書いた[[シェーダー]]が使えてる。なお、[[OpenGL]]系の環境では[[シェーダーモデル]]は3.Length, BufferUsage.WriteOnly); triangleVertexBuffer0までだ。まあSM3.SetData<VertexPositionColor>(vertices);0もあれば元気があれば何でもできる。
<movie>https:// インデックスバッファーを生成 var index = new ushort[] { 0, 1, 2 }; indexBuffer = new IndexBuffer( GraphicsDevice, IndexElementSizeyoutu.SixteenBits, index.Length, BufferUsage.WriteOnly); indexBuffer.SetDatabe/qU-toUOhvRI<ushort/movie>(index);
// インスタンスバッファーを生成
instanceVertexBuffer = new DynamicVertexBuffer(
GraphicsDevice,
new VertexDeclaration(
new VertexElement(
offset: 0,
elementFormat: VertexElementFormat.Vector3,
elementUsage: VertexElementUsage.Position,
usageIndex: 1)),
instances.Length,
BufferUsage.WriteOnly);
instanceVertexBuffer.SetData(instances);
}
 
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
 
// TODO: Add your update logic here
camera.Angle += 1f;
 
base.Update(gameTime);
}
 
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
 
// シェーダーのグローバル変数に値をセットする
effect.Parameters["myView"].SetValue(camera.View);
effect.Parameters["myProjection"].SetValue(camera.Projection);
 
// バーテックスバッファーを転送
GraphicsDevice.SetVertexBuffers(
new VertexBufferBinding(triangleVertexBuffer, 0, 0),
new VertexBufferBinding(instanceVertexBuffer, 0, 1));
 
// インデックスバッファーを転送
GraphicsDevice.Indices = indexBuffer;
 
// インスタンスバッファーを転送
instanceVertexBuffer.SetData(instances, 0, instances.Length, SetDataOptions.Discard);
 
// 描画
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
pass.Apply();
 
graphics.GraphicsDevice.DrawInstancedPrimitives(
primitiveType: PrimitiveType.TriangleList,
baseVertex: 0,
startIndex: 0,
primitiveCount: vertices.Length / 3,
instanceCount: instances.Length);
 
}
 
base.Draw(gameTime);
}
}
</syntaxhighlight>
 
== Mac上で動かしてみる==
こんな感じ。[[Mac]]でも[[HLSL]]で書いた[[シェーダー]]が使えてる。なお、[[OpenGL]]系の環境では[[シェーダーモデル]]は3.0までだ。まあSM3.0もあれば元気があれば何でもできる。
 
<movie>https://youtu.be/qU-toUOhvRI</movie>
== 関連項目==* [[MonoGameでHLSLにMatrixを渡す]]*[[MonoGameでプログラマブルシェーダーを使う]]*[[MonoGameでピクセルシェーダーを使ってテクスチャを貼る‎]]* [[ハードウェアインスタンシング]]* [[疑似インスタンシング]]* [[メッシュベイカー]]* [[ドローコール]]
[[category: MonoGame]]
[[category: HLSL]]