• Hey! Guest! The 40th (!!!) GMC Jam will take place between February 25th, 12:00 UTC to March 1st 12:00 UTC. Why not join in this very special anniversary jam! Click here to find out more!

3D Translate 3D to 2D perspective?


Hi guys, first post since GMS2!

I don't quite have the firmest grasp on this math just yet, but this was a huge help.
I'm currently trying to create something that looks somewhat similar to Octopath Traveler, but FAR less complex.

If I had started this as a 3D project, I'd be billboarding the sprites, but times change, feature creep happens. So now I'm attempting to overlay my existing (basically feature-complete) 2D gameplay engine over some 3D backgrounds.

The main thing I need to do is figure out how to move the 2D camera the same observed distance that the 3D camera has traveled, preferably regardless of the size of the 3D projection or position of the camera (in case I'd like to make adjustments to the viewing angle on the fly, etc.)

This is what I've got, which is a very hacky version of what I'd like to pull off:

In this case, I'm moving the 2d object in the opposite direction of the 3D camera, but I used trial and error to get it to look (almost) right.

///pos3d_get_screen_pos(x, y, z)

var pos = matrix_transform_vertex(global.mat_world_view_projection, loader3D_obj.xto, loader3D_obj.yto, 0);

pos[0] = (1 + (pos[0] / (pos[2] + 1))) * 0.5 * 960;
pos[1] = (1 - (pos[1] / (pos[2] + 1))) * 0.5 * 640;

var old = matrix_transform_vertex(global.mat_world_view_projection, loader3D_obj.xto_old, loader3D_obj.yto_old, 0);
old[0] = (1 + (old[0] / (old[2] + 1))) * 0.5 * 960;
old[1] = (1 - (old[1] / (old[2] + 1))) * 0.5 * 640;

var new;
new[1] = pos[1] - old[1];
new[0] = pos[0] - old[0];

return new;
And in the 2D object:
new = PerspectiveChange();
x += new[0]*4.5;
That 4.5 is a made-up number, but that's the number I'd be trying to find presumably.

I'm confident I can figure this out, but I'm definitely a little out of my depth.

Thanks so much for any insight.

@Noah Copeland have you got the thing you were doing working yet? if not I've got this script here:
///pos3d_get_screen_pos(x, y, z)

var pos = matrix_transform_vertex(
matrix_multiply(matrix_get(matrix_world), matrix_multiply(matrix_get(matrix_view), matrix_get(matrix_projection))), argument0, argument1, argument2);

pos[0] = (1 + (pos[0] / (pos[2] + 1))) * 0.5 * global.screen_width
pos[1] = (1 - (pos[1] / (pos[2] + 1))) * 0.5 * global.screen_height

return pos

It takes the xyz in world space of a certain object then performs the same stuff that the shader does then returns the position. You can also use the z coordinate to set the depth while drawing the sprite, which should solve any depth sorting problems. It's intended that you draw the sprites in a 2d view\projection, basically filling the screen like
d3d_set_projection_ortho(0, 0, global.ViewWidth, ViewHeight 0);

Are you using gms1.4? this is the version of the script for that:
///pos3d_get_screen_pos(x, y, z)

matrix_set(matrix_world, matrix_multiply(matrix_get(matrix_world), matrix_multiply(matrix_get(matrix_view), matrix_get(matrix_projection))))

var pos = d3d_transform_vertex(argument0, argument1, argument2);

pos[0] = (1 + (pos[0] / (pos[2] + 1))) * 0.5 * global.screen_width
pos[1] = (1 - (pos[1] / (pos[2] + 1))) * 0.5 * global.screen_height


return pos
If you use this several times, which you will be, it's always better if you precalculate the matrix and save it to a global uniform, just after the 3d view and projection is set:
global.mat_world_view_projection = matrix_multiply(matrix_get(matrix_world), matrix_multiply(matrix_get(matrix_view), matrix_get(matrix_projection)))
Then you can set that at the start of the script instead, or even better, set it before the script, then execute the script several times. And always remember to d3d_transform_set_identity() afterwards ;)
I think what you'll want to do is make sure the 3d view and the 2d view are showing the same area... i.e., of the xy plane at z=0. This means there has to be a connection between the field of view of the 3d camera, and the distance at which it is located from z=0.

tan(fov/2) = view_height / (2 * cam_z)
fov = 2 * atan( view_height / (2 * cam_z )
cam_z = view_height / (2 * tan(fov/2) )

If the 3d camera looks directly in the z direction, and the 3d camera shows the same area as the 2d cam, then you should be able to move both cameras the same amount and keep them in sync.

You know, come to think of it, you should only really need to use the 3d projection, if you are looking down at the xy plane, won't 2d stuff still draw normall? (not sure about background tiles).
Last edited:

Joe Ellis

Your problem might be simpler than you thought, in the ref pic I think that could all be done with a 3d camera and drawing sprites at different depths, plus the horizontal floors.
The script I showed was to basically convert a 3d position into the position it is on screen, which you need if the 3d camera could be facing any direction, but with the game you showed, the camera is basically pointing exactly in the z direction, and sprites won't need to be made to face the camera cus they already are, as they're drawn flat onto the z plane.
So you should be able to just have one camera, with perspective

If you want to do it manually without making a gamemaker camera you can do:
matrix_set(matrix_view, matrix_build_lookat(camera_x, camera_y, camera_z, camera_x, camera_y, ground_z, 0, -1, 0))
matrix_set(matrix_projection, matrix_build_projection_perspective_fov(60, screen_width\screen_height, 1, 10000))