3
\$\begingroup\$

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.

\$\endgroup\$
4
  • \$\begingroup\$ Haha, greetings are censored. I didn't know stackexchange.com enforced impoliteness xD \$\endgroup\$
    – Jack
    Commented Nov 30, 2012 at 21:53
  • \$\begingroup\$ 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/… \$\endgroup\$ Commented Nov 30, 2012 at 22:20
  • \$\begingroup\$ 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. \$\endgroup\$ Commented Dec 1, 2012 at 1:25
  • \$\begingroup\$ 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 ? \$\endgroup\$
    – Jack
    Commented Dec 1, 2012 at 1:35

4 Answers 4

2
\$\begingroup\$

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.

\$\endgroup\$
6
  • \$\begingroup\$ 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. \$\endgroup\$
    – Jack
    Commented Nov 30, 2012 at 22:39
  • \$\begingroup\$ @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). \$\endgroup\$ Commented Nov 30, 2012 at 22:45
  • \$\begingroup\$ 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. \$\endgroup\$
    – Jack
    Commented Nov 30, 2012 at 22:57
  • \$\begingroup\$ 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 ? \$\endgroup\$
    – Jack
    Commented Nov 30, 2012 at 22:57
  • \$\begingroup\$ @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". \$\endgroup\$ Commented Nov 30, 2012 at 23:00
0
\$\begingroup\$

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).

\$\endgroup\$
0
\$\begingroup\$

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 that gives you a good idea of how it functions. Use the mouse to move and the scroll wheel to change the depth.

Demo courtesy of @Stick and @JimmyBoh.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ 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. \$\endgroup\$
    – Stick
    Commented Feb 17, 2014 at 16:43
  • \$\begingroup\$ That wasn't the intention but I really like that effect, it drastically emphasizes the depth! \$\endgroup\$
    – Jim Buck
    Commented May 22, 2015 at 19:50
0
\$\begingroup\$
x = ((X - xFocus) / (Z / depthScale)) + xFocus
y = ((Y - yFocus) / (Z / depthScale)) + yFocus

The point is at (X, Y, Z) The focus is the vanishing point (xFocus, yFocus) The depthScale is how much to zoom in. Usually the average of the width and height of your screen.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .