WisdomSoft - for your serial experiences.

エフェクト

頂点配列を描画するには、頂点で結ばれたポリゴンをピクセル化するエフェクトが必要になります。ここでは XNA Framework が用意する組み込みエフェクトである BasicEffect クラスを用いて三角形のプリミティブを描画します。

頂点の入力から描画までの仕組み

頂点を作成しただけでは、画面に 3D グラフィックスを描画することはできません。3D グラフィックスの描画には、デバイスに入力した頂点データに対して必要な変換処理を行い、平面であるディスプレイに表示するためのピクセルを生成しなければなりません。そのため、プリミティブをピクセル化して画面に描画するための処理が必要になります。これを行うのがエフェクトです。 

エフェクトは Microsoft.Xna.Framework.Graphics.Effect クラスで表されます。

Microsoft.Xna.Framework.Graphics.Effect クラス
public class Effect : GraphicsResource

プリミティブを描画するにはエフェクトが必要になりますが Effect クラスのインスタンス化は簡単ではありません。

エフェクトは、上位レベルシェーダ言語 HLSL(High Level Shading Language)と呼ばれる特別な言語を用いて作ります。GPU は、入力された頂点データを描画するためにパイプラインと呼ばれる処理工程を順に実行します。このとき、GPU に入力された頂点データを処理し、ピクセルの色を生成する処理を実行する命令をシェーダと呼びます。

図1 頂点の入力から描画までの流れ
図1 頂点の入力から描画までの流れ

図1はアプリケーションから入力された頂点が GPU によって処理される過程を大雑把にまとめたものです。入力された頂点は、先頭から順に処理され、必要な変換や三角形の表裏などの判定が行われます。ディスプレイやピクセルの集合なので、頂点の計算が終了するとポリゴンをピクセルの集合に変換します。これをラスタライズと呼びます。最後に、生成されたピクセルの合成やアンチエイリアスといった変換処理が行われます。

シェーダは、すべて GPU の中で処理されるもので、CPU とは独立しています。古くは、パイプラインで行われる処理がハードウェアや API によって固定されていましたが、現在では HLSL のような専用の言語を使って GPU の処理そのものをプログラムすることが可能になっているのです。プログラム可能な、という意味からプログラマブルシェーダとも呼ばれます。

シェーダは、頂点シェーダピクセルシェーダの 2 種類に分けられます。最初に、入力された頂点に対して頂点シェーダが実行され、頂点の変換を行います。その後、画面に描画するプリミティブのピクセルを、ピクセルシェーダによって生成するという工程になります。図1における頂点処理とピクセル処理の最初にシェーダが実行されます。エフェクトは、これらシェーダをまとめたものです。

昔、一般的なアプリケーション開発でアセンブリ言語が使われていたように、シェーダも最初は低水準なアセンブリ言語を使って描かれていました。しかし、シェーダの機能が複雑になるにつれ、低水準なアセンブリ言語では生産性が低く、開発が難しくなってきました。そこで、現在ではシェーダ専用の高水準言語が使われています。XNA Framework や DirectX では、Microsoft が開発したシェーダ言語 HLSL を使ってシェーダを記述し、エフェクトとして取得できます。

HLSL の仕組みは、一般的なプログラミング言語と同じです。エフェクトファイルと呼ばれる fx という拡張子のテキストファイルに HLSL で定められた構文に従ってプログラムを書き、コンパイルされることで GPU 用のコードが生成される仕組みです。

プロファイル

シェーダを実行するには、当然 GPU がシェーダに対応していなければなりません。実行可能なシェーダにも、GPU の世代によっていくつかのバージョンに分かれており、これを HLSL シェーダモデルと呼びます。XNA Framework には対象のプラットフォームの性能に応じてプロファイルを選択します。

XNA Framework 4.0 以降のゲームを動かすには、最低でもシェーダモデル 2.0 以降のバージョンと DirectX 9.0c をサポートしているグラフィックスカードを必須とし、Windows PC 上ではシェーダモデル 3.0 以降を推奨しています。Xbox 360 は、シェーダモデル 3.0 に対応しています。

プロファイルの選択は XNA Game Studio のプロジェクトを右クリックして表示されるメニューから「プロパティ」項目を選択し、表示された「プロパティ」ウィンドウにある「XNA Game Studio」タブを表示します。

図2 プロジェクトのプロパティ
図2 プロジェクトのプロパティ
図3 ゲームプロファイルの設定
図3 ゲームプロファイルの設定

上部の「ゲーム プロファイル」のラジオボタンから「Reach」または「HiDef」のいずれかのプロファイルを選択します。Reach プロファイルで開発されたゲームは Windows Phone 7 でも動作させることができ、Windows Phone 7 ゲームの開発が目的の場合は Reach のみが唯一の選択です。Windows PC や Xbox 360 用のゲームの場合は Reach に加えて、より表現力の高い HiDef プロファイルを選択できます。

Reach プロファイルと HiDef プロファイルの詳細は MSDN の「プロファイルとは」をご覧ください。本稿のサンプルは、特にプロファイルの明記がない限り Reach でもビルドできます。

基本エフェクトの生成

最初からシェーダプログラムを書くことを 3D プログラミングの初学者に求めるのは難しいでしょう。幸い、HLSL による独自のエフェクトを用意しなくても XNA Framework が基本的なシェーダ機能を提供する Microsoft.Xna.Framework.Graphics.BasicEffect クラスを用意しています。HLSL の詳細は後述するとして、この場では BasicEffect クラスを使ってプリミティブを描画しましょう。

Microsoft.Xna.Framework.Graphics.BasicEffect クラス
public class BasicEffect : Effect, IEffectMatrices, IEffectLights, IEffectFog

このクラスは Effect クラスを継承し、プリミティブの描画に必要なシェーダ機能を備えています。HLSL を書かなくても、プリミティブの描画テストや、独自のエフェクトを必要としない 3D ゲームなどに応用できます。

BasicEffect クラスのコンストラクタには、エフェクトを使用するグラフィックスデバイスを指定します。

BasicEffect クラスのコンストラクタ
public BasicEffect (GraphicsDevice device)

device パラメータに、このエフェクトを用いてプリミティブを描画する GraphicsDevice オブジェクトを指定します。

VertexPositionColor 構造体を頂点として描画する場合、BasicEffect クラスの VertexColorEnabled プロパティを true に設定することで、頂点の色を有効にできます。このプロパティを true にしなければ、頂点の色が使われないので注意してください。頂点の色が使われない場合、プリミティブは白で塗りつぶされます。

BasicEffect クラス VertexColorEnabled プロパティ
public bool VertexColorEnabled { get; set; }

このプロパティの値が true の場合は、頂点の色を使用することを表します。デフォルトは false に設定されています。

LoadContent() メソッドなどで、BasicEffect クラスのインスタンスを生成し VertexColorEnabled プロパティを設定します。

effect = new BasicEffect(GraphicsDevice);
effect.VertexColorEnabled = true;

これでエフェクトの準備は整いました。

テクニック

テクニックは、頂点シェーダとピクセルシェーダを含むパスと呼ばれるオブジェクトを格納するコレクションとして機能します。エフェクトファイルに書かれたシェーダは、パスによって使用するべき頂点シェーダとピクセルシェーダが選択します。プリミティブを正しく描画するには、テクニックに含まれているパスを選択しなければなりません。

エフェクトが持つテクニックを取得するには Techniques プロパティを使います。

Effect クラス Techniques プロパティ
public EffectTechniqueCollection Techniques { get; }

Techniques プロパティは、エフェクトが保有するテクニックを管理する Microsoft.Xna.Framework.Graphics.EffectTechniqueCollection クラスのオブジェクトを返します。このクラスは、IEnumerable インターフェイスを実装する任意の数のテクニックを管理するコレクションとして機能します。

Microsoft.Xna.Framework.Graphics.EffectTechniqueCollection クラス
public sealed class EffectTechniqueCollection : IEnumerable<EffectTechnique>

EffectTechniqueCollection クラスの利用は、基本的にリストと同じです。テクニックの数は Count プロパティから取得できます。

EffectTechniqueCollection クラス Count プロパティ
public int Count { get; }

コレクションが保有するテクニックは、インデクサから取得できます。テクニックには名前を付けることができるため、整数によるインデックスの他に名前からテクニックを識別することも可能です。

EffectTechniqueCollection クラスのインデクサ
public EffectTechnique this [int index] { get; }
public EffectTechnique this [string name] { get; }

index パラメータには、取得するテクニックの 0 から始まる番号を指定します。指定できる番号の最大値は Count プロパティの値を参考にしてください。name パラメータには、取得するテクニックの名前を指定します。取得するべきテクニックの名前が明確である場合は、文字列からテクニックを取得した方が簡単です。

これらのインデクサは、テクニックを表す Microsoft.Xna.Framework.Graphics.EffectTechnique クラスのオブジェクトを返します。

Microsoft.Xna.Framework.Graphics.EffectTechnique クラス
public sealed class EffectTechnique

前述したように、テクニックは名前を持ちます。テクニックの名前は Name プロパティから取得できます。

EffectTechnique クラス Name プロパティ
public string Name { get; }

これらの機能を使って、エフェクトから任意のテクニックを検索して取得できます。ただし、BasicEffect クラスであれば 1 つのテクニックに 1 つのパスしか持っていません。HLSL を記述したエフェクトファイルから作られた独自のエフェクトであれば、任意の数のテクニックを指定することができ、それぞれのテクニックに任意の名前を命名できます。

Effect クラスは、現在のテクニックを提供する CurrentTechnique プロパティを公開しています。アプリケーションは使用するテクニックを、このプロパティに設定しなければなりません。テクニックが 1 つのエフェクトであれば、デフォルトで対象のテクニックが設定されているため BasicEffect では意識する必要はありません。

Effect クラス CurrentTechnique プロパティ
public EffectTechnique CurrentTechnique { get; set; }

このプロパティは、現在のテクニックを表す EffectTechnique オブジェクトを返します。Effect クラスの Begin() メソッドは、このプロパティに設定されている現在のテクニックを開始するという意味を持ちます。テクニックが開始されると、現在のテクニックにパスを設定しなければなりません。

パス

テクニックには、1 つ以上のパスが含まれています。テクニックからパスを取得するには Passes プロパティを使います。

EffectTechnique クラス Passes プロパティ
public EffectPassCollection Passes { get; }

Passes プロパティは、任意の数のパスを管理する Microsoft.Xna.Framework.Graphics.EffectPassCollection クラスのオブジェクトを返します。 EffectTechniqueCollection クラスと同様に、IEnumerable インターフェイスを実装するパスのコレクションとして機能します。

Microsoft.Xna.Framework.Graphics.EffectPassCollection クラス
public sealed class EffectPassCollection : IEnumerable<EffectPass>

保有しているパスの数は Count プロパティから取得します。

EffectPassCollection クラス Count プロパティ
public int Count { get; }

コレクションに含まれているパスは、インデクサから取得できます。パスも、テクニックと同じように名前で識別することができるため、番号と名前のいずれかで取得できます。

EffectPassCollection クラスのインデクサ
public EffectPass this [int index] { get; }
public EffectPass this [string name] { get; }

index パラメータには、取得するパスの 0 から始まる番号を指定します。name パラメータには、取得するパスの名前を指定します。これらのインデクサは、パスを表す Microsoft.Xna.Framework.Graphics.EffectPass クラスのオブジェクトを返します。

Microsoft.Xna.Framework.Graphics.EffectPass クラス
public sealed class EffectPass

この EffectPass クラスのオブジェクトが、頂点シェーダとピクセルシェーダを定義するパスとなります。パスを有効にすることで、グラフィックスデバイスに送られた頂点配列がピクセル化され、平面のディスプレイに描画されます。

パスの名前は Name プロパティから取得できます。インデクサに名前を指定した場合は、このプロパティの文字列と一致するパスが返されます。 

EffectPass クラス Name プロパティ
public string Name { get; }

このプロパティは、パスにつけられている名前を返します。パスの名前は HLSL で開発者が自由に指定できますが、BasicEffect を使用する場合は 1 つのパスしか持たないため、この場では重要ではありません。

パスを有効にするには EffectPass クラスの Apply() メソッドを呼び出します。

EffectPass クラス Apply() メソッド
public void Apply ()

これまでの内容をまとめると、プリミティブの描画処理にはゲームの Draw() メソッド内でテクニックを取得し、有効なテクニックに含まれているパスを開始します。現在のテクニックに含まれるすべてのパスを用いてプリミティブを描画する場合は、以下のように foreach 文で描画コードを囲みます。

foreach (EffectPass pass in effect.CurrentTechnique.Passes)
{
    pass.Apply();
    GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 1);
}

上のコードを Draw() メソッド内に記述することで、用意した VertexPositionColor 構造体型の値による頂点をプリミティブとして描画できます。

コード1
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class TestGame : Game
{
    public static void Main(string[] args)
    {
        using (Game game = new TestGame()) game.Run();
    }

    private GraphicsDeviceManager graphicsDeviceManager;
    private BasicEffect effect;
    private VertexPositionColor[] vertices;

    public TestGame()
    {
        graphicsDeviceManager = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";

        vertices = new VertexPositionColor[] {
	        new VertexPositionColor(new Vector3(0, 0.5F, 0), Color.Red),
	        new VertexPositionColor(new Vector3(0.5F, -0.5F, 0), Color.Blue),
	        new VertexPositionColor(new Vector3(-0.5F, -0.5F, 0), Color.Lime),
        };
    }

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice);
        effect.VertexColorEnabled = true;
        base.LoadContent();
    }

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

        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();
            GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 1);
        }

        base.Draw(gameTime);
    }
}
実行結果
コード1 実行結果

コード1は、3 つの頂点からなる三角形のプリミティブを描画します。結果はシンプルですが、このプリミティブを描画するまでには、頂点とエフェクトの設定が必要であり、これらの仕組みを正しく理解する必要があります。