Translating a Position in 3D Orthographic Camera to 2D Camera

Hello. I want to draw some 2D user interface stuff where some of my 3D objects are positioned.

3D camera stuff is beyond me, but I did find some code here: Transforming a 3D position to a screen (2D) position

This ALMOST works, as you can see from the screenshots. The red bar should be drawn on the center of the two characters (see the blue lines I drew to show where). I thought it might have something to do with Orthographic projection, but I tried changing it to Perspective and the results were similar.



Here’s that function to get a Point from a 3d Position

  /// <summary>
  /// Transforming a 3D position to a 2D position
  /// </summary>
  /// <param name="vec">3D Position</param>
  /// <param name="viewMatrix">View Matrix</param>
  /// <param name="projMatrix">Projection Matrix</param>
  /// <param name="Width">Screen Width</param>
  /// <param name="Height">Screen Height</param>
  /// <returns></returns>
  public Point GetProjectPoint(Vector3 vec)//, Matrix viewMatrix, Matrix projMatrix, int Width, int Height)
  {
  	int width = ScreenWidth; 
  	int height = ScreenHeight; 

  	
  	Matrix mat = Matrix.Identity * View * Projection;

  	Vector4 v4 = Vector4.Transform(vec, mat);

  	return new Point((int)((v4.X / v4.W + 1) * (width / 2)), (int)((1 - v4.Y / v4.W) * (height / 2)));
  }
  //Properties
  public Matrix Projection
  {
  	get;
  	protected set;
  }
  public float Zoom
  {
  	get { return zoom; }
  	set
  	{
  		zoom = value;
  		UpdateLookAt(); //any time the position changes, it calls update look at
  		References.textureEffect.Parameters["MatrixTransform"].SetValue(View * Projection);
  	//	References.flatEffect.Parameters["MatrixTransform"].SetValue(ViewFlat * Projection);
  	}
  }

  public Vector3 Position
  {
  	get { return cameraPosition; }
  	set
  	{
  		cameraPosition = value;
  		UpdateLookAt(); //any time the position changes, it calls update look at
  		cameraFacing = CameraFacing();
  		References.textureEffect.Parameters["MatrixTransform"].SetValue(View * Projection);
  		//	References.flatEffect.Parameters["MatrixTransform"].SetValue(ViewFlat * Projection);

  		if (Ortho)
  			ThreeDRoom.UpdateCameraRotation();
  	}
  }

  public Vector3 Rotation
  {
  	get { return cameraRotation; }
  	set
  	{
  		cameraRotation = value;
  		UpdateLookAt(); //any time the rotation changes, it calls update look at
  		References.textureEffect.Parameters["MatrixTransform"].SetValue(View * Projection);

  		if (Ortho)
  			ThreeDRoom.UpdateCameraRotation();
  	}
  }

  public Matrix View //Defines entire view of camera. Where it's located, it's target and the up vector.  Essentially creating a transform with forward, up, and relative camera position.
  { //view and projection needed for camera's orientation in 3D space.
  	get
  	{
  			return Matrix.CreateLookAt(cameraPosition, cameraLookAt, Vector3.Up) * Matrix.CreateScale(Zoom,Zoom,Zoom);
  	}
  }
  //Constructor
  public _3DCamera(Game game, Vector3 position, Vector3 rotation, float speed)
  //	: base(game)
  {
  	cameraSpeed = speed;

  	//set up projection matrix
  	//Orthographic project = 2D, Perspective = 3D
  	if(Ortho)
  	{
  		Projection = Matrix.CreateOrthographic(10, 10, 0.0f, 4000.0f);
  	}
  	else
  	{
  		Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, game.GraphicsDevice.Viewport.AspectRatio, 0.05f, 4000.0f);
  	}

  	
  	//Set camera position and rotation
  	MoveTo(position, rotation);

  	prevMouseState = Mouse.GetState();
  }

MonoGame has the Viewport.Project and Viewport.Unproject functions to convert form 2D to 3D coordinates and vice versa.

1 Like

I know but that didn’t work either. It produced sort of similar results if I tried something like this:

  	Viewport v = new Viewport(0, 0, 1280, 720);
  	Vector3 P = viewport.Unproject(vec, Projection, Matrix.Invert(View), Matrix.Identity);
  	return new Point((int)(P.X/100f), (int)(P.Z/100f));

Here’s a new image with my code again and more enemies. It looks like something is being scaled by the wrong number or something.

To go from 3D space to screen space you should use Viewport.Project instead of Viewport.Unproject. You shouldn’t need the view matrix inversion then. Also the division by 100 looks a bit weird.

Your custom 3D to 2D conversion looks good to me though, so I would guess that the problem is not with the conversion code itself.
Is it possible that you are using different matrices while rendering, than the ones you use when doing the conversion?

Okay I tried this and got pretty close results but something is still off:

Viewport v = new Viewport(0, 0, 1280, 720);
Vector3 P = viewport.Project(vec, Projection, View, Matrix.Identity);
return new Point((int)(P.X), (int)(P.Y));

In the image above, the camera is at Position(-5, ,5, -5) and 1 Zoom

But if I move the camera, up, to the left, and zoom in it spreads those red bars out like this:

And if I put this second image over the first image, everything lines up as it should:

This is done without changing the rotation, so this is what tells me that there is some multiplier or something I am missing somewhere.

Drawing objects is done like so:

  	References.textureParameter.SetValue(imageRect.texture);
  	References.device.SamplerStates[0] = imageRect.samplerState;
  	References.device.DepthStencilState = References.depthStencilState;

  		foreach (EffectPass pass in References.textureEffect.CurrentTechnique.Passes)
  		{
  			pass.Apply();
  			References.device.DrawUserIndexedPrimitives<VertexPositionColorTexture>(
  			  PrimitiveType.TriangleList,
  				gpuVertices, 0, 4,
  				  edgeFaceList, 0, 2);
  		}

The Texture Effect is: textureEffect.Parameters[“MatrixTransform”].SetValue(View * Projection);

Not sure if it will help, but in my engine, I have a function to get a screen pixel and move it into view space.

    public static Vector3 ScreenPixelToViewPixel(Point screenPixel, ICameraService camera = null)
    {
        Vector3 nearSource = camera.Viewport.Unproject(new Vector3(screenPixel.X, screenPixel.Y, camera.Viewport.MinDepth), camera.Projection, camera.View, Matrix.Identity);

        return nearSource;
    }

Are you drawing the red bars using SpriteBatch?

Yes

It works now. My ScreenWidth and ScreenHeight values were switched. Oops.