MonoGameでピクセルシェーダーを使ってテクスチャを貼る
「MonoGameでプログラマブルシェーダーを使う」の実践編として以下をやってみる。Macでやっているのがミソ。
- MacOS上のVisual Studio for MacとMonoGameを使い
- バーテックスシェーダーでジオメトリ変換してみる
- ピクセルシェーダーでテクスチャマッピングしてみる。
準備 編集
HLSLを書く 編集
iOSやAndroid向けの場合は強制的に「OPENGL」シンボルが定義されており、HLSLのシェーダーモデルは3.0固定にしておけ。
// file: effect1.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
// ----------------------------------------------------------------
// 呼び出し側から設定されるグローバル変数(シェーダー内では実質定数)
// カメラ座標
float4x4 myView;
float4x4 myProjection;
// テクスチャ
texture myTexture;
// ----------------------------------------------------------------
// tex2D関数で使うテクスチャサンプラー
// tex2D関数はサンプラーと座標を入力すると、テクスチャからその位置の
// テクセルを抜き出してCOLORが返ってくる。
sampler mySampler = sampler_state
{
Texture = <myTexture>;
};
// ----------------------------------------------------------------
// 入出力用の構造体
struct VertexShaderInput
{
float4 Position : POSITION0;
float4 TextureCoordinate : TEXCOORD;
};
struct VertexShaderOutput
{
float4 Position : SV_POSITION;
float4 TextureCoordinate : TEXCOORD;
};
// ----------------------------------------------------------------
// バーテックスシェーダー
VertexShaderOutput MainVS(in VertexShaderInput input)
{
// 出力用の頂点
VertexShaderOutput output = (VertexShaderOutput)0;
// 頂点をカメラから見た座標に変換
output.Position = mul(input.Position, mul(myView, myProjection));
// テクスチャ座標をコピー
output.TextureCoordinate = input.TextureCoordinate;
return output;
}
// ----------------------------------------------------------------
// ピクセルシェーダー
float4 MainPS(VertexShaderOutput input) : COLOR
{
// テクスチャサンプラーからピクセルに対応する色を抜き出す
return tex2D( mySampler, input.TextureCoordinate);
}
// ----------------------------------------------------------------
// テクニック、いわゆるエントリーポイント
technique MyTechnique
{
pass MyPass
{
VertexShader = compile VS_SHADERMODEL MainVS();
PixelShader = compile PS_SHADERMODEL MainPS();
}
};
HLSLをコンパイルする 編集
macOS上のVisual Studio for MacではHLSLをコンパイルするには準備の際にいれた「InfinitespaceStudios.Pipeline」を用いる。
この際にProjectツリーの「Content」を開き「Platform」の設定が間違っていないことを確認すること。iOSやAndroid向けなのに「Windows」のままコンパイルするとOpenGLではなくDirectX向けのモノができあがり残念なことになる。なお、iOSとAndroidを間違えてもどっちもOpenGL系なので動く。
コンテンツファイルを準備 編集
このサンプルで使っているコンテンツファイルは以下のとおり。
- texture1.xnb (texture1.pngをコンパイルしたもの。画像は自分で用意してください)
- effect1.xnb (上記のHLSLをコンパイルしたもの)
C#で呼び出してみる 編集
即席コードなのでバーテックスバッファーとインデックスバッファーは使用せずにDrawUserPrimitivesメソッドで描画を行っている。
また、「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;
SpriteBatch spriteBatch;
Camera camera;
Effect effect;
Texture texture;
VertexPositionTexture[] vertices = {
new VertexPositionTexture(new Vector3( 0, 1, 0), new Vector2(0.5f, 0.0f)),
new VertexPositionTexture(new Vector3( 1, 0, 0), new Vector2(1.0f, 1.0f)),
new VertexPositionTexture(new Vector3(-1, 0, 0), new Vector2(0.0f, 1.0f))
};
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
protected override void Initialize()
{
IsMouseVisible = true;
base.Initialize();
}
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// 即席カメラ
camera = new Camera(this);
camera.Position = new Vector3(0, -2, 2);
camera.Target = new Vector3(0, 0, 0);
// テクスチャ読み込み
texture = Content.Load<Texture2D>("texture1");
// エフェクト読み込み
effect = Content.Load<Effect>("effect1");
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
// カメラを回転させる
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);
effect.Parameters["myTexture"].SetValue(texture);
foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
// パス適用開始(シェーダー有効化)
pass.Apply();
// 描画
graphics.GraphicsDevice.DrawUserPrimitives(
primitiveType: PrimitiveType.TriangleList,
vertexData: vertices,
vertexOffset: 0,
primitiveCount: vertices.Length / 3);
}
base.Draw(gameTime);
}
}
動かしてみる 編集
こんな感じ。MacでもHLSLで書いたシェーダーが使えてる。なお、シェーダーモデル3.0までだがSM3.0まであればそこそこなことはできる。。
動画