「MonoGameでハードウェアインスタンシングしてみる」を編集中

ナビゲーションに移動 検索に移動

警告: ログインしていません。編集を行うと、あなたの IP アドレスが公開されます。ログインまたはアカウントを作成すれば、あなたの編集はその利用者名とともに表示されるほか、その他の利点もあります。

この編集を取り消せます。 下記の差分を確認して、本当に取り消していいか検証してください。よろしければ変更を保存して取り消しを完了してください。

最新版 編集中の文章
5行目: 5行目:
 
これがついにMonoGame 3.7で[[OpenGL]]環境でも[[ハードウェアインスタンシング]]が使えるようになったそうだ。さっそく[[MacOS]]上で試してみた。[[ハードウェアインスタンシング]]が使えるとなると[[ボリュームレンダリング]]の実装が捗る可能性がある。
 
これがついにMonoGame 3.7で[[OpenGL]]環境でも[[ハードウェアインスタンシング]]が使えるようになったそうだ。さっそく[[MacOS]]上で試してみた。[[ハードウェアインスタンシング]]が使えるとなると[[ボリュームレンダリング]]の実装が捗る可能性がある。
  
== Macでシェーダーをコンパイルできるようにする ==
+
==HLSLを書く==
Mac上でHLSLをコンパイルする方法は以下を参照。
+
[[HLSL]]はこんな感じ。今回は移動だけで回転はしていない。 [[Mac]]上で[[HLSL]]を[[コンパイル]]する方法は「 [[MonoGameでプログラマブルシェーダーを使う]]」および「[[InfinitespaceStudios.Pipeline]]」の項目を参照。
* [[MonoGameでプログラマブルシェーダーを使う]]
 
* [[InfinitespaceStudios.Pipeline]]
 
  
== HLSL側を書く ==
+
<syntaxhighlight lang="text">
[[ワールド座標]](ワールド空間内での位置や傾きなど)は、
+
// file: effect4.fx
  
一般的な[[シェーダー]]では[[ユニフォーム変数]]で受け取る。
+
// ----------------------------------------------------------------
<source lang="hlsl">
+
// MonoGame PipelineでOpenGL環境の場合は「OPENGL」シンボルが立っている。
float4x4 World; //これがワールド座標
+
// 以下は定型文だと思ってコピペしとけ。
float4x4 View;
+
#if OPENGL
float4x4 Projection;
+
#define SV_POSITION POSITION
</source>
+
#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 lang="hlsl">
+
// 呼び出し側(C#)から設定されるグローバル変数(シェーダー内では実質定数)
float4x4 View;
+
float4x4 myView : VIEW;
float4x4 Projection;
+
float4x4 myProjection : PROJECTION;
  
// これがワールド座標群を格納したダイナミック頂点バッファ。
+
// ----------------------------------------------------------------
// C#側にfloat4x4の送信手段がないので「float4が4個」で代用している。
+
// 入出力用の構造体
struct VSInstance
+
struct VertexPositionColor
 
{
 
{
float4 w1 : BLENDWEIGHT0;
+
float4 Position : POSITION0;
float4 w2 : BLENDWEIGHT1;
+
float4 Color : COLOR;
float4 w3 : BLENDWEIGHT2;
 
float4 w4 : BLENDWEIGHT3;
 
 
};
 
};
  
// 「float4が4個」をシェーダー関数内でfloat4x4に再構成して利用する。
+
// ----------------------------------------------------------------
VSOutput VSFunc(VSInput input, VSInstance instance)
+
// バーテックスシェーダー
 +
VertexPositionColor MyVertexShader(
 +
VertexPositionColor input,
 +
float3 position : POSITION1
 +
)
 
{
 
{
     float4x4 world = float4x4(instance.w1, instance.w2, instance.w3, instance.w4);
+
     // inputの位置をpositionほど移動させる。
 +
    // 今回は移動だけ。
 +
    // 回転したい場合なども同じ要領でここで変形させる。
 +
input.Position.xyz += position;
 +
 
 +
    // 頂点をカメラから見た座標に変換する。
 +
input.Position = mul( input.Position, mul(myView, myProjection));
 +
return input;
 
}
 
}
  
</source>
+
// ----------------------------------------------------------------
 +
// ピクセルシェーダー
 +
float4 MyPixelShader(float4 color : COLOR) : COLOR0
 +
{
 +
return color;
 +
}
  
== C#側を書く ==
+
technique HardwareInstancing
まず3Dモデルのインスタンス([[ワールド座標]])を格納する[[クラス]]を作る。
 
<source lang="csharp">
 
public struct VertexDynamicInstance : IVertexType
 
 
{
 
{
    public Matrix World;
+
pass Pass1
 +
{
 +
VertexShader = compile VS_SHADERMODEL MyVertexShader();
 +
PixelShader  = compile PS_SHADERMODEL MyPixelShader();
 +
}
 +
}
 +
</syntaxhighlight>
 +
==C#で呼び出してみる==
 +
この[[コード]]は「[[MonoGameのカメラを作る]]」で作ったカメラを使っている。Cameraクラスは[[MonoGame]]標準物ではないので注意。
  
     public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration(
+
<syntaxhighlight lang="csharp">
        new VertexElement( 0, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 0),
+
     using System;
        new VertexElement(16, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 1),
+
    using System.Collections.Generic;
        new VertexElement(32, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 2),
+
    using System.Text;
        new VertexElement(48, VertexElementFormat.Vector4, VertexElementUsage.BlendWeight, 3));
+
    using Microsoft.Xna.Framework;
 +
    using Microsoft.Xna.Framework.Graphics;
 +
    using Microsoft.Xna.Framework.Input;
  
     VertexDeclaration IVertexType.VertexDeclaration
+
     public class Game1 : Game
 
     {
 
     {
         get { return VertexDynamicInstance.VertexDeclaration; }
+
         GraphicsDeviceManager graphics;
    }
+
 
}
+
        Camera camera;
</source>
+
        Effect effect;
 +
        VertexBuffer triangleVertexBuffer;
 +
        IndexBuffer indexBuffer;
 +
        DynamicVertexBuffer instanceVertexBuffer;
 +
 
 +
        VertexPositionColor[] vertices = {
 +
            new VertexPositionColor(new Vector3(-1,  1, 0), Color.Blue ),
 +
            new VertexPositionColor(new Vector3( 1,  1, 0), Color.White),
 +
            new VertexPositionColor(new Vector3( 1, -1, 0), Color.Red  )
 +
        };
 +
 
 +
        Vector3[] instances = new Vector3[] {
 +
            new Vector3(0f, 0f, 0f),
 +
            new Vector3(1f, 1f, 0f),
 +
            new Vector3(2f, 2f, 0f)
 +
        };
 +
 
 +
        public Game1()
 +
        {
 +
            graphics = new GraphicsDeviceManager(this);
 +
            Content.RootDirectory = "Content";
 +
        }
 +
 
 +
        protected override void Initialize()
 +
        {
 +
            IsMouseVisible = true;
 +
            base.Initialize();
 +
        }
 +
 
 +
        protected override void LoadContent()
 +
        {
 +
            // 即席カメラ
 +
            camera = new Camera(this);
 +
            camera.Position = new Vector3(0, 0, 10);
 +
            camera.Target = new Vector3(0, 0, 0);
 +
 
 +
            // エフェクト読み込み
 +
            effect = Content.Load<Effect>("effect4");
 +
 
 +
            // バーテックスバッファーを生成
 +
            triangleVertexBuffer = new VertexBuffer(
 +
                GraphicsDevice,
 +
                VertexPositionColor.VertexDeclaration,
 +
                vertices.Length,
 +
                BufferUsage.WriteOnly);
 +
            triangleVertexBuffer.SetData<VertexPositionColor>(vertices);
 +
 
 +
            // インデックスバッファーを生成
 +
            var index = new ushort[] { 0, 1, 2 };
 +
            indexBuffer = new IndexBuffer(
 +
                GraphicsDevice,
 +
                IndexElementSize.SixteenBits,
 +
                index.Length,
 +
                BufferUsage.WriteOnly);
 +
            indexBuffer.SetData<ushort>(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);
  
とりあえず動作確認用に1000個のテストデータを作る。
+
            // バーテックスバッファーを転送
<source lang="csharp">
+
            GraphicsDevice.SetVertexBuffers(
var rand = new System.Random();
+
                new VertexBufferBinding(triangleVertexBuffer, 0, 0),
var instances = new VertexDynamicInstance[1000];
+
                new VertexBufferBinding(instanceVertexBuffer, 0, 1));
for (int i = 0; i < instances.Length; i++)
 
{
 
    instances[i].World = Matrix.CreateTranslation(
 
        (float)rand.NextDouble(),  
 
        (float)rand.NextDouble(),  
 
        (float)rand.NextDouble());
 
}
 
</source>
 
  
インスタンス群を格納したダイナミック頂点バッファを生成する。
+
            // インデックスバッファーを転送
<source lang="csharp">
+
            GraphicsDevice.Indices = indexBuffer;
// ダイナミック頂点バッファを作って
 
var instanceVertexBuffer = new DynamicVertexBuffer(graphicsDevice,
 
    VertexDynamicInstance.VertexDeclaration,
 
    instances.Length,
 
    BufferUsage.WriteOnly);
 
  
// インスタンス群を入れ込む
+
            // インスタンスバッファーを転送
instanceVertexBuffer.SetData(instances);
+
            instanceVertexBuffer.SetData(instances, 0, instances.Length, SetDataOptions.Discard);
</source>
 
  
描画する。
+
            // 描画
<source lang="csharp">
+
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
// 頂点バッファとダイナミック頂点バッファの2つを指定する。
+
            {
graphicsDevice.SetVertexBuffers(
+
                pass.Apply();
    new VertexBufferBinding( meshPart.VertexBuffer, meshPart.VertexOffset, 0),
 
    new VertexBufferBinding( instanceVertexBuffer , 0, 1));
 
  
// インデックスバッファは普通に指定する。
+
                graphics.GraphicsDevice.DrawInstancedPrimitives(
graphicsDevice.Indices = meshPart.IndexBuffer;
+
                    primitiveType: PrimitiveType.TriangleList,
 +
                    baseVertex: 0,
 +
                    startIndex: 0,
 +
                    primitiveCount: vertices.Length / 3,
 +
                    instanceCount: instances.Length);
  
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
+
            }
{
 
    pass.Apply();
 
  
    // ドローコールはDrawInstancedPrimitivesで呼び出す。
+
            base.Draw(gameTime);
    graphicsDevice.DrawInstancedPrimitives(
+
         }
         primitiveType: PrimitiveType.TriangleList,
+
    }
        baseVertex: 0,
+
</syntaxhighlight>
        startIndex: 0,
 
        primitiveCount: meshPart.PrimitiveCount,
 
        instanceCount: instances.Length);
 
}
 
</source>
 
  
== Mac上で動かしてみる ==
+
==Mac上で動かしてみる==
 
こんな感じ。[[Mac]]でも[[HLSL]]で書いた[[シェーダー]]が使えてる。なお、[[OpenGL]]系の環境では[[シェーダーモデル]]は3.0までだ。まあSM3.0もあれば元気があれば何でもできる。
 
こんな感じ。[[Mac]]でも[[HLSL]]で書いた[[シェーダー]]が使えてる。なお、[[OpenGL]]系の環境では[[シェーダーモデル]]は3.0までだ。まあSM3.0もあれば元気があれば何でもできる。
  
 
<movie>https://youtu.be/qU-toUOhvRI</movie>
 
<movie>https://youtu.be/qU-toUOhvRI</movie>
  
 +
==関連項目==
  
 
+
*[[MonoGameでプログラマブルシェーダーを使う]]
== 関連項目 ==
+
*[[MonoGameでピクセルシェーダーを使ってテクスチャを貼る‎]]
* [[MonoGameでHLSLにMatrixを渡す]]
+
*[[ハードウェアインスタンシング]]
* [[MonoGameでプログラマブルシェーダーを使う]]
+
*[[疑似インスタンシング]]
* [[MonoGameでピクセルシェーダーを使ってテクスチャを貼る‎]]
+
*[[メッシュベイカー]]
* [[ハードウェアインスタンシング]]
+
*[[ドローコール]]
* [[疑似インスタンシング]]
 
* [[メッシュベイカー]]
 
* [[ドローコール]]
 
  
 
[[category: MonoGame]]
 
[[category: MonoGame]]
 
[[category: HLSL]]
 
[[category: HLSL]]

MonoBookへの投稿はすべて、他の投稿者によって編集、変更、除去される場合があります。 自分が書いたものが他の人に容赦なく編集されるのを望まない場合は、ここに投稿しないでください。
また、投稿するのは、自分で書いたものか、パブリック ドメインまたはそれに類するフリーな資料からの複製であることを約束してください(詳細はMonoBook:著作権を参照)。 著作権保護されている作品は、許諾なしに投稿しないでください!

このページを編集するには、下記の確認用の質問に回答してください (詳細):

取り消し 編集の仕方 (新しいウィンドウで開きます)