# How can I emulate Diablo 3's Isometric view using Perspective?

Using DX11, SimpleMath I am building a isometric game like Diablo 3 in 3D and I want to use a perspective camera that emulates their top down view.

Projection Matrix: CreatePerspectiveFieldOfView(1, width/height, 0.1f, 100.0f);

But after this I am a bit unsure how I am suppose to rotate the camera. I assume I could just do p - Vector3(10, 10, 10) to get a 45 degree angle at any given p.

How do I properly position, and rotate the camera and point it at position p, to mimic Diablo 3 view?

• This answer may be of help with the angles. As for the use of a perspective projection instead of an orthographic, I have not tried this. – Vaillancourt May 31 '18 at 4:02
• IIRC Diablo 3 camera was perspective, Diablo 2 offered switch between perspective and isometric, and Diablo 1 was isometric. – Kromster May 31 '18 at 6:31

As mentioned, D3 is not in true Isometric view, it's merely at an angle. Isometric (orthographic) ignores the Z axis and thus and no true depth in the shot. It's hard to describe without just playing with it.

I find it much easier to work in 3d because the math for everything being displayed makes more sense to me.

Regardless, I posted a similar answer to another post of my camera code:

public class UlmerCam3D
{
public Vector3 CameraTarget { get { return tar; } set { tar = value; } }
public Vector3 CameraPosition { get { return pos; } set { pos = value; } }
private Vector3 Up { get; } = new Vector3(0.0f, 1.0f, 0.0f);

// a variable is modified easier in this case than a property
private Vector3 pos;
private Vector3 tar;

// phi is the angle from the vertical axis (Y-axis) expressed as radians
// NOTE: Because of the way it is oriented and the fact we don't want an upside-down camera,
// phi is *always* negative, between 0 and Pi. It does not "roll over" when changed beyond this, but instead "sticks"
public float phi;

// theta is the angle from the horizontal axis (X-axis) expressed as radians
// because of it's orientation, theta is always positive, between 0 and 2-Pi
// This min and max value roll into eachother when changed
public float theta;

// the distance between the Camera Position and Camera Target, used to calculate where the target is

// The graphics device and the member variables that are related to it
private static GraphicsDevice gd;

public UlmerCam3D(GraphicsDevice ingd)
{
gd = ingd;
pos = new Vector3(0f, 100f, 0f);
tar = new Vector3(0f, 0f, 0f);

theta = Utilities.halfPi;
phi = -Utilities.halfPi * .97f;

//TODO : eventually move this copy-code somewhere
//Target is based on Position, that way they are always synchronus
tar.X = pos.X + radius * (float)Math.Sin(theta) * (float)Math.Cos(phi);
tar.Z = pos.Z + radius * (float)Math.Cos(theta) * (float)Math.Cos(phi);
tar.Y = pos.Y + radius * (float)Math.Sin(phi);
}

const float FIELD_OF_VIEW = Utilities.halfPi;
public Matrix BuildProjection()
{
// TODO: Only Calculate if it needs updating
return Matrix.CreatePerspectiveFieldOfView(
FIELD_OF_VIEW,
gd.DisplayMode.AspectRatio,
1f, 500f);
}

public Matrix BuildView()
{
// TODO: Only Calculate if it needs updating
return Matrix.CreateLookAt(pos, tar, new Vector3(0f, 1f, 0f));
}

public Matrix BuildWorld()
{
return Matrix.Identity;// CreateWorld(tar, Vector3.Forward, Vector3.Up);
}

private bool updateCamera = true;
public void HandleKeyboardInput()
{
// TODO: Mark that the camera pos or target changed when it does

bool w = Keyboard.GetState().IsKeyDown(Keys.W);
bool a = Keyboard.GetState().IsKeyDown(Keys.A);
bool s = Keyboard.GetState().IsKeyDown(Keys.S);
bool d = Keyboard.GetState().IsKeyDown(Keys.D);

if (!(w || a || s || d))
{
return;
}

updateCamera = true;
bool shift = Keyboard.GetState().IsKeyDown(Keys.LeftShift);

if (!shift)
{
if (w)
{
pos.X += (float)Math.Sin(theta);
pos.Z += (float)Math.Cos(theta);
}
if (s)
{
pos.X -= (float)Math.Sin(theta);
pos.Z -= (float)Math.Cos(theta);
}

if (a)
{
pos.X += (float)Math.Cos(theta);
pos.Z -= (float)Math.Sin(theta);
}
if (d)
{
pos.X -= (float)Math.Cos(theta);
pos.Z += (float)Math.Sin(theta);
}
}
else
{
if (w && phi < -Utilities.thirdPi)
{
phi += (float)Math.PI / 180;
}
if (s && phi > Utilities.halfPi * -0.97)
{
phi -= (float)Math.PI / 180;
}

if (a)
{
theta += (float)Math.PI / 180;
if (theta > Math.PI * 2)
{
theta = 0;
}
}
if (d)
{
theta -= (float)Math.PI / 180;
if (theta < 0)
{
theta = (float)Math.PI * 2;
}
}
}

//Target is based on Position, that way they are always synchronus
tar.X = pos.X + radius * (float)Math.Sin(theta) * (float)Math.Cos(phi);
tar.Z = pos.Z + radius * (float)Math.Cos(theta) * (float)Math.Cos(phi);
tar.Y = pos.Y + radius * (float)Math.Sin(phi);
}


This is technically in my monogame C# project, but is based on old DirectX 9 code I wrote way back when. Unfortunately this is way more readable than my C++ code from them and it doesn't require any hacks.

For the most part you just want the math for the Target based on the Position; then simply set Phi to -Pi/4 and Theta to Pi/4 (any multiple of it) and you'll be looking at a 45 degree plane down at 45 degrees.

Side note: You should stop thinking in degrees. I highly recommend memorizing the Unit Circle (https://www.mathsisfun.com/geometry/unit-circle.html) and getting used to thinking of Angles in terms of P (http://math.rice.edu/~pcmi/sphere/drg_txt.html.)

In your case, Pi/4 is 45 degrees, which means you want to adjust the camera "down" Pi/4, and then adjust it "over" or "around" Pi/4.

• Thank you! Now I will have to play with the math to try and mimic D3 camera position/degrees. – Chris May 31 '18 at 17:42