WisdomSoft - for your serial experiences.

深度ステンシルステート

複数のプリミティブを描画したとき、描画順序とは関係なく、より手前にあるプリミティブが後ろにあるプリミティブを隠します。これは深度バッファと呼ばれるピクセルに対応する深さが記録されているためです。深度バッファの設定を調整することで、プリミティブの重ね合わせを制御できるようになります。

深度バッファ

複数のプリミティブを重ねたとき、より手前にある物体が後ろにある物体を隠す深度計算が行われます。これは、グラフィックスデバイスが個々のピクセルの深さを情報として保持し、カメラに近いピクセルを優先して保持するためです。

具体的な例として、以下のような頂点配列を用意します。この頂点配列は 2 つの赤い三角形と青い三角形を表します。カメラを正の Z 軸方向に配置したとき、赤い三角形が手前、青い三角形が奥になります。

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

この頂点配列による 2 つの三角形を 2 回の DrawUserPrimitives() メソッドに分けて描画します。最初に赤い三角形を、次に青い三角形を描画します。

GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 1);
GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 3, 1);

カメラを (0, 0, 5) に配置して上のプリミティブを描画すると、結果は図1のようになります。青い三角形は赤い三角形よりも後に描画されていますが、赤い三角形の後ろにある青い三角形のピクセルは描画されないことが確認できます。

図1 描画された2つの三角形
図1

このように物体の重なる部分(ピクセル)が、描画順序とは無関係にカメラからの距離に応じて保持されていることが確認できます。もし、物体までの距離を無視して描画した場合、より後に描画した青い三角形が、赤い三角形の一部を上書きしているはずです。前に描画した赤い三角形のピクセルが、後に描画した青い三角形に塗りつぶされないのは、赤い三角形のピクセルに深度が保持されているためです。このような書き込まれているピクセルに対応する距離は深度バッファと呼ばれる領域に保存されています。

既定の設定で深度バッファは有効になっていますが、例えば深度に無関係に 2D 的な重ね合わせを行いたい場合など、深度計算が不都合な場合は深度バッファを無効にしなければなりません。深度バッファを無効にした場合、単純に後に描画したプリミティブでピクセルが上書きされます。

深度バッファは深度ステンシルステートと呼ばれるグラフィックスデバイスの設定で制御できます。深度ステンシルステートは DepthStencilState プロパティで表されます。

GraphicsDevice クラス DepthStencilState プロパティ
public DepthStencilState DepthStencilState { get; set; }

このプロパティは深度ステンシルステートを表す Microsoft.Xna.Framework.Graphics.DepthStencilState クラスのオブジェクトを設定または取得します。深度バッファに関する設定は DepthStencilState オブジェクトに格納されています。

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

DepthStencilState クラスのインスタンスは GraphicsDevice オブジェクトに設定された時点で読み取り専用になります。そのため、DepthStencilState プロパティから得られたオブジェクトのプロパティを変更しようとすると実行時例外が発生してしまいます。深度ステンシルステートを変更する場合は、必ず新しい DepthStencilState インスタンスを生成し、プロパティの設定を行った後に GraphicsDevice オブジェクトに設定してください。

DepthStencilState の新しいインスタンスを作成するには、以下のコンストラクタを用います。

DepthStencilState クラスのコンストラクタ
public DepthStencilState ()

深度バッファを使用するかどうかは、深度バッファが有効かどうかを表す DepthBufferEnable プロパティを設定します。このプロパティが true の時、深度バッファが用いられます。

DepthStencilState クラス DepthBufferEnable プロパティ
public bool DepthBufferEnable { get; set; }

このプロパティの既定の値は true です。false に設定した場合、深度バッファは無効になります。深度バッファを無効にした状態でプリミティブを描画すると、深度は記録されず、既存のピクセルは新しいプリミティブの描画で無条件に塗りつぶされます。

コード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";
    }

    protected override void Initialize()
    {
        vertices = new VertexPositionColor[] {
	        new VertexPositionColor(new Vector3(-1, 1, 0), Color.Red),
	        new VertexPositionColor(new Vector3(1, -1, 0), Color.Red),
	        new VertexPositionColor(new Vector3(-3, -1, 0), Color.Red),
	        new VertexPositionColor(new Vector3(1, 1, -1), Color.Blue),
	        new VertexPositionColor(new Vector3(3, -1, -1), Color.Blue),
	        new VertexPositionColor(new Vector3(-1, -1, -1), Color.Blue),
        };

        base.Initialize();
    }

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

        DepthStencilState depthStencilState = new DepthStencilState();
        depthStencilState.DepthBufferEnable = false;
        GraphicsDevice.DepthStencilState = depthStencilState;

        base.LoadContent();
    }

    protected override void Update(GameTime gameTime)
    {
        effect.View = Matrix.CreateLookAt(new Vector3(0, 0, 5), Vector3.Zero, Vector3.Up);
        effect.Projection = Matrix.CreatePerspectiveFieldOfView(
            MathHelper.ToRadians(45), 16F / 9F, 1, 10
        );
        base.Update(gameTime);
    }

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

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

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

コード1は LoadContent() メソッド内でグラフィックスデバイスに深度バッファを無効化した深度ステンシルステートを設定しています。上の実行結果のように、深度によるピクセルの選択が行われなくなるため、単純に後に描画したプリミティブが既存のピクセルを上書きします。よって、後に描画した青い三角形が、先に描画した赤い三角形を上書きしています。

さらに進んで、深度バッファによる深度計算を有効にしながら、新しく描画したプリミティブのピクセルに対しては深度データを更新したくない(既存の深度バッファを保護したい)という場合もあります。例えば、次のような頂点配列を用意し、3 つの赤い三角形、青い三角形、緑の三角形を描画します。

vertices = new VertexPositionColor[] {
	new VertexPositionColor(new Vector3(-1, 1, 0), Color.Red),
	new VertexPositionColor(new Vector3(1, -1, 0), Color.Red),
	new VertexPositionColor(new Vector3(-3, -1, 0), Color.Red),
	new VertexPositionColor(new Vector3(1, 1, -1), Color.Blue),
	new VertexPositionColor(new Vector3(3, -1, -1), Color.Blue),
	new VertexPositionColor(new Vector3(-1, -1, -1), Color.Blue),
	new VertexPositionColor(new Vector3(1, 0, -2), Color.Green),
	new VertexPositionColor(new Vector3(3, -2, -2), Color.Green),
	new VertexPositionColor(new Vector3(-1, -2, -2), Color.Green),
};

この頂点配列を、以下のように 3 回の DrawUserPrimitives() メソッドに分けて描画した場合、深度バッファを有効にした場合は図2のように、深度バッファを無効にした場合は図3のようになります。

図2 深度バッファが有効な場合の描画
図2 深度バッファが有効な場合の描画
図3 深度バッファが無効な場合の描画
図3 深度バッファが無効な場合の描画

描画順序を変えることなく、青い三角形と緑の三角形を赤い三角形に隠れるように赤い三角形との深度計算を有効にしながら、しかし緑色の三角形は青い三角形を上書きする(青い三角形の深度は保存しない)ように描画したいとします。

このような場合、深度バッファは有効にしながらも、青い三角形の描画時に深度バッファのデータを更新しない(深度バッファには赤い三角形に対応する深度のみが保存されている)状態に設定します。深度バッファへの書き込みを制御するには DepthBufferWriteEnable プロパティを設定します。

DepthStencilState クラス DepthBufferWriteEnable プロパティ
public bool DepthBufferWriteEnable { get; set; }

このプロパティが true であれば深度バッファはシステムによって更新されますが、false であれば深度バッファを有効にしながらも、現在の深度バッファが更新されることは無くなります。このプロパティの既定値は true です。

コード2
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;
    private DepthStencilState defaultDepthStencilState;
    private DepthStencilState readOnlyDepthStencilState;

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

    protected override void Initialize()
    {
        vertices = new VertexPositionColor[] {
	        new VertexPositionColor(new Vector3(-1, 1, 0), Color.Red),
	        new VertexPositionColor(new Vector3(1, -1, 0), Color.Red),
	        new VertexPositionColor(new Vector3(-3, -1, 0), Color.Red),
	        new VertexPositionColor(new Vector3(1, 1, -1), Color.Blue),
	        new VertexPositionColor(new Vector3(3, -1, -1), Color.Blue),
	        new VertexPositionColor(new Vector3(-1, -1, -1), Color.Blue),
	        new VertexPositionColor(new Vector3(1, 0, -2), Color.Green),
	        new VertexPositionColor(new Vector3(3, -2, -2), Color.Green),
	        new VertexPositionColor(new Vector3(-1, -2, -2), Color.Green),
        };

        base.Initialize();
    }

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

        defaultDepthStencilState = GraphicsDevice.DepthStencilState;
        readOnlyDepthStencilState = new DepthStencilState();
        readOnlyDepthStencilState.DepthBufferWriteEnable = false;

        base.LoadContent();
    }

    protected override void Update(GameTime gameTime)
    {
        effect.View = Matrix.CreateLookAt(new Vector3(0, 0, 5), Vector3.Zero, Vector3.Up);
        effect.Projection = Matrix.CreatePerspectiveFieldOfView(
            MathHelper.ToRadians(45), 16F / 9F, 1, 10
        );
        base.Update(gameTime);
    }

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

        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();
            GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 1);
            GraphicsDevice.DepthStencilState = readOnlyDepthStencilState;
            GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 3, 1);
            GraphicsDevice.DepthStencilState = defaultDepthStencilState;
            GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 6, 1);
        }

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

コード2では、既定の深度ステンシルステートを defaultDepthStencilState フィールドに代入し、DepthBufferWriteEnable プロパティに false を設定した(DepthBufferEnable プロパティは既定のまま true)読み取り専用深度バッファの設定を readOnlyDepthStencilState フィールドに代入しています。

Draw() メソッドの処理で、青い三角形を描画する手前で readOnlyDepthStencilState フィールドの深度ステンシルステートを設定し、深度バッファを更新しないようにしています。その後、元の defaultDepthStencilState フィールドの深度ステンシルステートで元の状態に戻しています。

青い三角形の描画では深度計算を行っているため、より手前にある赤い三角形のピクセルが維持されています。しかし、深度バッファは更新されていないため、青い三角形のピクセルは深度を持ちません。よって、青い三角形よりも後ろにある緑の三角形が、より手前にある青い三角形を塗りつぶしています。

上のコードのように DepthStencilState クラスの新しいインスタンスを作成しても問題ありませんが、一般的によく使われる既定の状態、読み取り専用、または深度バッファが無効な深度ステンシルステートは組み込みの深度ステンシルステートオブジェクトとして用意されています。

DepthBufferEnable プロパティと DepthBufferWriteEnable プロパティが true に設定されている既定の状態の DepthStencilState オブジェクトは、既定の深度ステンシルステートは読み取り専用の Default フィールドから取得できます。

DepthStencilState クラス Default フィールド
public static readonly DepthStencilState Default

既定で GraphicsDevice オブジェクトの DepthStencilState プロパティに Default フィールドに相当するオブジェクトが設定されています。他の処理で深度ステンシルステートが変更され、元の状態に戻したい場合に Default フィールドを使えます。

DepthBufferEnable プロパティが true で、DepthBufferWriteEnable プロパティが false の読み取り専用の深度バッファを表す深度ステンシルステートは DepthRead フィールドから取得できます。

DepthStencilState クラス DepthRead フィールド
public static readonly DepthStencilState DepthRead

同様に DepthBufferEnable プロパティと DepthBufferWriteEnable プロパティが false に設定されている、深度バッファが無効な深度ステンシルステートは None フィールドから取得できます。

DepthStencilState クラス None フィールド
public static readonly DepthStencilState None

例えばコード2で作成した readOnlyDepthStencilState フィールドに保存した読み取り専用の設定は DepthStencilState クラスの DepthRead フィールドから得られる組み込み深度ステンシルステートと同じなので、これを使ったほうが効率的です。

 GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, 1);
GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 3, 1);
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 6, 1);

コード2の Draw() メソッド内を上記のように書き換えても同じような結果が得られます。