# Mapping 3D object coordinates to 2D screen coordinates with perspective projection

Sort of following on from where this person was in their topic: Matrix for 2D perspective

What I have at the moment is a transformation matrix that's defined as follows: http://pastebin.com/GM6BhP0R -- The field projMat is just a 4x4 matrix.

I have an array of 3D points that I'm trying to map to the canvas and I'm doing that by first multiplying each vertex (3D point) by a view matrix, which I've just set to the identity matrix for the time being, although if I can get this set up that will be responsible for creating a camera with I suppose.

Lastly I take this newly transformed 3D point and I multiply it by the projection matrix as defined in the code sample above. I was hoping that this would give me the screen coordinates for the points, however there appears to be no depth to the points (e.g. points with a higher Z coordinate should start to taper in, while closer points shouldn't), so objects further away from the camera appear smaller. As I said though, after the transformations it appears to render the points in the same position irrespective of its Z coordinate. Here's how I'm rendering the points (in Java just in case you want to know):

for(int i = 0; i < vertices.length; i++) {
Vector3f transPos = Matrix4f.multiply(projMat, Matrix4f.multiply(viewMat, vertices[i]));
screen.drawPoint((int) transPos.getX(), (int) transPos.getY(), 0xffffffff);
}


As you can see here the inner multiplication just multiplies the vextex (point) with the view matrix, which is set to the identity, then the 3D vector that's produced by that is then multiplied by the projection matrix, which should set it to a perspective view.

If you can see where I've gone wrong I'd appreciate any help.

• Ah, it seems to be working now. Thanks to everyone for your help! Also, I'm not sure why but the X axis seems to be flipped for my coordinate system and I'm not sure why. Anyway, I'll hopefully figure out why that is. Feb 20 '13 at 22:33

was recently banging my head against the same wall and here is my projection code:

private function project( transform:Transform, input:IVector3, output:IVector2 ):IVector2
{
var worldspace:IVector3  = Vector3.multiplyMatrix( input, transform.getMatrix(), new Vector3() );
var cameraspace:IVector3 = Vector3.multiplyMatrix( worldspace, this.camera.transform.getMatrix(), new Vector3() );

var devicespace:IVector3 = this.camera.toNormalDeviceSpace( cameraspace, new Vector3() );
var clipspace:IVector3   = this.camera.toClipSpace( cameraspace, new Vector3() );

return this.camera.toScreenSpace( clipspace, output );
}


transform is the transformation-matrix in world space of the 3Dobject the input vertex belongs to, input is the vertex I want to project and output is now irrelevant and implementation detail. First to arguments of multiplyMatrix are relevant, the third is output again.

The vertex is still in modelspace so I first convert it to worldspace. Then I want the position relative to the camera and transform the world space coordinates of my vertex to cameraspace. Converting to homogenous coordinates is next as RobCurr pointed out (my Vector3 implementation has a w component) and finally to clip and screen space.

// toClipSpace:
Vector3.multiplyMatrix( input, this.projectionMatrix, output );

// toScreenSpace:
output.x = this.viewport.width  * 0.5 + input.x * this.viewport.width  * 0.5 / input.z;
output.y = this.viewport.height  * 0.5 + input.y * this.viewport.height * 0.5 / input.z;


The perspective projection done in toClipSpace using a perspective transform matrix does not give me the screencoordinates yet and still stores the depth of each vertex in the z component. Just in the last toScreenSpace operation I convert the 3D point into a 2D point. Because I previously converted the vertex into normal devices space, zero is in the center of the screen and values range between +1 and -1. Simply dividing by z gives you the depth.

The reason behind doing the detour of first converting into normal device space is that you can flexible project on different resolutions and perform calculations without knowing the final resolution. Clipspace still keeps the z value seperate because you can then easily discard/clip any vertices not within the frustum of your camera.

• This is great but the problem I'm having is when the Z component is negative and supposedly out of the frame and behind the camera, it appears to just come back into the screen from the opposite corner of the screen. e.g. A point at coordinate 1:1:0 should be in the top right corner of the screen. If I increase its Z component it moves forward as expected, but if I decrease it, rather than moving behind the scene and not being rendered, it begins to move in from the bottom left hand corner towards the centre of the screen, in this example. Feb 21 '13 at 1:45
• hmm, maybe you can resolve this transposing a few matrices or changing the multiplication order. its a problem I ran into a lot, as its difficult see if the code is column or row based. Feb 21 '13 at 11:58
• I think the matrix multiplications are in the correct order. I'm not entirely sure but I think it has something to do with the last division of the x and y components by the z. Here's my most recent Camera3D class where I organise the matrices and render the points. Can you spot any errors that would cause the points to render in the opposite corner after passing the Z value of the camera (behind): pastebin.com/NGGHpqGv Feb 23 '13 at 15:49

Note that you will end up with a point in clip space, not screen space. You need another bit of math to convert the [-1,+1]x[-1,+1] values to [0,width]x[0,height] coordinates.

You can do this simply by converting [-1,+1] to [0,+1] (add 1, multiply by 0.5) then scale appropriately (multiply by width or height). This can be put in matrix form if that's easier for you.

I notice that you aren't using homogenous coordinates for your transformation. You are simply using a Vector3f which is strange as I didn't think you could multiply a 4x4 matrix with a 1x3 vector. Try changing your Vector3f's to Vector4f's and put a 1 in the fourth element. Multiply it as you did then take the resulting Vector4f and divide the x,y,z value by the fourth(w) value to produce the desired result.

Finally, do as Sean suggested and perform the conversion from clip space to screen space.

Hope that helps.