忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

かんたんXNA HLSL編 その7 テクスチャ

このページは古いです
最新版はこちら

今回はテクスチャです。
ポリゴンの上に貼り付けるテクスチャを扱います。
つまりBasicEffectで言うとTextureプロパティ関連でやっていたことです。
ただしはるかに柔軟なことが出来ます。


大まかな流れを言うと、

「まずHLSL側でtexture型のグローバル変数を宣言し、
C#側からそれにインスタンスをセットし、
ピクセルシェーダでそのテクスチャから
相当するテクスチャ座標の色をサンプルして出力する」、

といったぐあいです。


これをやると「ピクセルの色は自分が完全に決めているんだなぁ」という実感が持てると思います。
ちょこっとプログラムを書き換えると水面の波のような効果を出すことも出来ます。


HLSLでテクスチャを宣言するにはこうします:

texture [変数名];

このテクスチャのテクセル(テクスチャのピクセル)の色を、
ピクセルシェーダで使います。
つまり、ピクセルシェーダの引数として与えられるテクスチャ座標に
対応する部分のテクスチャの色をtex2D関数を使って得て、戻り値として返すのです。

float4 tex2D ( sampler samplerState, float2 textureCoordinate )

textureCoordinateはテクスチャ上の座標を意味します。ここに指定した座標の色が返されます。
この関数の戻り値は色を表すfloat4です。
こいつをピクセルシェーダに使うことになるでしょう。

ただし、最初の引数samplerというのを見てわかるように、
初学者にとっては困ったことですが、このままではtex2D関数を使うことが出来ません。
テクスチャからどのように色を取り出す(サンプルする)かを
表すサンプラを宣言しなければならないのです。
このサンプラしだいで表示されるテクスチャがギザギザになったり滑らかになったりします。

しかしまあ、最初は一番シンプルな例を見るべきでしょう。
ギザギザにはなってしまいますが。

sampler [変数名] = sampler_state
{
        Texture = <テクスチャの変数名>;
};

「テクスチャの変数名」というのは、このサンプラが使うテクスチャです。
(HLSLの将来のバージョンではこれを設定する必要はなく、
サンプラとテクスチャは(正当にも)独立して扱えるようですが、
現時点では必ずテクスチャを指定してやらねばなりません。)

テクスチャの変数名は < > で囲ってやらなければなりません。(でも試したところ ( ) でもいけました。)
単に変数名を書いただけではコンパイルエラーになります。
(まあ、初期化が行われるときには変数にインスタンスは入っていないでしょうから正当です。
C#側からセットして初めてテクスチャの変数がつかえるようになるわけですからね。)

もしかしたら < > にはC/C++でいう変数のポインタみたいな意味があるのかもしれません。(わかりませんが)

この { } の中では他にも
テクスチャからサンプルする色を滑らかにするフィルターの
設定が出来るのですが、それは後で述べることにします。

MyGame.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;


public class MyGame : Game
{
    GraphicsDeviceManager graphics;
    ContentManager content;

    Effect effect;

    VertexPositionTexture[] vertices = {
        new VertexPositionTexture(new Vector3(0, 1, 0), new Vector2(0.5f, 0)),
        new VertexPositionTexture(new Vector3(1, 0, 0), new Vector2(1, 1)),
        new VertexPositionTexture(new Vector3(-1, 0, 0), new Vector2(0, 1))
    };


    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        content = new ContentManager(Services);
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = content.Load<Effect>("MyEffect");
            Matrix view = Matrix.CreateLookAt(
                new Vector3(1, 0, 1), 
                new Vector3(),
                new Vector3(0, 1, 0)
                );
            Matrix projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(90),
                (float)Window.ClientBounds.Width/Window.ClientBounds.Height,
                0.1f, 100
                );
            effect.Parameters["View"].SetValue(view);
            effect.Parameters["Projection"].SetValue(projection);
            effect.Parameters["MyTexture"].SetValue(content.Load<Texture2D>("Xnalogo"));

            graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
                graphics.GraphicsDevice,
                VertexPositionTexture.VertexElements
                );
        }
    }

    protected override void UnloadGraphicsContent(bool unloadAllContent)
    {
        if (unloadAllContent) { content.Unload(); }
    }

    protected override void Draw(GameTime gameTime)
    {
        graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

        effect.Begin();

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

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
                );

            pass.End();
        }

        effect.End();
    }
}

MyEffect.fx
float4x4 View;
float4x4 Projection;

texture MyTexture;
sampler mySampler = sampler_state{
	Texture = <MyTexture>;
};

struct VertexPositionTexture
{
	float4 Position : POSITION;
	float4 TextureCoordinate : TEXCOORD;
};

VertexPositionTexture MyVertexShader(VertexPositionTexture input)
{
	VertexPositionTexture output;
	output.Position = mul(input.Position, mul(View, Projection));
	output.TextureCoordinate = input.TextureCoordinate;
	return output;
}

float4 MyPixelShader(float2 textureCoordinate : TEXCOORD) : COLOR
{
	return tex2D(mySampler, textureCoordinate);
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}

hlslTesture.jpg(公式のXnalogo.pngを拝借してきました。クリックで拡大)
テクスチャがちゃんと張り付いていますね。

しかし、よくみると、テクスチャがギザギザしています。
一番近い"a"のところなんか荒すぎてすりガラスのようです。
手前はテクスチャが拡大されるので、そのように見えるのです。
これを治し滑らかにするには、サンプラでフィルターを指定してやります。

使うフィルターはMagFilter (Magnification filter)、
テクスチャが拡大されるときに使われえるフィルターです。
これをLinearにセットすると、拡大したとき滑らかに表示されます。
フィルターにセットできる値は、C#でのTextureFilter列挙型に対応しているようです。

定数名 解説
None  「MipMapは無効になり、ラスタライザはかわりにMagFilterを使います。」だそうです。
Point  滑らかになりません。一番近いテクセルの色が使われます。ギザギザです。
Linear  バイリニアフィルターです。周りの2x2の領域の中での平均値が使われます。いろんなサンプルを見たところ、これが一番使われているようですね。
Anisotropic  異方性フィルターです。
PyramidalQuad  4サンプル・テントフィルターです。
GaussianQuad  4サンプル・ガウシアンフィルターです。画像をぼかすことで滑らかにするそうです。

とりあえずLinearを使えば良いようです。
ほとんどのサンプルがLinearをフィルターとして使っています。

float4x4 View;
float4x4 Projection;

texture MyTexture;
sampler mySampler = sampler_state{
	Texture = <MyTexture>;
	MagFilter = Linear;
};

struct VertexPositionTexture
{
	float4 Position : POSITION;
	float4 TextureCoordinate : TEXCOORD;
};

VertexPositionTexture MyVertexShader(VertexPositionTexture input)
{
	VertexPositionTexture output;
	output.Position = mul(input.Position, mul(View, Projection));
	output.TextureCoordinate = input.TextureCoordinate;
	return output;
}

float4 MyPixelShader(float2 textureCoordinate : TEXCOORD) : COLOR
{
	return tex2D(mySampler, textureCoordinate);
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}

magLinear.jpg
近くの文字、"a"が滑らかになりました。

しかしやっぱり、よく見ると変です。
確かに近くの文字は滑らかになりましたが、遠くの文字はまだ荒れています。
"x"の下の部分がなんだかギザギザしていますね。

遠くの文字は遠近法によって縮小されるからです。
MagFilterをセットすることによってテクスチャを拡大したときには滑らかになったものの、
テクスチャを逆に縮小したときにはまだギザギザしてしまうのです。

テクスチャが縮小されるときに使われるフィルターはMinFilter (Minification Filter)です。
これにLinearをセットすると、テクスチャが縮小されたときも滑らかになります。
float4x4 View;
float4x4 Projection;

texture MyTexture;
sampler mySampler = sampler_state{
	Texture = <MyTexture>;
	MagFilter = Linear;
	MinFilter = Linear;
};

struct VertexPositionTexture
{
	float4 Position : POSITION;
	float4 TextureCoordinate : TEXCOORD;
};

VertexPositionTexture MyVertexShader(VertexPositionTexture input)
{
	VertexPositionTexture output;
	output.Position = mul(input.Position, mul(View, Projection));
	output.TextureCoordinate = input.TextureCoordinate;
	return output;
}

float4 MyPixelShader(float2 textureCoordinate : TEXCOORD) : COLOR
{
	return tex2D(mySampler, textureCoordinate);
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}

minMagFilter.jpg
xの字もなめらかになりました。

拍手[0回]

PR

かんたんXNA HLSL編 その6 マトリックス

このページは古いです
最新版はこちら

ここまではHLSLで面白い効果を出すと言っておきながら、
見た目は3Dではなく2Dでした。
頂点シェーダに流し込む頂点データが変換されずにそのまま返されていたからです。
この点で、Projectionプロパティでモデルを立体的に見せることのできるBasicEffectより劣っています。
この差を埋めることにしましょう。

頂点の座標データを変換するにはマトリックスを使います。
ちょうどBasicEffectのViewプロパティやProjectionプロパティのようにです。
グローバル変数としてマトリックスを宣言し、それをC#側から操作すればいいのです。
HLSL側でマトリックスを宣言するには、次のようにします:

[型][変換前のベクトルの次元数]x[変換後のベクトルの次元数] [変数名];

普通は4次元ベクトル→4次元ベクトルの変換なのでfloat4x4です。
(3DCGの話をしているのになぜ4次元が出てくるのか不思議に思えるかもしれませんが、これはマトリックスによる変換の柔軟性を大きくするためです。3次元ベクトルではなく4次元ベクトルの変換ということにしたほうが、よりいろんな種類の変換が出来るのです。そこで、位置ベクトルなんかは3次元でなくて4次元ベクトルとして扱います。その4次元ベクトルの一番最後の成分には位置の情報ではなく1が入っていますが(ジョークではありませんよ)、これは数学的な計算のつじつまを合わせるためで、特に意味はありません。かといって一番最後の成分に全く意味がないと言うわけでもなく、この値が1でないとマトリックスで変換したときの結果が変になってしまいます。)

float4x4 transform;

ただし、『プログラミングDirectX9グラフィックスパイプライン』なんかをみると、
4次元ベクトル→3次元ベクトルの変換を表すfloat4x3や
3次元ベクトル→3次元ベクトルの変換を表すfloat3x3も使われるようです。
きっとこれはそれほど柔軟性が必要ないからでしょう。(多分)

HLSL側でグローバル変数として宣言したマトリックスはたいていC#側で値がセットされるので
初期化についてはあんまり気にする必要はないでしょう。

このグローバル変数を使って頂点シェーダに引数として入ってくる位置ベクトルを変換していくわけですが、
それにはmul関数を使います。

mul ( vector, matrix )

vectorは変換前のベクトル、matrixは変換を表すマトリックスです。
戻り値は変換後のベクトルです。

mulという名前から想像できるように、これは掛け算を意味する関数です。
(※マトリックスによるベクトルデータの変換は掛け算として表されるのです。)
そのため、mul関数の引数は実はスカラーでもベクトルでも何でもかまいません

mul(2, 3);    // == 6
mul(2, float3(1, 0, 0));    // == float3(2, 0, 0)

もちろん、マトリックス同士のかけ算をすることも出来ます。
その場合は、2つのマトリックスの変換を同時に行うマトリックスを返します。

mul(view, projection);    // viewとprojectionの変換をいっぺんにやってしまうマトリックス。



MyGame.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;


public class MyGame : Game
{
    GraphicsDeviceManager graphics;
    ContentManager content;

    Effect effect;
    VertexPositionColor[] vertices = {
            new VertexPositionColor(new Vector3(0, 1, 0), Color.White),
            new VertexPositionColor(new Vector3(1, 0, 0), Color.Blue),
            new VertexPositionColor(new Vector3(-1, 0, 0), Color.Red)
        };


    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        content = new ContentManager(Services);
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = content.Load<Effect>("MyEffect");
            Matrix view = Matrix.CreateLookAt(
                new Vector3(2, 0, 3), 
                new Vector3(),
                new Vector3(0, 1, 0)
                );
            Matrix projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(90),
                (float)Window.ClientBounds.Width/Window.ClientBounds.Height,
                0.1f, 100
                );
            effect.Parameters["Transform"].SetValue(
                view * projection
                );

            graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
                graphics.GraphicsDevice,
                VertexPositionColor.VertexElements
                );
        }
    }

    protected override void UnloadGraphicsContent(bool unloadAllContent)
    {
        if (unloadAllContent) { content.Unload(); }
    }

    protected override void Draw(GameTime gameTime)
    {
        graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

        effect.Begin();

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

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
                );

            pass.End();
        }

        effect.End();
    }
}

MyEffect.fx
float4x4 Transform;

struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	VertexPositionColor output = input;
	output.Position = mul(input.Position, Transform);
	return output;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	return color;
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}

hlslTransformed.jpg
斜めから見た図です。
きちんと三角形が遠近法の効果でゆがんでいるのがわかると思います。
これでようやくBasicEffectに一歩近づきました。

BasicEffectと違ってViewやProjectionを分けていません。
この2つのマトリックスを一緒にしてTransformという名前のグローバル変数にしています。
C#側では別々に作ってはいますが、結局掛け合せてエフェクトにセットしています。
こういうのもアリと言うことです。

もちろん、ViewとProjectionを分けることも出来ます。

MyGame.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;


public class MyGame : Game
{
    GraphicsDeviceManager graphics;
    ContentManager content;

    Effect effect;
    VertexPositionColor[] vertices = {
            new VertexPositionColor(new Vector3(0, 1, 0), Color.White),
            new VertexPositionColor(new Vector3(1, 0, 0), Color.Blue),
            new VertexPositionColor(new Vector3(-1, 0, 0), Color.Red)
        };


    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        content = new ContentManager(Services);
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = content.Load<Effect>("MyEffect");
            Matrix view = Matrix.CreateLookAt(
                new Vector3(2, 0, 3), 
                new Vector3(),
                new Vector3(0, 1, 0)
                );
            Matrix projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(90),
                (float)Window.ClientBounds.Width/Window.ClientBounds.Height,
                0.1f, 100
                );
            effect.Parameters["View"].SetValue(view);
            effect.Parameters["Projection"].SetValue(projection);

            graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
                graphics.GraphicsDevice,
                VertexPositionColor.VertexElements
                );
        }
    }

    protected override void UnloadGraphicsContent(bool unloadAllContent)
    {
        if (unloadAllContent) { content.Unload(); }
    }

    protected override void Draw(GameTime gameTime)
    {
        graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

        effect.Begin();

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

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
                );

            pass.End();
        }

        effect.End();
    }
}

MyEffect.fx
float4x4 View;
float4x4 Projection;

struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	VertexPositionColor output = input;
	output.Position = mul(input.Position, mul(View, Projection));
	return output;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	return color;
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}

実行結果はさっきと同じです。
さっきと違うのは、マトリックスを2つに分けていることです。
このようにマトリックスはいくらでも増やすことが出来ます。
BasicEffectでは後ひとつ、Worldマトリックスがありました。

拍手[0回]


かんたんXNA HLSL編 その5 グローバル変数 

このページは古いです
最新版はこちら

今回のお題はグローバル変数です。
HLSLのグローバル変数はちょうどC#で言うプロパティのようなノリで使います
つまり、BasicEffectのViewやProjectionプロパティみたいな感じです。
実際、BasicEffectのプロパティの多くがHLSLのグローバル変数をラップしたもののようです。

(※エフェクトのグローバル変数はこんなコードで確かめることが出来ます。
         BasicEffect effect = new BasicEffect(graphics.GraphicsDevice, null);

            foreach (EffectParameter parameter in effect.Parameters)
            {
                System.Console.WriteLine(parameter.Name);
            }

結果はこうなりました:

BasicTexture
TextureSampler
FogEnabled
FogStart
FogEnd
FogColor
EyePosition
DiffuseColor
Alpha
EmissiveColor
SpecularColor
SpecularPower
AmbientLightColor
DirLight0Direction
DirLight0DiffuseColor
DirLight0SpecularColor
DirLight1Direction
DirLight1DiffuseColor
DirLight1SpecularColor
DirLight2Direction
DirLight2DiffuseColor
DirLight2SpecularColor
World
View
Projection
ShaderIndex
VSArray
PSArray

これはBasicEffectのエフェクトで使われているグローバル変数ですが、
ほとんどBasicEffectのプロパティと対応します。(全部ではありませんけどね))


概念としてはC#のプロパティやメンバ変数と同じと言うことです。
このグローバル変数を使うと、C#側からいろいろ操作できるので
エフェクトをより柔軟なものに出来るでしょう。




宣言の仕方はこうです:

スコープ データ型 変数名 : セマンティクス = 初期化する値;

このうち必須なのはデータ型と変数名だけです。
そのため、一番シンプルな例だとこうなります:

float4 DiffuseColor;

これだけでも外側から(C#のコードから)アクセスできます。
スコープを指定しなくてもデフォルトで外側からアクセスできるようになっているのです。
(これがC#とは違うところです。C#のメンバはpublicをつけないと外側からアクセスできませんからね)

C#側からアクセスするには、Microsoft.Xna.Framework.Graphics.EffectParameterクラスを使います。

public sealed class EffectParameter

このクラスはエフェクトのグローバル変数を表しており、
これ使って、グローバル変数をsetしたりgetしたりします。
※なお、msdnによると、getもsetも遅いのであんまり頻繁に使うのは避けたほうがいいそうです。ちょっぴりショックです・・・

読み込むにはEffectParameter.GetValue*****という名前のメソッドを使います。
色々あるのはC#からだとグローバル変数の型がわからないからでしょうね。

public bool GetValueBoolean ()
public bool[] GetValueBooleanArray ( int count )       //count は配列の要素数

public int GetValueInt32 ()
public int[] GetValueInt32Array ( int count )        //countは配列の要素数

public Matrix GetValueMatrix ()
public Matrix[] GetValueMatrixArray ( int count )    //countは配列の要素数

public Matrix GetValueMatrixTranspose ()    //転置行列(行と列が入れ替わったマトリックス)
public Matrix[] GetValueMatrixTransposeArray ( int count )    //countは(以下略

public Quaternion GetValueQuaternion ()
public Quaternion[] GetValueQuaternionArray ( int count ) 

public float GetValueSingle ()
public float[] GetValueSingleArray ( int count )

public string GetValueString ()

public Texture2D GetValueTexture2D ()

public Texture3D GetValueTexture3D ()

public TextureCube GetValueTextureCube ()

public Vector2 GetValueVector2 ()
public Vector2[] GetValueVector2Array ( int count )

public Vector3 GetValueVector3 ()
public Vector3[] GetValueVector3Array ( int count ) 

public Vector4 GetValueVector4 ()
public Vector4[] GetValueVector4Array ( int count )



書き込みにはほとんどの場合、EffectParameter.SetValueメソッドを使います。
GetValueの場合と違って、引数の型によってオーバーロードしてるので
名前が違うなんてことはありません。

public void SetValue ( bool value )
public void SetValue ( bool[] value )

public void SetValue ( int value )
public void SetValue ( int[] value )

public void SetValue ( Matrix value )
public void SetValue ( Matrix[] value )

public void SetValue ( Quaternion value )
public void SetValue ( Quaternion[] value )

public void SetValue ( float value )
public void SetValue ( float[] value )

public void SetValue ( string value )

public void SetValue ( Texture value )

public void SetValue ( Vector2 value )
public void SetValue ( Vector2[] value )

public void SetValue ( Vector3 value )
public void SetValue ( Vector3[] value )

public void SetValue ( Vector4 value )
public void SetValue ( Vector4[] value )



さきほど「ほとんどの場合」といいましたが、それは例外があるからで、
転置行列をセットする場合です。
普通のマトリックスをセットする場合と引数の型が同じなので
オーバーロードできなくなり、別の名前にしたのでしょう。
(まぁあんまり使いそうにないのでこの解説はスルーしてもよかったんですけど。)

public void SetValueTranspose ( Matrix value )
public void SetValueTranspose ( Matrix[] value )



さて、肝心のEffectParameterクラスのインスタンスを得る方法ですが、
それにはEffect.Parametersプロパティを使います。

public EffectParameterCollection Parameters { get; }

戻り値はHLSLで書いたエフェクトファイルのグローバル変数の一覧を表す、
Microsoft.Xna.Framework.Graphics.EffectParameterCollectionクラスの
インスタンスです。
こいつにEffectParameterのインスタンスが入っています。

public sealed class EffectParameterCollection : IEnumerable<EffectParameter>

目的のグローバル変数を表すEffectParameterを得るには、インデクサを使います。

public EffectParameter this [ string name ] { get; }

nameはもちろんグローバル変数の名前です。


MyGame.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;


public class MyGame : Game
{
    GraphicsDeviceManager graphics;
    ContentManager content;

    Effect effect;
    VertexPositionColor[] vertices = {
            new VertexPositionColor(new Vector3(0, 1, 0), Color.White),
            new VertexPositionColor(new Vector3(1, 0, 0), Color.Blue),
            new VertexPositionColor(new Vector3(-1, 0, 0), Color.Red)
        };


    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        content = new ContentManager(Services);
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = content.Load<Effect>("MyEffect");

            graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
                graphics.GraphicsDevice,
                VertexPositionColor.VertexElements
                );
        }
    }

    protected override void UnloadGraphicsContent(bool unloadAllContent)
    {
        if (unloadAllContent) { content.Unload(); }
    }

    protected override void Update(GameTime gameTime)
    {
        double angle = gameTime.TotalGameTime.TotalSeconds;
        effect.Parameters["offset"].SetValue(
            new Vector3((float)System.Math.Cos(angle), (float)System.Math.Sin(angle), 0)
            );
    }

    protected override void Draw(GameTime gameTime)
    {
        graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

        effect.Begin();

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

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
                );

            pass.End();
        }

        effect.End();
    }
}


MyEffect.fx
float3 offset;

struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	input.Position.xyz += offset;
	return input;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	return color;
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}


このサンプルでは三角形をぐるぐる回しています。

rotationByEffectParameter1.jpg
rotationByEffectParameter2.jpg

rotationByEffectParameter3.jpg
rotationByEffectParameter4.jpg
rotationByEffectParameter5.jpg

回転の中心はウィンドウの中心です。
エフェクトファイルで三角形のオフセットを表すグローバル変数を宣言して、
それをC#側から連続して変更していき、回転させているのです。


スコープ

HLSLのグローバル変数の前には記憶域クラス修飾子であるスコープをつけることが出来ます。
C#で言うpublicとかprivateのことです。
ただし、HLSLではC言語風の名前になっていて少し戸惑うかもしれません。


static

C#でいうprivateです。
これを付けられたグローバル変数はエフェクトの外から「見えなく」なります。
アプリケーションからEffectParameterを使ってgetしたりsetしたり出来ません。

C#でstaticと言えば全てのインスタンスでその変数が共有されることを意味しますが、
HLSLでこれに対応するのは"shared"です。

(※とはいえまったく関係ないということはなく、
これを関数などのローカルスコープ内の変数につけた場合、
C#のstaticと似た意味を持つようになります。
つまり、全ての呼び出しでその変数が共有されます。

float incrementA()
{
        static float a;
        a++;
        return a;
}

float4 MyPixelShader() : COLOR
{
        incrementA();    //この時点でaは1。この行をコメントアウトすると色は灰色になります。
        return incrementA() / 2;    //この時aは2。結局白を返します。
}

まぁピクセルシェーダ間で共有することは出来ないようですが。



extern

C#でいうpublicです。
これを付けられたグローバル変数はエフェクトの外からgetしたりsetしたり出来ます。
これはスコープのデフォルトで、何もスコープが付けられていないグローバル変数はexternになります。


shared

C#でいうstaticです。
これを付けられたグローバル変数はエフェクト間で共有されます。


拍手[0回]


かんたんXNA HLSL編 その4 フロー制御文

このページは古いです
最新版はこちら

HLSLにも他の多くのC言語ベースの言語と同じように、
フロー制御文があります。

つまりifやfor, while, doとかのことです。
あらためて説明の必要はないとは思いますが、一応見ておきましょう。
(なんせ人間原理的な意味でこの記事を見ている方はフロー制御をほかの言語で知っているはずですからね)

拍手[0回]


かんたんXNA HLSL編 その3 スカラーとベクトル型

このページは古いです
最新版はこちら

前回の終わりのほうでHLSLを直接いじると
おもしろいいことができるというようなことを書いた気がしますが、
それにはまず変数を操作できないといけないことに気がつきました・・・・・・。
(くそっ、何で気がつかなかったんだ!)

そこでスカラー型とベクトル型の変数に関する簡単な説明をしながら
そのおもしろい効果を見ていこうと思います。

拍手[1回]