ページ「利用者・トーク:Administrator」と「MonoGameでハードウェアインスタンシングしてみる」の間の差分
(ページ間の差分)
ナビゲーションに移動
検索に移動
imported>Administrator ([[Special:Contributions/imported>Administrator|imported>Administrator]] ([[User talk:imported>Administrator|トーク]]) による版 313 を取り消し) |
imported>Administrator |
||
1行目: | 1行目: | ||
− | + | 1つの[[ポリゴンメッシュ]]を[[GPU]]側で複製することで[[Drawコール]]を減らして描画を高速化する「[[ハードウェアインスタンシング]]」という技術がある。 | |
− | |||
− | |||
− | |||
− | |||
− | + | ただ[[MonoGame]]と[[OpenGL]]の組み合わせの環境では長らく「new NotImplementedException()」であった。 | |
+ | |||
+ | これがついにMonoGame 3.7で[[OpenGL]]環境でも[[ハードウェアインスタンシング]]が使えるようになったそうだ。さっそく[[MacOS]]上で試してみた。[[ハードウェアインスタンシング]]が使えるとなると[[ボリュームレンダリング]]の実装が捗る可能性がある。 | ||
+ | |||
+ | ==HLSLを書く== | ||
+ | [[HLSL]]はこんな感じ。今回は移動だけで回転はしていない。 [[Mac]]上で[[HLSL]]を[[コンパイル]]する方法は「 [[MonoGameでプログラマブルシェーダーを使う]]」および「[[InfinitespaceStudios.Pipeline]]」の項目を参照。 | ||
+ | |||
+ | <syntaxhighlight lang="text"> | ||
+ | // file: effect4.fx | ||
+ | |||
+ | // ---------------------------------------------------------------- | ||
+ | // MonoGame PipelineでOpenGL環境の場合は「OPENGL」シンボルが立っている。 | ||
+ | // 以下は定型文だと思ってコピペしとけ。 | ||
+ | #if OPENGL | ||
+ | #define SV_POSITION POSITION | ||
+ | #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 | ||
+ | |||
+ | // ---------------------------------------------------------------- | ||
+ | // 呼び出し側(C#)から設定されるグローバル変数(シェーダー内では実質定数) | ||
+ | float4x4 myView : VIEW; | ||
+ | float4x4 myProjection : PROJECTION; | ||
+ | |||
+ | // ---------------------------------------------------------------- | ||
+ | // 入出力用の構造体 | ||
+ | struct VertexPositionColor | ||
+ | { | ||
+ | float4 Position : POSITION0; | ||
+ | float4 Color : COLOR; | ||
+ | }; | ||
+ | |||
+ | // ---------------------------------------------------------------- | ||
+ | // バーテックスシェーダー | ||
+ | VertexPositionColor MyVertexShader( | ||
+ | VertexPositionColor input, | ||
+ | float3 position : POSITION1 | ||
+ | ) | ||
+ | { | ||
+ | // inputの位置をpositionほど移動させる。 | ||
+ | // 今回は移動だけ。 | ||
+ | // 回転したい場合なども同じ要領でここで変形させる。 | ||
+ | input.Position.xyz += position; | ||
+ | |||
+ | // 頂点をカメラから見た座標に変換する。 | ||
+ | input.Position = mul( input.Position, mul(myView, myProjection)); | ||
+ | return input; | ||
+ | } | ||
+ | |||
+ | // ---------------------------------------------------------------- | ||
+ | // ピクセルシェーダー | ||
+ | float4 MyPixelShader(float4 color : COLOR) : COLOR0 | ||
+ | { | ||
+ | return color; | ||
+ | } | ||
+ | |||
+ | technique HardwareInstancing | ||
+ | { | ||
+ | pass Pass1 | ||
+ | { | ||
+ | VertexShader = compile VS_SHADERMODEL MyVertexShader(); | ||
+ | PixelShader = compile PS_SHADERMODEL MyPixelShader(); | ||
+ | } | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | ==C#で呼び出してみる== | ||
+ | この[[コード]]は「[[MonoGameのカメラを作る]]」で作ったカメラを使っている。Cameraクラスは[[MonoGame]]標準物ではないので注意。 | ||
+ | |||
+ | <syntaxhighlight lang="csharp"> | ||
+ | using System; | ||
+ | using System.Collections.Generic; | ||
+ | using System.Text; | ||
+ | using Microsoft.Xna.Framework; | ||
+ | using Microsoft.Xna.Framework.Graphics; | ||
+ | using Microsoft.Xna.Framework.Input; | ||
+ | |||
+ | public class Game1 : Game | ||
+ | { | ||
+ | GraphicsDeviceManager graphics; | ||
+ | |||
+ | Camera camera; | ||
+ | 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); | ||
+ | |||
+ | // バーテックスバッファーを転送 | ||
+ | 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でプログラマブルシェーダーを使う]] | ||
+ | *[[MonoGameでピクセルシェーダーを使ってテクスチャを貼る]] | ||
+ | *[[ハードウェアインスタンシング]] | ||
+ | *[[疑似インスタンシング]] | ||
+ | *[[メッシュベイカー]] | ||
+ | *[[ドローコール]] | ||
+ | |||
+ | [[category: MonoGame]] | ||
+ | [[category: HLSL]] |
2019年10月27日 (日) 00:01時点における版
1つのポリゴンメッシュをGPU側で複製することでDrawコールを減らして描画を高速化する「ハードウェアインスタンシング」という技術がある。
ただMonoGameとOpenGLの組み合わせの環境では長らく「new NotImplementedException()」であった。
これがついにMonoGame 3.7でOpenGL環境でもハードウェアインスタンシングが使えるようになったそうだ。さっそくMacOS上で試してみた。ハードウェアインスタンシングが使えるとなるとボリュームレンダリングの実装が捗る可能性がある。
HLSLを書く
HLSLはこんな感じ。今回は移動だけで回転はしていない。 Mac上でHLSLをコンパイルする方法は「 MonoGameでプログラマブルシェーダーを使う」および「InfinitespaceStudios.Pipeline」の項目を参照。
// file: effect4.fx
// ----------------------------------------------------------------
// MonoGame PipelineでOpenGL環境の場合は「OPENGL」シンボルが立っている。
// 以下は定型文だと思ってコピペしとけ。
#if OPENGL
#define SV_POSITION POSITION
#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
// ----------------------------------------------------------------
// 呼び出し側(C#)から設定されるグローバル変数(シェーダー内では実質定数)
float4x4 myView : VIEW;
float4x4 myProjection : PROJECTION;
// ----------------------------------------------------------------
// 入出力用の構造体
struct VertexPositionColor
{
float4 Position : POSITION0;
float4 Color : COLOR;
};
// ----------------------------------------------------------------
// バーテックスシェーダー
VertexPositionColor MyVertexShader(
VertexPositionColor input,
float3 position : POSITION1
)
{
// inputの位置をpositionほど移動させる。
// 今回は移動だけ。
// 回転したい場合なども同じ要領でここで変形させる。
input.Position.xyz += position;
// 頂点をカメラから見た座標に変換する。
input.Position = mul( input.Position, mul(myView, myProjection));
return input;
}
// ----------------------------------------------------------------
// ピクセルシェーダー
float4 MyPixelShader(float4 color : COLOR) : COLOR0
{
return color;
}
technique HardwareInstancing
{
pass Pass1
{
VertexShader = compile VS_SHADERMODEL MyVertexShader();
PixelShader = compile PS_SHADERMODEL MyPixelShader();
}
}
C#で呼び出してみる
このコードは「MonoGameのカメラを作る」で作ったカメラを使っている。CameraクラスはMonoGame標準物ではないので注意。
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
public class Game1 : Game
{
GraphicsDeviceManager graphics;
Camera camera;
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);
// バーテックスバッファーを転送
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);
}
}
Mac上で動かしてみる
こんな感じ。MacでもHLSLで書いたシェーダーが使えてる。なお、OpenGL系の環境ではシェーダーモデルは3.0までだ。まあSM3.0もあれば元気があれば何でもできる。