ビューポート
描画範囲の限定
レンダリングターゲット全体に描画するのではなく、画面の一部にのみ描画したいということがあります。例えば、マルチプレイヤーによる同時対戦などで画面を分割したい場合、第 1 プレイヤーと第 2 プレイヤーの画面は個別に切り替えて描画するべきです。第 1 プレイヤーの画面をレンダリングターゲットの上半分に、第 2 プレイヤーの画面をレンダリングターゲットの下半分に描画するといった処理です。
このような画面分割を実現するには、ビューポートの設定を変更します。ビューポートとは 3D グラフィックの描画先となる領域のことで、レンダリングターゲットに対するピクセル単位の長方形を表します。デフォルトのビューポートは、レンダリングターゲット全体に設定されています。ビューポートの設定をレンダリングターゲット内の一部の長方形領域に限定することで、描画先を画面の一部に特定できます。
ビューポートを画面の上半分に設定して第 1 プレイヤーの描画を行い、続いてビューポートを画面の下半分に設定して第 2 プレイヤーの描画を行うという形で画面分割が可能です。ビューポートの座標とサイズは自由に変更できるため、画面要素ごとにビューポートを変更しながら描画し、1 フレーム内に複数の視点による 3D グラフィックを表示するといったことができます。
ビューポートの設定は GraphicsDevice クラスの Viewport プロパティから行います。
public Viewport Viewport { get; set; }
このプロパティには、ビューポートとなる長方形領域を表す Microsoft.Xna.Framework.Graphics.Viewport 構造体の値を設定、または取得できます。
[SerializableAttribute] public struct Viewport
ビューポートの座標は、X プロパティと Y プロパティを使って指定します。この座標は、長方形の左上隅の座標を表します。ビューポートの幅と高さは Width プロパティと Height プロパティで表されます。
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
X プロパティには X 座標を、Y プロパティには Y 座標を、Width プロパティには幅を、Height プロパティには高さを表す値を、それぞれピクセル単位で指定します。ビューポートの領域は、レンダリングターゲットの範囲を超えてはなりません。レンダリングターゲットの範囲外にある場合は、例外が発生します。
デフォルトで GraphicsDevice に設定されているビューポートでは、バックバッファの幅と高さと同じ値です。
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はビューポートで左右に画面分割し、同一の回転する三角形を前から見た場合(左)と後ろから見た場合(右)を描画しています。画面左の領域を表す leftViewport フィールドと、画面右の領域を表す rightViewport フィールドを用意し、Draw() メソッド内でグラフィックスデバイスにビューポートを設定し、ビュー変換を切り替えながら画面左用の描画と、画面右用の描画を行っています。
GraphicsDevice オブジェクトによる描画は、設定されているレンダリングターゲットのビューポートに対して行われるため、レンダリングターゲットを複数のビューポートごとに分割して描画できます。これを応用すれば、複数プレイヤーによる画面分割などが可能です。