Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I am developing a camera for a 2D game with a top-down view that has depth. It's almost a 3D camera. Basically, every object has a Z even though it is in 2D, and similarly to parallax layers their position, scale and rotation speed vary based on their Z.

I guess this would be a perspective projection.

But I am having trouble converting the objects' 3D coordinates into the 2D space of the screen so that everything has correct perspective and scale. I never learned matrices though I did dig the topic a bit today. I tried without using matrices thanks to the Wikipedia article on perspective projection but every attempt gave awkward results.

I'm using ActionScript 3 and Flash 11+ (Starling), where the screen coordinates work like this: Left-handed coordinates system illustration

I can explain further what I did if you want to help me sort out what's wrong, or you can directly tell me how you would do it properly. In case you prefer the former, read on.

These are images from the Wikipedia article linked above, showing the formulas I used:

The long formula is greatly simplified because I believe a normal top-down 2D camera has no X/Y/Z rotation values (correct?). Then it becomes d = a - c. Still, I can't get it to work.

Maybe you could explain what numbers I should put in a(xyz), c(xyz), theta(xyz), and particularly, e(xyz)? I don't quite get how e is different than c in my case. c.z is also an issue to me. If the Z of the camera's target object is 0, should the camera's Z be something like -600? ( = focal length of 600) Whatever I do, it's wrong.

I only got it to work when I used arbitrary calculations that "looked" right, like most cameras with parallax layers seem to do, but that's fake! If I want objects to travel between Z layers I might as well do it right.

share|improve this question
    
Haha, greetings are censored. I didn't know stackexchange.com enforced impoliteness xD –  Jack Nov 30 '12 at 21:53
    
Yeah that's definitely an interesting tidbit regarding this community. The general thought behind it is due to such things detracting from the question. meta.stackexchange.com/questions/2950/… –  SpartanDonut Nov 30 '12 at 22:20
    
Are you going to be rendering pixels manually, or are you just looking for a system where 2D objects further away are scaled down? Perspective 3D transforms aren't going to look right unless you're rasterizing triangles and handling perspective correction while texturing them. –  Sean Middleditch Dec 1 '12 at 1:25
    
I'm using 2D objects represented as quads (2 triangles), on which a texture is applied. The game is based on Starling, it's a 2D framework standing over the 3D Flash API "Stage3D". It's open source if you're curious for details. :) The X, Y, Z and scale properties are separate from Starling's display objects, I only set the real x/y/scale once all the transformations are done, and the untransformed X/Y/Z/Scale are also kept intact. I mean, they translate as objects move, but they do not undergo coordinate space transformations. Would this look right ? –  Jack Dec 1 '12 at 1:35

4 Answers 4

Those formulas look crazy. Perspective projection just means dividing each object's XY position and size by its Z distance from the camera. Then you scale the resulting values to get the desired field of view. This is assuming the camera is located at the origin and the Z axis points into the screen, so do this after any camera rotation/translation has been applied.

I'm not sure how helpful this will be to you, but you might want to look at the projection matrix formula shown on this page from the Direct3D API documentation. In D3D and similar APIs, perspective projection is done by multiplying a point like [x, y, z, 1] by this matrix, then dividing the result by its fourth component (see What does the graphics card do with the fourth element of a vector?). Again, this is done after any camera rotation/translation are applied.

share|improve this answer
    
Heh, I've looked over OpenGL earlier. I'm not yet used enough to matrices to make use of your hints though... I'm afraid I would need to be stepped through the whole process in detail >_< Regarding your first paragraph, I don't even get how you scale to the field of view. I suppose it's the game window width and height, but I don't get why, how and what I should scale to these values... That's rather amusing to be so clueless. –  Jack Nov 30 '12 at 22:39
    
@Jack Well, perhaps you can think of it in terms of deciding what Z-depth should be 1:1 with screen pixels. If, say, an object at a depth of 1000 should be the same size on screen that it is authored at in your world, then you'd want to divide by Z and then multiply by 1000. Then, objects at a depth of 2000 would appear farther (size reduced by 2x) and those at a depth of 500 would appear closer (size expanded by 2x). –  Nathan Reed Nov 30 '12 at 22:45
    
Thanks Nathan. I think that's pretty similar to what I was doing when I said I used arbitrary calculations. ||| But I thought I had to first transform the object coordinates in the world into camera space and THEN into screen space. I know 3D is all about transforming coordinates into various spaces, applying carefully rotations, translations and scaling along the way. –  Jack Nov 30 '12 at 22:57
    
I know I'm in simple 2D with an arbitrary Z, but I would like to do it the real way so I can reuse this knowledge for other purposes. Is what you said the "real" way ? Maybe dividing by Z is transforming to camera space and multiply by 1000 is screen space ? So X, Y and Scale would be first divided by Z and then multiplied by 1000 as you said. I don't think angle would have the same treatment, but "rotation since last frame" would, no ? –  Jack Nov 30 '12 at 22:57
    
@Jack I'm assuming you're starting with coordinates in camera space. Multiplying by 1000 (or whatever number) and dividing by Z gets you to screen space. Z here is depth relative to the camera - that's really all that "camera space" is in the 2D context. And yes, this is basically "the real way". –  Nathan Reed Nov 30 '12 at 23:00

I'm sorry to put this as a reply but the site just won't show me a "comment" button.

Nathan's solution works to a point but there remained a translation to do because the camera X and Y are centered, they're not 0. If I put two of the same object at the same X and Y but a different Z, I would expect that the camera perspective would display them one right above the other in the center of the screen. Without the final translation, they overlay top left.

I mostly wanted to understand correctly what happened. So dividing by the difference in Z between the camera and the object and then multiplying by a constant was getting from camera space to screen space. But after that I had to translate the objects by camX and camY. Is it still part of the transformation named above ? Or does it mean I went from world space to camera space to screen space ? I'd think the former, but I would like to be sure.

Secondly, let's say I want a non-linear depth view, i.e. as Z grows towards the depths, 1 more Z weights higher and higher. What's the "proper" way to do this ? I would normally add some arbitrary factor in there, but I feel like this kind of operation on an axis has a name and a standard way of doing it.

Finally, also remains an unanswered part of my question: How should 2D rotation (or difference in rotation since last frame) be handled ? I mean just basic angle of a 2D image, no 3D fanciness. It should probably be affected by perspective in some way, no ? Maybe not.

share|improve this answer
2  
If your camera moves (ie: the game's world isn't all displayed on camera, and so the camera can reorient within the world), then before you draw your objects, you need to make sure they're aligned with the camera; all of the camera-based perspective stuff assumes that the centre of the camera is at 0,0. So to align things, you need to make sure that you're subtracting camera from object-position. But it sounds more like in your sprite's model-space, you're considering 0,0 to be the top-left, instead of centre. That should be translated to centre, before anything else (pre-World-Space). –  Norguard Dec 1 '12 at 19:01

Although you state that you want perspective projection, I would first try orthographic, since it is surprisingly simple and might just give you the type of effect you're looking for.

For this to work, you just need to keep track of the center of the object (x,y, and z) and the distance from the camera to the near clipping plane. Then to translate those 3D coordinates into 2D space your need the following formula:

f(z, v) = v/(v+z)
* Where f is the size ratio to the original sprite aka size factor
    and v is the distance to the near clipping plane (trial and error value)
    and z is the distance from the near clipping plane to the object


// Example:

Assuming you have a sprite that is 2.5x1.8 units in size and 10 units away 
   from the camera, and that the near clipping plane is 5 units from the camera.

depthFactor = 5/(5+10) = 0.3

renderHeight = actualHeight * depthFactor = 1.8 * 0.3 = 0.54
renderWidth  = actualWidth * depthFactor = 2.5 * 0.3 = 0.75

This function will give your the correct factor to scale the images by, then you can just rotate and render them in the correct coordinates, starting with the object with the highest z to the lowest.

A neat fact about that result, it also works for the speed of an object. For example, a parallax background will work with that same factor value. In theory, you could create a whole sets of artwork that is proportional to each other and use that one function to scale the size and speed of every object.

EDIT:

Here is a quick demo I put together, use the mouse to move and the scroll wheel to change the depth.

share|improve this answer
    
Is the jsfiddle missing the calculation for a horizon point, or is that not part of the consideration intentionally? See jsfiddle.net/wRSLb/16 for what I'm talking about. –  Stick Feb 17 at 16:43

I see this is a fairly old question but I wanted to add something to the discussion.

If I am wrong in my approach, feel free to correct me, but this has worked out well for me so far.

It looks like the previous suggestions do not take the horizon point into account, which is part of what may be missing from previous answers. The suggested changes involving the scale factor are correct inasmuch as determining how large your objects should appear:

drawn_x = obj.x * scale_factor
drawn_y = obj.y * scale_factor

However, if you're looking for a way to draw relative to a horizon, might I suggest a modification; wherein the offset is calculated, then added to the drawn_ values of x and y.

The following pseudocode as an example:

horizon_x = screen_width / 2 //or wherever you've placed it
horizon_y = screen_height / 2 //again, wherever else is fine
scaleinverse = 1.0 - scale_factor //the inverse of your scale_factor

drawn_x = (obj.x * scale_factor) + (horizon_x * scaleinverse)
drawn_y = (obj.y * scale_factor) + (horizon_y * scaleinverse)

By ADDING (horizon_x OR horizon_y * scaleinverse) to your calculations, you determine the perspective's 'offset' for the object, such that it appears to move towards the vanishing point or horizon, or whatever you wish to call it.

http://jsfiddle.net/wRSLb/16/ is an example I've modified based off of a previous answer. I think I updated the fiddle instead of forking it :( I apologize for that! I left several comments which hopefully clearly outline what I did and why.

One thing I have noted with doing it in this way is that IF you decide to use 3D objects, it does sort of force you to determine an appropriate amount of "depth" for the screen. If for example your screen has screen_width, screen_height, and screen_depth as instance variables, if the depth is less than (screen_width + screen_height) / 1.5 then you will see some odd distortions towards points which are extremely far away from the vanishing point. Especially if objects are rotated/rotating AND they are far from the horizon, this will really become apparent. However, if you're simply using 2D sprites and scaling them in relation to a given vanishing point, this will give you both perspective as well as a parallax effect wherein the further an object is from the observer, the slower it appears to travel (even though its own speed constant need not change).

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.