忍者ブログ

Memeplexes

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

かんたん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回]

PR