Viewports in managed DirectX.


I’ve been asked a couple of times how to achive the effect of a rear-view mirror in managed DirectX, and this is very easy:


Let’s start with this simple translation animation of a teapot moving along the Z-axis:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

public class OurGame : Form {
private Device d3dDevice = null;
private float zPos = 10;
private Mesh mesh = null;
private Material mtrl;

public OurGame() {
this.Width = 800;
this.Height = 600;
}

public bool InitializeGraphics() {
try {
PresentParameters presentParams =
new PresentParameters();
presentParams.Windowed = true;
presentParams.SwapEffect = SwapEffect.Discard;

presentParams.EnableAutoDepthStencil = true;
presentParams.AutoDepthStencilFormat = DepthFormat.D16;

presentParams.BackBufferFormat = Format.R5G6B5;
presentParams.BackBufferHeight = this.Height;
presentParams.BackBufferWidth = this.Width;

d3dDevice = new Microsoft.DirectX.Direct3D.Device(
0,
DeviceType.Hardware,
this,
CreateFlags.SoftwareVertexProcessing,
presentParams);

mesh = Mesh.Teapot(d3dDevice);

mtrl = new Material();
mtrl.Diffuse = Color.White;
d3dDevice.Material = mtrl;

return true;
} catch(Exception ex) {
return false;
}
}

private void SetupCamera() {
d3dDevice.Transform.View = Matrix.LookAtLH(
new Vector3(0,3,-5),
new Vector3(),
new Vector3(0,1,0));

d3dDevice.Transform.Projection = Matrix.PerspectiveFovLH(
(float)Math.PI/4,
(float)this.Width/this.Height,
1,
15000);
}

private void SetupLights() {
d3dDevice.Lights[0].Type = LightType.Directional;
d3dDevice.Lights[0].Direction = new Vector3(0,-3,5);
d3dDevice.Lights[0].Diffuse = Color.White;
d3dDevice.Lights[0].Enabled = true;
}

private void Render() {
d3dDevice.Clear(ClearFlags.Target | ClearFlags.ZBuffer,
Color.BlueViolet,1,0);
d3dDevice.BeginScene();

d3dDevice.Transform.World =
Matrix.Translation(0,0,zPos-=0.1f);
mesh.DrawSubset(0);

d3dDevice.EndScene();
d3dDevice.Present();
}

public const int WM_PAINT = 0x000F;

protected override void WndProc(ref Message m) {
switch(m.Msg) {
case WM_PAINT:
SetupLights();
SetupCamera();
Render();
break;
default:
base.WndProc(ref m);
break;
}
}

public static void Main() {
OurGame ourGame = new OurGame();
if(ourGame.InitializeGraphics())
Application.Run(ourGame);
}
}


This is something that you can find in samples all over the internet. If we now want to create the rearview mirror we need to leverage a structure called Viewport. Yes, this can be done by leveraging other techiniques as well but I’ve found this solution very appealing.


To keep the code simple I will only update the Render method. First create the frontView Viewport, set the view to fill the screen, and attach it to the device:

Viewport frontView   = new Viewport();
frontView.X = 0;
frontView.Y = 0;
frontView.Width = this.Width;
frontView.Height = this.Height;
frontView.MinZ = 0;
frontView.MaxZ = 1;

d3dDevice.Viewport = frontView;


Next we render our scene as we would have previously, and after the call to EndScene we create our second viewport, this time the rearView:

Viewport rearView    = new Viewport();
rearView.X = 400;
rearView.Y = 0;
rearView.Width = 200;
rearView.Height = this.Height/4;
rearView.MinZ = 0;
rearView.MaxZ = 1;

d3dDevice.Viewport = rearView;


Notice that the X,Y, Width and Height parameters will position the rear view mirror slightly to right of the center of screen. But before we render once again, we need to update the transform matrices on the device, since otherwise we will just render the same image once more. I’ve chosen to update the View and Projection matrices on the device for this demo purpose, this is something that might be worth discussion since apparently moving the camera around this way is not recommended (I’ve read it somewhere but can’t seem to remember). Anyway this is the code that goes after the viewport has been set onto the device:

d3dDevice.Transform.View = Matrix.LookAtLH(
new Vector3(0,3,-5),
new Vector3(0,0,-10),
new Vector3(0,1,0));
d3dDevice.Transform.Projection = Matrix.PerspectiveFovLH(
(float)Math.PI/4,
(float)rearView.Width/rearView.Height,
1,
15000);

Once the transform matrices have been set, I clear the device once again (this time only the rearView viewport ‘though) and call BeginScene before I render the objects on the same positions as previously (that is no world transformation occurs). After the rendering is done, I call EndScene and Present, and that’s it.

d3dDevice.Clear(ClearFlags.Target | ClearFlags.ZBuffer,
Color.BlueViolet,1,0);
d3dDevice.BeginScene();

mesh.DrawSubset(0);

d3dDevice.EndScene();
d3dDevice.Present();


Hopefully this will give you some cool ideas what might be done, try it out, and don’t hesitate to comment or contact me with any questions.

Comments (3)

  1. sav says:

    Hi,

    That’s great..

  2. Mashiharu says:

    As I commented over at

    Rick Hoskinson’s blog, http://blogs.msdn.com/rickhos/archive/2005/03/30/403952.aspx

    … this game loop creates recursion.

    His corrections/additions: http://blogs.msdn.com/rickhos/archive/2005/04/04/405327.aspx

  3. JohanLindfors says:

    Thanks for the comment, I’ve updated the code.