WisdomSoft - for your serial experiences.

ビューポート

グラフィックスデバイスによるプリミティブの描画はレンダリングターゲット上のビューポートに対して行われます。ビューポートはグラフィックスデバイスの描画範囲となる長方形の領域を定義します。描画はビューポートの範囲に制限されるため、レンダリングターゲットを複数のビューポートに分割して描画することで、ゲーム画面内に複数のシーンを表示できます。

描画範囲の限定

レンダリングターゲット全体に描画するのではなく、画面の一部にのみ描画したいということがあります。例えば、マルチプレイヤーによる同時対戦などで画面を分割したい場合、第 1 プレイヤーと第 2 プレイヤーの画面は個別に切り替えて描画するべきです。第 1 プレイヤーの画面をレンダリングターゲットの上半分に、第 2 プレイヤーの画面をレンダリングターゲットの下半分に描画するといった処理です。

このような画面分割を実現するには、ビューポートの設定を変更します。ビューポートとは 3D グラフィックの描画先となる領域のことで、レンダリングターゲットに対するピクセル単位の長方形を表します。デフォルトのビューポートは、レンダリングターゲット全体に設定されています。ビューポートの設定をレンダリングターゲット内の一部の長方形領域に限定することで、描画先を画面の一部に特定できます。

図1 ビューポート
図1 ビューポート

ビューポートを画面の上半分に設定して第 1 プレイヤーの描画を行い、続いてビューポートを画面の下半分に設定して第 2 プレイヤーの描画を行うという形で画面分割が可能です。ビューポートの座標とサイズは自由に変更できるため、画面要素ごとにビューポートを変更しながら描画し、1 フレーム内に複数の視点による 3D グラフィックを表示するといったことができます。

ビューポートの設定は GraphicsDevice クラスの Viewport プロパティから行います。 

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

このプロパティには、ビューポートとなる長方形領域を表す Microsoft.Xna.Framework.Graphics.Viewport 構造体の値を設定、または取得できます。

Microsoft.Xna.Framework.Graphics.Viewport 構造体
[SerializableAttribute]
public struct Viewport

ビューポートの座標は、X プロパティと Y プロパティを使って指定します。この座標は、長方形の左上隅の座標を表します。ビューポートの幅と高さは Width プロパティと Height プロパティで表されます。

Viewport 構造体 X プロパティ
public int X { get; set; }
Viewport 構造体 Y プロパティ
public int Y { get; set; }
Viewport 構造体 Width  プロパティ
public int Width { get; set; }
Viewport 構造体 Height  プロパティ
public int Height { get; set; }

X プロパティには X 座標を、Y プロパティには Y 座標を、Width プロパティには幅を、Height プロパティには高さを表す値を、それぞれピクセル単位で指定します。ビューポートの領域は、レンダリングターゲットの範囲を超えてはなりません。レンダリングターゲットの範囲外にある場合は、例外が発生します。

デフォルトで GraphicsDevice に設定されているビューポートでは、バックバッファの幅と高さと同じ値です。

コード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 VertexBuffer vertexBuffer;
    private IndexBuffer indexBuffer;
    private BasicEffect effect;
    private VertexPositionColor[] vertices;
    private short[] indices;
    private Viewport leftViewport, rightViewport;

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

    protected override void Initialize()
    {
        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),
        };
        indices = new short[] { 0, 1, 2 };

        int width = GraphicsDevice.PresentationParameters.BackBufferWidth / 2;
        int height = GraphicsDevice.PresentationParameters.BackBufferHeight;
        leftViewport.X = 0;
        leftViewport.Y = 0;
        leftViewport.Width = width;
        leftViewport.Height = height;

        rightViewport.X = width;
        rightViewport.Y = 0;
        rightViewport.Width = width;
        rightViewport.Height = height;

        base.Initialize();
    }

    protected override void LoadContent()
    {
        vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor), vertices.Length, BufferUsage.None);
        vertexBuffer.SetData(vertices);
        GraphicsDevice.SetVertexBuffer(vertexBuffer);

        indexBuffer = new IndexBuffer(GraphicsDevice, IndexElementSize.SixteenBits, indices.Length, BufferUsage.None);
        indexBuffer.SetData(indices);
        GraphicsDevice.Indices = indexBuffer;

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

        GraphicsDevice.RasterizerState = RasterizerState.CullNone;

        base.LoadContent();
    }

    protected override void Update(GameTime gameTime)
    {
        effect.World *= Matrix.CreateRotationY(MathHelper.ToRadians(2));
        effect.Projection = Matrix.CreatePerspectiveFieldOfView(
            MathHelper.ToRadians(45), (float)leftViewport.Width / (float)leftViewport.Height, 1, 10
        );

        base.Update(gameTime);
    }

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

        GraphicsDevice.Viewport = leftViewport;
        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            effect.View = Matrix.CreateLookAt(new Vector3(0, 0, 2), Vector3.Zero, Vector3.Up);
            pass.Apply();
            GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, 1);
        }

        GraphicsDevice.Viewport = rightViewport;
        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            effect.View = Matrix.CreateLookAt(new Vector3(0, 0, -2), Vector3.Zero, Vector3.Up);
            pass.Apply();
            GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, 1);
        }

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

コード1はビューポートで左右に画面分割し、同一の回転する三角形を前から見た場合(左)と後ろから見た場合(右)を描画しています。画面左の領域を表す leftViewport フィールドと、画面右の領域を表す rightViewport フィールドを用意し、Draw() メソッド内でグラフィックスデバイスにビューポートを設定し、ビュー変換を切り替えながら画面左用の描画と、画面右用の描画を行っています。

GraphicsDevice オブジェクトによる描画は、設定されているレンダリングターゲットのビューポートに対して行われるため、レンダリングターゲットを複数のビューポートごとに分割して描画できます。これを応用すれば、複数プレイヤーによる画面分割などが可能です。