[A5] Trying to understand projection transformations

Following my previous thread about Allegro 5 transformations, I'm now trying to understand Allegro's projection transforms.

Suppose I have a 3D point (x, y, z) and I want to find out it's screen coordinates. Allegro uses an orthographic transform by default, does that mean I can simply ignore the z coordinate and draw, say, a rectangle representing that point at (x, y)?.

What if, instead, I set up a perspective transform using al_perspective_transform() and al_use_projection_transform()? How would I then find the screen coordinates for a point? I thought that using al_transform_coordinates_3d() with the current projection transform would give me the screen coordinates but the results I'm getting are not what I expected. It seems like the resulting values from al_transform_coordinates_3d() lie within (-1, -1) and (1, 1) so I have to multiply those values by the screen -or window- width and height to get the actual screen coordinates, is that correct?. Also, I see there are a couple of new functions in 5.2.4 to simplify this kind of things, al_transform_coordinates_4d() and al_transform_coordinates_3d_projective(); haven't tried those yet since I'm still using 5.2.3 but they look like what I need in this case, right?.

Finally, and somewhat related, Allegro's default projection transform uses near and far values of -1 and 1, respectively. That is not a problem if you're using only 2D operations but, as soon as you use any of the al_*_transform_3d() functions, it might be a problem. I know it makes sense that, if you're going to use any of the 3D transformation functions, you set up a corresponding projection transform but that may not be so obvious at first for unexperienced users, so I guess at least a mention about this in the documentation would be nice.


The default transformation is given here: http://liballeg.org/a5docs/trunk/transformations.html#al_use_projection_transform

And yes, it will just ignore z and draw something at (x, y, z) at (x, y). And yes, it will not scale depth by default and so z outside of [-1..1] would get clipped. It would indeed be a good idea to mention that clipping behavior somewhere.

al_orthographic_transform says "near/far is the z-buffer range, if there is no z-buffer you can set it to -1/1." which is misleading.

Maybe we can just change that to: "near/far is the z range, coordinates outside of that range will get clipped. If you have al_draw_rectangle(0, 0, 100, 100) and rotate around the x axis ("towards the screen"), make sure your z range allows values from -100 to 100 or the rotated rectangle will get clipped.

Also, if you are using a depth buffer, this z range decides the depth resolution. For example if you have a 16 bit depth buffer you only have 65536 discrete depth values. So if your near/far is set to -1000000/1000000 most of your z positions would not result in separate depth values which could lead to artifacts.

Edgar Reynaldo

Something to keep in mind is that when you are setting a perspective projection, near and far must both be positive. They are the z-distance in front of the camera. It would never make sense to clip something behind the camera. Also, values of near near 0 will distort the image, as the values explode.


Sorry for the late reply.

Elias said:

Maybe we can just change that to: "near/far is the z range, coordinates outside of that range will get clipped. If you have al_draw_rectangle(0, 0, 100, 100) and rotate around the x axis ("towards the screen"), make sure your z range allows values from -100 to 100 or the rotated rectangle will get clipped.

Yeah, that sounds about right. It could've saved me, and may save others, a lot of trouble.

Something to keep in mind is that when you are setting a perspective projection, near and far must both be positive.

Interesting, I'd have to experiment with that to see its effect. What about a transformation like the one mentioned by Elias (rotating a 100x100 rectangle around the X or Y axis)?.

This following question might be more fitting to my previous thread but since it's locked I'm going to post it here. Maybe I still don't quite get the Allegro transforms but I thought these two transformations (one using Allegro and one using OpenGL) would be equivalent, however the result they produce is very different; what am I doing wrong here? Does it have anything to do with the projection transform?. The highlighted lines are where the transformations are defined:

1#include <allegro5/allegro.h> 2#include <allegro5/allegro_opengl.h> 3#include <allegro5/allegro_image.h> 4#include <allegro5/allegro_primitives.h> 5 6int SCR_WIDTH = 800, SCR_HEIGHT = 600; 7ALLEGRO_DISPLAY *display = NULL; 8ALLEGRO_TRANSFORM t; 9float i = 0; 10 11int main() { 12 assert(al_init()); 13 assert(al_install_keyboard()); 14 15 uint32_t version = al_get_allegro_version(); 16 printf("Allegro version: %d.%d.%d\n", version >> 24, (version >> 16) & 255, (version >> 8) & 255); 17 display = al_create_display(SCR_WIDTH, SCR_HEIGHT); 18 assert(display); 19 assert(al_init_primitives_addon()); 20 21 al_identity_transform(&t); 22 al_orthographic_transform(&t, 0, 0, -1000, 800, 600, 1000); // Change default Z clipping 23 al_use_projection_transform(&t); 24 // al_identity_transform(&t); 25 // al_use_transform(&t); 26 while (1) { 27 ALLEGRO_TRANSFORM move; 28 29 al_clear_to_color(al_map_rgb(255, 255, 255)); 30 31 // Using Allegro transforms 32 al_identity_transform(&t); 33 al_identity_transform(&move);
34 al_rotate_transform_3d(&t, 0, 1, 1, i);
35 al_translate_transform(&move, 250, 300);
36 al_compose_transform(&t, &move);
38 39 al_draw_filled_rectangle(-100, -100, 100, 100, al_map_rgb(255, 0, 0)); 40 al_draw_filled_rectangle(-10, -10, 10, 10, al_map_rgb(255, 255, 0)); 41 al_draw_filled_rectangle(-0.5, -0.5, 0.5, 0.5, al_map_rgb(0, 0, 255)); 42 43 // Using OpenGL 44 al_identity_transform(&t); // Reset Allegro transforms 45 al_use_transform(&t);
46 glTranslatef(500, 300, 0);
47 glRotatef(i * 180 / ALLEGRO_PI, 0, 1, 1);
48 49 al_draw_filled_rectangle(-100, -100, 100, 100, al_map_rgb(255, 0, 0)); 50 al_draw_filled_rectangle(-10, -10, 10, 10, al_map_rgb(255, 255, 0)); 51 al_draw_filled_rectangle(-0.5, -0.5, 0.5, 0.5, al_map_rgb(0, 0, 255)); 52 53 al_flip_display(); 54 al_rest(0.15); 55 i += 0.1; 56 } 57}


The equivalent Allegro transform to your OpenGL one would be below, so the only difference should be the 250 vs 500. Are you seeing something different?

al_rotate_transform_3d(&t, 0, 1, 1, i);
al_translate_transform(&t, 500, 300);


Well, yeah, the move transformation is only there to draw both on the screen without overlapping, forgot to mention that. The rotation transform, though, I thought should be the same but this is what I get (the GIF has some artifacts not present in the actual output, but you can see the rotation is quite different):



What if you use al_rotate_transform_3d(&t, 0, -1, 1, i); instead? I.e. flip the y axis - OpenGL has the y axis going up instead of down after all.


Nope. Same result.

I've noticed something, though.

al_rotate_transform_3d() documentation said:

Combines the given transformation with a transformation which rotates coordinates around the given vector by the given angle in radians.

So, for example, (0, 1, 1) and (0, 2, 2) represent the same rotation vector, right?. The weird scaling for the Allegro version of the transformation you can see in the GIF increases with the size of the vector; using (0, 5, 5) makes the Allegro animation be so huge it won't fit on the screen. The OpenGL version, on the other side, behaves just as with (0, 1, 1).


Ohh, I suppose it has to be a unit vector. Since changing a function is problematic with backwards compatibility we likely can only update the documentation and tell you to pass a unit vector as rotation axis.

Does that fix your example?


Oh, that makes sense and yes, it does fix the output in my example.

I think mentioning this little detail in the documentation would indeed be the right thing to do. I think Allegro 4 had a function for normalizing a vector, right? Maybe adding a function like that to Allegro 5 could come in handy; after all, I see a couple new functions being added to the transformations section in 5.2.4.

Thanks a lot for your help.


Thanks for the report and trying the fix, I just filed a pull request to update the documentation: https://github.com/liballeg/allegro5/pull/880


Sorry for bumping this thread yet again but I just built Allegro 5.2.4 from git and, what's the difference between al_transform_coordinates_3d(), al_transform_coordinates_4d() and al_transform_coordinates_3d_projective().


al_transform_coordinates_3d takes three parameters, al_transform_coordinates_4d takes four and as the documentation says, al_transform_coordinates_3d_projective is a shortcut which divides by w.


That does the documentation say, indeed. So, if I'm getting it right, al_transform_coordinates_3d() simply applies the transform while al_transform_coordinates_3d()_projective() goes one step further and normalizes the resulting coordinates.


If you search for it on http://docs.liballeg.org, you can actually click on the "Source Code" link for the function: https://github.com/liballeg/allegro5/blob/master/src/transformations.c#L467

So _projective actually calls the _4d version with w set to 1 then divides the other three coordinates by the resulting w.

The _3d version is identical to calling the _4d version with a w parameter of 0 (and ignoring the w return).

Thread #617225. Printed from Allegro.cc