Transforming a 3D point to a 2D point

TCH68k

New member
Joined
Jul 8, 2020
Messages
13
Hello there,

I apologize in advance, as i am really not good with math at all, so i'll try to be as specific as i can, to prevent any misunderstandings.

I have a 3D coordinate system with axes X, Y, Z, from -1.0 to +1.0. I have a point somewhere in there, it can be anywhere. I also have an also freely placed viewpoint in this coordinate system, which have three angles Theta X, Y, Z from 0.0 to 359.999... This viewpoint has a 2D viewframe with it's own coordinate system with axes X and Y from -1.0 to 1.0 and i would like to make a function which calculates that 3D point's 2D coordinates in the viewpoint's viewframe.

So, i have p3DX, p3DY, p3DZ, VpX, VpY, VpZ, VpThX, VpThY and VpThZ and from these values, i would like to calculate the resulting p2DX and p2DY.

I've checked this topic on Wikipedia and other forums, but all of the articles/explanations assumes that the reader is adept in high math, so i did not understand those fully.

Anyone can help me in this? Thanks in advance.
 
So, you found an article explaining the process, but it contains math you don't understand. Do you expect someone to write a similar article only with detailed explanations of those math topics? Such an undertaking is called writing a book.
If you post a link to what you found and tell us where exactly you are stuck, we'll try to explain that step or point you to some resources.
 
I only need the formula for the two resulting points. Wikipedia for instance puts another viewplane in the concept and i have no idea why is that there and if the parameters of that plane are constants, then what are they.
 
I have this following code in C and where i am stuck are the ex, ey and ez variables. I have no idea what they shall be and Wikipedia did not explained it also.
C:
typedef struct
{
    float x, y, z, thx, thy, thz;
} Camera;

typedef struct
{
    float x, y;
} Point2D;

typedef struct
{
    float x, y, z;
} Point3D;

void TransformPoint3DToPoint2D(Point3D *p3D, Camera *c, Point2D *p2D)
{
    float x, y, z, cx, cy, cz, sx, sy, sz, dx, dy, dz, ex, ey, ez;

    if ((p3D == NULL) || (c == NULL) || (p2D == NULL))
    {
        return;
    }

    x = p3D->x - c->x;
    y = p3D->y - c->y;
    z = p3D->z - c->z;
    ex = 0.0;
    ey = 0.0;
    ez = sqrt((x * x) + (y * y) + (z * z)); // this was just a guess
    cx = cos(c->thx);
    cy = cos(c->thy);
    cz = cos(c->thz);
    sx = sin(c->thx);
    sy = sin(c->thy);
    sz = sin(c->thz);

    dx = cy * (sz * y + cz * x) - sy * z;
    dy = sx * (cy * z + sy * (sz * y + cz * x)) + cx * (cz * y - sz * x);
    dz = cx * (cy * z + sy * (sz * y + cz * x)) - sx * (cz * y - sz * x);

    p2D->x = ((ez / dz) * dx) + ex;
    p2D->y = ((ez / dz) * dy) + ey;
}
 
Last edited:
I only need the formula for the two resulting points. Wikipedia for instance puts another viewplane in the concept and i have no idea why is that there and if the parameters of that plane are constants, then what are they.
Please link the article.
 
The \(e_z\) value should not change for every point that you project. That wiki page says that \( \alpha=2 \arctan\left(\frac{1}{e_z}\right) \) where alpha is the viewed angle. This is a static property of the camera.

This rearranges as \( e_z=\cot\left(\frac{\alpha}{2}\right) \). Simply choose an alpha value that suits your scene (one that fits all your points of interest within the "viewing surface" or screen)
 
I only need the formula for the two resulting points. Wikipedia for instance puts another viewplane in the concept and i have no idea why is that there and if the parameters of that plane are constants, then what are they.
Are you referring to "the display surface's position relative to the camera pinhole C"? This is not "another" viewplane, this is the viewplane. This is where the image will be. If you use the plane passing through the camera/pinhole, all you'll see is one point onto which everything is projected. So your actual viewplane needs to be offset from the pinhole.
 
The \(e_z\) value should not change for every point that you project. That wiki page says that \( \alpha=2 \arctan\left(\frac{1}{e_z}\right) \) where alpha is the viewed angle. This is a static property of the camera.

This rearranges as \( e_z=\cot\left(\frac{\alpha}{2}\right) \). Simply choose an alpha value that suits your scene (one that fits all your points of interest within the "viewing surface" or screen)
The camera can move and rotate. Can still alpha be a constant?
Are you referring to "the display surface's position relative to the camera pinhole C"? This is not "another" viewplane, this is the viewplane. This is where the image will be. If you use the plane passing through the camera/pinhole, all you'll see is one point onto which everything is projected. So your actual viewplane needs to be offset from the pinhole.
And how can i calculate that offset? Or is that a constant?
 
The camera can move and rotate. Can still alpha be a constant?And how can i calculate that offset? Or is that a constant?

alpha (and therefore \(e_z\)) would normally remain constant while calculating the points for a single frame of an animation.
This quantity is like the zoom control of a physical camera.
 
And how can i calculate that offset? Or is that a constant?
You choose the location of the image plane.
{\mathbf  {e}}_{{x,y,z}}
is an input, along with the 3d point and camera location and orientation.

 
C:
    cz = cos(c->thz);
    sx = sin(c->thx);
}

BTW: A couple of optimisation suggestions:- While computing the points for a single frame your camera probably won't move, so you can set "cz", "sx" etc once per frame and then use these values for all the points in the scene. Also you might want to switch to using cosf() and sinf() functions which are optimised for floats.

If you are interested in how 3d rendering works then this project sounds like a nice idea. But if you want to draw a 3d scene really quickly you might want to consider using OpenGL or some other library designed for this purpose, since they should be able to directly use the hardware on your graphics card
 
alpha (and therefore \(e_z\)) would normally remain constant while calculating the points for a single frame of an animation.
This quantity is like the zoom control of a physical camera.
If i do not want to zoom at all, then what value should i choose? 0, 1 or something else?
You choose the location of the image plane.
{\mathbf  {e}}_{{x,y,z}}
is an input, along with the 3d point and camera location and orientation.

Well, in that case, that plane is my monitor. What values should i assign to ex, ey and ez then?
BTW: A couple of optimisation suggestions:- While computing the points for a single frame your camera probably won't move, so you can set "cz", "sx" etc once per frame and then use these values for all the points in the scene.
Of course, this is just a proof of concept function for experimenting with one point.
Also you might want to switch to using cosf() and sinf() functions which are optimised for floats.
In the end, it will work with integer values, currently i only use floats for convenience, but thanks for the tip.
If you are interested in how 3d rendering works then this project sounds like a nice idea. But if you want to draw a 3d scene really quickly you might want to consider using OpenGL or some other library designed for this purpose, since they should be able to directly use the hardware on your graphics card
I would like to work with software rendered filled vectorgraphics, so it's the first one.
 
If i do not want to zoom at all, then what value should i choose? 0, 1 or something else?
Of course, this is just a proof of concept function for experimenting with one point.

Refer to the diagram in post#11, I include part of the image where I've marked on "angle alpha"...

alpha.png

Consider drawing point "B". The camera is rotated directly towards the 3d point, so the zoom value does not matter. The 3d point will be mapped to the center of your display surface. Zooming in and out won't change the point's position on the display.

However if your camera is not rotated directly at the 3d point, like point "C", then that point will not appear at the center of the 2d display. "Zooming in" (reducing angle alpha) will then have the effect of moving the point farther away from the the center of your display (towards the edge of the screen - and perhaps even off the edge!)

When you are setting up the camera rotation it would be easier to start with a wide viewing angle (zooming out) because the dot is more likely to appear on your screen even if the rotation isn't perfect. I would start with an alpha of around 160° or so, which corresponds to \(e_z\)≈0.18. But when you find the correct rotation then it is good to reduce the angle alpha otherwise you can get something called "perspective distortion" when drawing an object that has lots of points. Things near to the edge of the display can appear unpleasantly stretched.

The way of changing alpha involves setting the "z=near", blue arrow, which corresponds to the \(e_z\) distance while keeping the height of the "black line on the left" constant.
 
The point is not necessarily on the screen, it may be in the opposite direction than the camera looks. Also, the screen size never changes, so does that mean, that alpha is always the same? If not, how alpha is calculated? Or did i misinterpret something? I don't understand what did you mean by "find the correct rotation".
 
The point is not necessarily on the screen, it may be in the opposite direction than the camera looks.

This would correspond to a -ve "dz", in which case you should not plot the point

Beyond this, I'm not too sure what you're actually struggling to understand, this would be far easier face-to-face! (I'm not always the best with writing/ reading). So I recommend that you have a play with your code, which looks well written so far. Just start by changing this line...

C:
    ez = sqrt((x * x) + (y * y) + (z * z)); // this was just a guess

to...
C:
    ez = 0.18f;

Plot the point on a screen/ image. Add some extra points (draw the 4 vertices of a square maybe?). Play with the variables and see what happens. Hopefully at some point it will "click" and you'll understand.
 
Okay, i'll start with that ez value and observe what happens if i change that. Thank you for the advices.
 
I made a basic SDL implementation of what you've suggested, with stub camera movement. It works, but something is not quite right. When i look directly (as in orthogonally) to the four points, then i get what i expected. But when i begin to rotate the camera on axes X or Y, then instead of the entire "polygon" moving to the opposite direction i'm rotating, only the opposite side's two vertices are beginning to move that way and also apart from each other. Either i made a bug, or the problem lies within the conception. Also rotating on axis Z does not rotate the "polygon" but deforms it.

Can you please check it? Source is available here: http://oscomp.hu/depot/dtest_0.zip

Cursor left and right rotates the camera horizontally, up and down do it vertically and Numpad - and + rotates it clockwise and counter-clockwise. Numpad 2, 4, 6 and 8 moves the camera on axes X and Y. Numpad / and * moves it on axis Z.
 
Well done on building this up.I just tried it. The effects you describe are due to three things:-

A) Your square wasn't a square (at least I didn't think it was!)

B) the very wide camera angle that I suggested. This wide angle can be useful to help get your camera pointing in the correct direction, but the results are not very pleasing to the eye. So now that you have the camera pointing at your square I suggest reducing the alpha angle.

C) Some horizontal stretching of the scene. If your screen pixels are "square" then you should really scale things by the same factor horizontally and vertically (otherwise there will be some stretch).

The following changes fix the above points...

C:
//To reduce the viewing angle...
    ez=1.0

//To make sure the square is truly square, leave the z coordinate at the same place...
    p30.x = -0.15;
    p30.y = -0.15;
    p30.z = 0.15;
    
    p31.x = 0.15;
    p31.y = -0.15;
    p31.z = 0.15;
    
    p32.x = 0.15;
    p32.y = 0.15;
    p32.z = 0.15;
    
    p33.x = -0.15;
    p33.y = 0.15;
    p33.z = 0.15;

//Initial camera points directly at the "new" square...
    C.x = 0.0;
    C.y = 0.0;
    C.z = -0.25;
    C.thx = 0.0;
    C.thy = 0.0;
    C.thz = 0.0;


//To fix the stretching change...
    p2D->ix = (int)round(((    w   / 2.0 ) * p2D->x) + (w / 2.0));
//to...
    p2D->ix = (int)round(((    h   / 2.0 ) * p2D->x) + (w / 2.0));
 
A) Your square wasn't a square (at least I didn't think it was!)
No, it wasn't, but it didn't mean to be one either. It was just a quadrilateral.
B) the very wide camera angle that I suggested. This wide angle can be useful to help get your camera pointing in the correct direction, but the results are not very pleasing to the eye. So now that you have the camera pointing at your square I suggest reducing the alpha angle.
Yes, that's it, setting alpha to 90 degree (ez=1.0) fixed the disortion. Thank you.
C) Some horizontal stretching of the scene. If your screen pixels are "square" then you should really scale things by the same factor horizontally and vertically (otherwise there will be some stretch).
Yes, currently i need square pixels, thanks for pointing out.

Currently i have but one last question about the camera's movement: how can it move "forward"? Moving it along the axes is obvious, but moving towards that way where it is pointing is not. How can i calculate the "next" coordinate out of the camera's position, angle and a step interval?
 
Top