忍者ブログ

Memeplexes

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

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。


かんたんXNA4.0 HLSL編 その5 グローバル変数


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

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

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

結果はこうなりました:

Texture
DiffuseColor
EmissiveColor
SpecularColor
SpecularPower
DirLight0Direction
DirLight0DiffuseColor
DirLight0SpecularColor
DirLight1Direction
DirLight1DiffuseColor
DirLight1SpecularColor
DirLight2Direction
DirLight2DiffuseColor
DirLight2SpecularColor
EyePosition
FogColor
FogVector
World
WorldInverseTranspose
WorldViewProj
ShaderIndex

これは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;

class MyGame : Game
{
    GraphicsDeviceManager graphics;

    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);
    }

    protected override void LoadContent()
    {
        effect = Content.Load<Effect>("Content/MyEffect");
    }

    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)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

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

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です。
これを付けられたグローバル変数はエフェクト間で共有されます。


拍手[1回]

PR

かんたんXNA4.0 HLSL編 その4 フロー制御文


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

つまりifやfor, while, doとかのことです。

まず、msdnを見ると、
if, for, while, switch, do, break, continueと、
一通りそろっているようです。(以下msdnより)

[Attribute] if ( Conditional )
{
    Statement Block
}

[Attribute] for ( Initializer; Conditional; Iterator )
{
    Statement Block;
}

[Attribute] while ( Conditional )
{
    Statement Block;
}

[Attribute] switch ( Selector
{
    case 0 :
        { Statement Block; }
    break;
    case 1 :
        { Statement Block; }
    break;
    case n :
        { Statement Block; }
    break;
    default :
        { Statement Block; }
    break;
}

do 
{
    Statement Block;
} while( Conditional )

break;

continue;


おなじみの文です。
・・・・・・と思ったら[Attribute]とかいう変なのがありますね・・・。
どうやらこれはコンパイルの方法を指定する、C#の一部の属性のようなものらしいです。
でも省略可能なので普通のC言語系のフロー制御と同じように使うことができます。


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

class MyGame : Game
{
    GraphicsDeviceManager graphics;

    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);
    }

    protected override void LoadContent()
    {
        effect = Content.Load<Effect>("Content/MyEffect");
    }

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

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

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

MyEffect.fx
struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	return input;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	if(color.r % 0.1f < 0.05f)
		return float4(1,1,1,1);
	else
		return color;
}

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


stripeTriangle.jpg
ほどよくしましまになっているのがわかると思います。
ピクセルシェーダに与えられる色の赤いチャンネルをもとにして
条件分岐を行ったのです。
こんなことをしても実際には何の役にも立たないかもしれませんが、
HLSLの威力がよくわかる例ではないでしょうか。
少なくともこれはBasicEffectでは出来ませんからね。







拍手[1回]


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


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

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


スカラーとベクトル型

HLSLで使えるスカラー型(普通の数値を表す型)は
以下のようなものがあります:

HLSLのスカラー型
型名 説明
bool 真偽を表します。trueかfalseです。
int 32bitの符号付き整数です。
half 16bitの浮動小数点数です。
float 32bitの浮動小数点数です。
double 64bitの浮動小数点数です。


そして、さらにそれをまとめたベクトル型の変数を作ることができます。
HLSLの目的からして、ベクトルはよく使うでしょうからね。

[成分の型][ベクトルの成分の数] [変数名];

具体的にはこんな感じです。

float4 color;

float4は4つの成分を持つベクトルで、
位置や色を表すのに使われます。
(「今やっているのは3DCGなのに、どうして位置を表すのに4次元のベクトルを使うんだろう?」と思われるかもしれません。これはおそらく頂点データをマトリックスで変換するときの計算が関係しています。より柔軟なデータの変換をするためには、3次元のベクトルとして使うよりも4次元のベクトルとして扱ったほうが都合がいいのです。拡大なんかは3次元のままでもいいのですが、たとえば平行移動するときには3次元ではなく4次元ベクトルとして計算しなくてはなりません。つまり計算上の都合です。シェーダの引数として使われる空間ベクトルの4つ目の成分wには変換の計算を上手くやるため1が入っています。この値は計算に使うため変更してはいけません。たとえばwに2をセットすると、平行移動の距離も2倍になって、結果がなんだか変なことになってしまいます。)

この4つの成分にアクセスするためには、
x, y, z, w, (位置としてアクセス)または
r, g, b, a(色としてアクセス)を使います。

位置としても色としてもアクセスできるなんて
ちょっとカプセル化が破壊されているように思えるかもしれません。
でもまぁマーチン・ファウラーさんも
少しくらいカプセル化が壊れてても大丈夫
みたいなことを言ってたのでいいんじゃないでしょうか(たぶん)。


ちなみに初期化は次のように行います:

//new が無いことに注意!!C/C++風です!
float4 variable = float4(0, 1, 2, 3); 

あるいはこうです:

float4 variable = {0, 1, 2, 3};



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

class MyGame : Game
{
    GraphicsDeviceManager graphics;

    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);
    }

    protected override void LoadContent()
    {
        effect = Content.Load<Effect>("Content/MyEffect");
    }

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

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

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

MyEffect.fx

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

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	return input;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	color.r = 0;
	return color;
}

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

redMasked.jpg

ここではピクセルシェーダで各ピクセルの赤い成分を0にしています。
そのため三角形全体から赤っぽさが消えています。

※参考までに、赤っぽさを消す前はこんなかんじでした:
customEffectColor.jpg参考:赤っぽさを消す前

各ピクセルの赤い成分を消すために、ピクセルシェーダで
color.r = 0;
としています。
これは実は
color.x = 0;
としても同じです。変な感じはしますけどね。



複数の成分を扱う

ここまではC#的な観点からもまだ理解しやすいものだったと思います。
つまりメンバ変数やプロパティのような感じだからです。

しかしここから、見慣れない不穏な感じがでてきます。

こんな風に、複数の成分にアクセスできるのです。

color.rb = 0;

//color.r = 0; 
//color.b = 0; 
//と同じ


ふつうのC言語系の言語になれている人は絶句するのではないでしょうか!
これはrとbの両方を0にしているのですが、
書き方としてはまるでrbというメンバがあるかのようです。

もちろん実際はそんなことはなくて、書き方としてこういうのがあるだけです。
順番を逆、color.br = 0;としても同じことです。

そしてここでは2つの成分だけを同時に扱っていますが、
たとえば3つの成分でcolor.rgb = 0;なんてのもアリです。
struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	return input;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	color.rb = 0;
	return color;
}

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


rbMasked.jpg
赤と青の成分を消したため、緑の成分だけが残っています。


また、次のように書くことで成分を入れ替えることができます:

color.rb = color.br;
struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	return input;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	color.rb = color.br;
	return color;
}

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

rbExchanged.jpg

一見何も変わっていないように見えますが注意ぶかく見てください。
赤と青が入れ替わっています。(左右反転したようにも見えますが、変わっているのは色です)
ここではrとbだけ入れ替えていますが、
r, g, bの3つでローテーションのように入れ替えることも可能です。



スカラーとベクトルの計算

スカラーとベクトルを一緒に計算することもできます。
その場合、ベクトルの各成分がスカラーと計算されます。

たとえば、

color += 0.3f;

は次と同じです:

color.r += 0.3f;
color.g += 0.3f;
color.b += 0.3f;
color.a += 0.3f;


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

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	return input;
}

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

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


colorReversed.jpg
ここでは色を反転しています。
colorの各成分が、1から引かれています。
{1, 1, 1, 1}が白なので、そこからもとの色を引くと
色がひっくり返るのです。


ベクトルとベクトルの計算

ベクトルとベクトルの計算は、お互いの各成分を計算します。
足し算は数学のベクトルの足し算と同じですが、
かけ算は内積や外積とは違うので注意してください。

足し算:
float4 position = float4(0, 1, 2, 3);
float4 offset = float4(1, 2, 3, 4);
//position + offset は {1, 3, 5, 7}
// {0 + 1, 1 + 2, 2 + 3, 3 + 4}


かけ算:
float4 materialColor = float4(0, 1, 2, 3);
float4 lightColor = float4(1, 2, 3, 4);
// materialColor * lightColor は {0, 2, 6, 12}
// {0 * 1, 1 * 2, 2 * 3, 3 * 4}


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

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	return input;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	float4 lightColor = float4(1, 0, 0.5f, 1);
	return lightColor * color;
}

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


pinkLight.jpg

今度の三角形はピンクのライトが当たったようになっています。

ピクセルシェーダによって返される色は、

{1 * color.r, 0 * color.g, 0.5f * color.b, 1 * color.a}

です。

これは内積や外積とは違うことに注意してください。
詳しくは後で述べますが、内積はdot関数、外積はcross関数を使うのです。

拍手[1回]


かんたんXNA4.0 HLSL編 その2 構造体


前回表示したポリゴンに色はついていませんでした。
使った頂点データには色がついていたにもかかわらず、
実際に描画された三角形のすべてのピクセルの色が白だったのです。

これは前回のエフェクトファイルが色を扱っていないことが原因です。
頂点シェーダでは、位置情報だけではなく色の情報も扱わなければなりませんし、
ピクセルシェーダだって引数として色をとらなければなりません。

位置だけでなく色の情報も扱うためには、その二つをまとめるための
構造体を作る必要があります。
(複数の戻り値を返すことはできませんからね)

構造体のつくり方はC/C++のものとほとんど同じです。
もっともセマンティクス(用途)をつける必要はありますが・・・。

struct 構造体名
{
        型 変数名1 : セマンティクス;
        型 変数名2 : セマンティクス;
...
};


具体的に書くとしたらこんな感じでしょうか。

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

C#やJavaになれているひとは最後のセミコロン(;)を忘れないでください。
また、デフォルトでpublicのようにアクセスできることも意外かもしれません。


MyEffect.fx
struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	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();
	}
}

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;

    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);
    }

    protected override void LoadContent()
    {
        effect = Content.Load<Effect>("Content/MyEffect");
    }

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

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

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


 customEffectColor.jpg

今度は色がついています!

頂点シェーダで色を返すようになったので、
ピクセルシェーダの引数として色をとり、
それを描画に反映できるようになったのです。

でもまぁ、こんなんじゃBasicEffectだってできますし、
HLSLだなんてわけのわからないものを使うくらいなら
BasicEffectを使ったほうがずっとましだといえるでしょう。
しかし、HLSLをつかうと、いろいろとおもしろいことができるのです。
次回はそのことについて書くことにします。



拍手[2回]


かんたんXNA4.0 HLSL編 その1 エフェクトファイル

HLSLとは?

High Level Shader Language (HLSL)とは頂点データを
どのように処理して描画するかを表現するための特別な言語です。
この言語で書いたテキストファイルをコンパイルして、
C#でEffectクラスからコントロールするのです。
(BasicEffectのかわりです。というか、BasicEffect自体Effectから派生しています)

MSDNの記事には、これは上位レベルの言語で、
さらにC言語スタイルがベースになっててプログラマにとってなじみやすい
というような趣旨のことが書いてありますが、
しょうじきなところ真実はまったくの逆であるような気がします。
C言語ライクなのはたしかですが、C#やjavaになれているプログラマからすれば
かなり低レベルでしかもとっつきにくいように思えます。
(まぁでもHLSL以前では、アセンブリ言語をつかわなくてはならなかったため、
これでもまだマシになったほうなのです。)


ではなぜそんな言語を使わなければならないのでしょうか?
それはこの言語を使うと、C#だけを使うときより、
描画のやりかたをより細かくコントロールできるからです。
C#でBasicEffectを使うときよりより高度な描画ができるのです。
(そう、HLSLでやるのはBasicEffectとおなじです。
ただし、もう少し、細かい設定ができるのです。)


ここで少し復習です。
これまでかんたんXNAでは、頂点データをどのように描画するかを
BasicEffectクラスでコントロールしていました。
BasicEffectに頂点データをつっこんで、いろいろ処理させて、
ピクセルごとの色を計算し、ディスプレイに表示していたのです。
ところてんのかたまりをあの四角い器具に押しこんで
反対側から完成品の細長いところてんをニューとひねりだすようなかんじです。
データを突っ込み変形させていたのです。

つまり、DrawUserPrimitivesの引数に入れられた頂点データの
絶対座標をカメラからの相対座標に変換し、
それをさらに遠近法を考慮してデータ全体をゆがめ、
ライトの方向を考えて一部を暗くしたりし、
また質感をあらわすデータから色を微妙に調整し、
クスチャのデータなんかをまぜてピクセルの色を決めていたのです。
これはようするに、頂点データをピクセルごとの色に変換する
コンバータの一種と考えてもいいでしょう。

これでも十分に面白いゲームは作れるでしょう。
ただ、もう少し複雑なエフェクトを使いたいというときもあるのではないでしょうか。
たとえば画像をぼかしたりトゥーンレンダリングしたりと
ピクセルごとの色を綿密に制御したいような場合です。

そこでBasicEffectではなく、より細かいことのできるHLSLを使います。
HLSLで頂点データからピクセルの色への変換の仕方を記述し、
そのテキストファイルをContentManagerで読み込み、
Effectクラスのオブジェクトを作るのです。
その後はBasicEffectと同じようなノリで描画を行います。

HLSLの骨組み

HLSLでエフェクトファイルを書くにあたって必ず必要なのが、
テクニックパスの設定です。
このテクニックとパスというのは、BasicEffectで出てきた
CurrentTechniqueプロパティやEffectPassクラスが意味するものと同じです。
テクニックはパスを集めたもので、パスは描画の方法の設定を表すものです。
(テクニックもパスも複数書くことができますが、
ここでは話を簡単にするため1つだけの場合を見ます)


technique テクニック名
{
        pass パス名
        {
        }
}


このパスの中に書くのは、ちょうどコンストラクタや、
クラス内での変数の初期化のようなもので、
つまり設定の初期化です。
このHLSLでどのような描画をするかの設定を行うのです。

では具体的にどのような設定が必要なのでしょうか?
何とか動くようにするためには頂点シェーダピクセルシェーダの2つを
セットする必要があります。

HLSLで記述するのは頂点データから各ピクセルの色を決定する方法です。
じつはこれは一度に行われるのではなく、二つのステップにわけられます。

ひとつめはこのエフェクトに流し込む頂点データをべつの頂点データに変換します。
ここでは、たとえば絶対座標をカメラからの相対座標にしたり、
それぞれの頂点に遠近法の効果を適用して位置のデータを変更したりというふうに、
頂点を頂点に変換します。
入ってくるデータは頂点データで、出て行くデータも頂点データです。
これを頂点シェーダ(VertexShader)といいます。関数の形で表されます。

もうひとつは、各ピクセルの情報からそのピクセルの
色を決めるステップです。
この変換では引数はそのピクセルのテクスチャ座標だったり、乱反射の色だったり、
あるいは別の何かだったりします。
戻り値はそのピクセルの描画される色です。
これをピクセルシェーダ(PixelShader)といいます。関数の形で表されます。

この二つのセットの仕方はこんな感じです。

technique テクニック名
{
        pass パス名
        {
                VertexShader = compile vs_2_0 頂点シェーダ名();
                PixelShader = compile ps_2_0 ピクセルシェーダ名();
        }
}


気分的にはC#のデリゲートのセットと似た感じです。
vs_2_0とかps_2_0とか言うのがありますが、これは機能のバージョンをあらわします。
たとえば頂点シェーダのバージョンが3.0ならvs_3_0です。
(vs : VertexShader, ps : PixelShader)

MyEffect.fx
float4 MyVertexShader(float4 position : POSITION) : POSITION
{
	return position;
}

float4 MyPixelShader() : COLOR
{
	return float4(1, 1, 1, 1);
}

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

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;

    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);
    }

    protected override void LoadContent()
    {
        effect = Content.Load<Effect>("Content/MyEffect");
    }

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

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

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

customEffectWithoutColor.jpg
これがたぶんHLSLを使った最も簡単なプログラムのひとつなのではないでしょうか。
ここでは三角形を表示していますが、頂点データに指定した情報と違って、
色は真っ白になっています。(ほんとうは白、青、赤になるはず)
HLSLで色は無視して全部白にしてしまうようにプログラムしたからです。

MyEffect.fxのピクセルシェーダ、MyPixelShader関数を見てください。
これはすべてのピクセルの色を白にするということを意味しています。

float4 MyPixelShader() : COLOR
{
        return float4(1, 1, 1, 1);
}

"return float4(1, 1, 1, 1)"というのがそのピクセルの色です。
もしこれを"return float4(1, 0, 0, 1)"にすればすべてのピクセルは赤くなり
赤い三角形が表示されるでしょう。

ピクセルシェーダというのは普通引数にそのピクセルの情報を持っていてそれを元にピクセルの色をはじき出すのですが、
ここでは話を簡単にするために引数は省いています。
ほんとうなら引数としてテクスチャ座標やディフューズ色のデータが入ってきます。

ところで、関数名の横についている" : COLOR"というのが気になるかもしれません。
これは何を意味するかというと、この関数の戻り値が何なのかということです。
戻り値のデータの使われる目的、データ型のようなものです。
(しかしややこしいことに、データ型はちゃんと別にあるのです。float4がそれです。)
あるいは、ここにくるのはVertexElementUsage列挙型の
やつと同じようなものといえばわかりやすいかもしれません。
これは「セマンティクス」というもので、実は戻り値だけではなく、
頂点シェーダやピクセルシェーダの引数にもくっつきます。
これはグラフィックスデバイスがデータを識別するために使っているらしく、
これをつけておけば、たとえば引数の順番を入れ替えても平気です。
つけ方はこんな感じ:

データ型 変数名 : セマンティクス

つぎに頂点シェーダ、MyVertexShader関数を見てください。

float4 MyVertexShader(float4 position : POSITION) : POSITION
{
        return position;
}

これは頂点から頂点への変換を表す関数で、
ふつうなら位置の情報に遠近法を適用して結果を返したりするのですが、
ここでは話を簡単にするため位置データをそのまま返しています。

そのため描画した結果は3Dとは言いがたいものになっています。

セマンティクスは引数も戻り値も"POSITION"です。
つまり、位置のデータを扱っているということを意味しています。
ほんとうなら引数には色のデータも来るはずでした。
VertexPositionColorを頂点として使っているわけですからね。
しかしこのサンプルでは話を簡単にするために省きました。
(省くこともできるのです!)

そして戻り値についても同じです。
ほんとうなら戻り値は構造体になっていて、色のデータも含んでいるはずでした。
しかし構造体の話をするとややこしくなるため、シンプルに位置のデータだけにしたのです。

エフェクトファイルについての説明はこのくらいでいいでしょう。
このようにしてHLSLで作ったエフェクトファイルをC#からContentManagerで読み込み、
Effectクラスを使って操作するのです。
やりかたはBasicEffectとおなじです。

拍手[4回]