[GUIDE] Getting Started with 3D in GMS2 (Project Download)

L

Lemth

Guest
Thank you, that was it! I actually need a "cull_noculling" later on and will only draw 54 triangles at maximum so I should be fine.

One more question;

I tried to modify your "third person camera" by using arrow keys to make it move around (0,0,0) in a spherical way like, but it only makes very weird movements. Any ideas?

I'm trying to implement THIS
By using the following formulas:
x=rho*sin(phi)*cos(theta)
y=tho*sin(phi)*sin(theta)
z=rho*cos(phi)
rho=sqrt(sqr(x)+sqr(y)+sqr(z))
This is what it looks like:
 

mMcFab

Member
Sorry about the delay! I was a little busy on another project.

From looking at your code and doing a quick test, I only have about 3 things to suggest.
1) Now that you're back in 3rd person, you don't need the 1 in y-up anymore, just the 1 in z-up, assuming you can avoid the lookat vector and the up vector from becoming parallel. In my tests, having both y-up and z-up set to 1 causes the camera to have a sort of "swinging" motion.
The ideal 1-size fits all option is to actually calculate the up-vector every step (which can allow fully free orbital cameras), but this process varies depending on how you determine the lookat - I can't come up with an idea right of the top of my head as I haven't used this implementation before (at least, not knowingly) and I'm tired. Though the basic form of what I do is to determine the forward vector (normalised vector of lookfrom->lookto) and rotate it to point up.
2) Just remember to add the target location to the look from position in order to keep the camera relative, otherwise the camera will hang about near the origin and watch as it's target runs away.
3) I'm not 100% sure what calculation you are doing with "rho" here? From what I can tell, it doesn't actually do anything - I didn't need it in my test and I think the result of the calculation should be equal to what rho was before the calculation anyway - making no difference at all. Of course, I could be wrong - I'm very tired.

So, all in all, here's what I think might work:
-Remove "rho = blah" - it wasn't necessary in my test
-Change the matrix_build bit to this:
Code:
mLookat = matrix_build_lookat(
                            x + rho * sin(phi) * cos(theta),
                            y + rho * sin(phi) * sin(theta),
                            rho * sin(phi),
                            x,y,0,
                            0,0,1);
Hopefully this helps!
 
L

Lemth

Guest
@MaddeMichael Thank for the reply, no worry about delay! Very grateful that you are helping me!

I upgraded the code as you suggested; can be tested here on GMLIVE: http://tinyurl.com/l9zznfn (SET TO GL AND PRESS RUN)
The weird thing is that up-and down is no longer a rotate but a 'bounce' (press and hold up or down arrow to see what I mean.)

Too bad the gmlive is only gms1, so I had to convert code backwards, perhaps I made a mistake there?

EDIT: I found a helpful link that shows the EXACT movement that I want to create:

http://learningwebgl.com/lessons/lesson11/index.html

Trying to convert the underlying Javascript to GML. But I find some of the mat4. functions difficult to understand how they work in Gamemaker
 

mMcFab

Member
Ah, very nice - I see what you mean!

I've fixed the GMLive demo (dang, that tool is useful, thanks @YellowAfterlife!) - it was a simple typo where z = rho * sin(phi) instead of z = rho * cos(phi). So, yeah, probably just a little mistake porting :). I know for sure I've done this sort of thing too many times to count

Here's the link to that: http://tinyurl.com/kwdauxm

I've had a little look at that link, I don't think I want to recreate the whole thing, but I think I can at least give a few pointers on the mat4 stuff - this could be more info than you need, but I thought it could be helpful anyway:
  • "mat4.perspective(fov, aspect, clippingnear, clippingfar, targetmatrix)" is basically the same thing as "targetmatrix = matrix_build_perspective_fov(fov, aspect, clippingnear, clippingfar)";
  • "mat4.identity(mvMatrix)" is equivelant to "mvMatrix = matrix_build_identity()"
  • To acheive mat4.translate(mvMatrix, [0,0,-6]) after setting identity, you can just use "mvMatrix[14] = -6" (Matrices in GameMaker are just 16 index arrays where indices 12, 13 and 14 are the translation component)
  • I'm a little less sure about this one (I have no experience in direct WebGL with Javascript, but I think I get what's going on):
    • "mat4.multiply(mvMatrix, moonRotationMatrix)" should be the same as "mvMatrix = matrix_multiply(moonRotationMatrix, mvMatrix)", since the rotation should probably happen before the translation. If it acts a little wierd (i.e, the moon rotates around a point, but seemingly off-center), try swapping the arguments.
  • I see mat4.create() a few times, this isn't necessary in GM as matrix_build_* functions create the matrix anyway
  • The last mat4 bit I see is "mat4.rotate(target Matrix, angle, axis)" - this is a bit more complex - the equivelant function is d3d_transform_add_rotation_axis(), but that's a compatibility script - However, the demo only rotates about perfect x and y axes, which is a little easier.
    • Instead of creating a bunch of new matrices, the whole "newRotatinMatrix" stuff can probably be replaced with "moonRotationMatrix = matrix_multiply(moonRotationMatrix, matrix_build(0,0,0,deltaY / 10,deltaX / 10,0,1,1,1));" (Note that matrix build takes angle arguments in degrees, not radians) - this is assuming of course the camera aims from above (+Z), looking down (-Z) with the up-vector along the Y axis.
    • You could still convert the compatibility script into a useful matrix one for other uses though, even though it isn't necessary in this specific case.

Hopefully this helps make some sense of the mat4 stuff!

I'm considering updating the guide at some point and may include some more camera variations - perhaps including this orbital camera and a basic 3rd person and 1st person implementation. It might be in a little while though, when I've got some extra time.
 
L

Lemth

Guest
Ah, very nice - I see what you mean!

I've fixed the GMLive demo (dang, that tool is useful, thanks @YellowAfterlife!) - it was a simple typo where z = rho * sin(phi) instead of z = rho * cos(phi). So, yeah, probably just a little mistake porting :). I know for sure I've done this sort of thing too many times to count

Here's the link to that: http://tinyurl.com/kwdauxm

I've had a little look at that link, I don't think I want to recreate the whole thing, but I think I can at least give a few pointers on the mat4 stuff - this could be more info than you need, but I thought it could be helpful anyway:
  • "mat4.perspective(fov, aspect, clippingnear, clippingfar, targetmatrix)" is basically the same thing as "targetmatrix = matrix_build_perspective_fov(fov, aspect, clippingnear, clippingfar)";
  • "mat4.identity(mvMatrix)" is equivelant to "mvMatrix = matrix_build_identity()"
  • To acheive mat4.translate(mvMatrix, [0,0,-6]) after setting identity, you can just use "mvMatrix[14] = -6" (Matrices in GameMaker are just 16 index arrays where indices 12, 13 and 14 are the translation component)
  • I'm a little less sure about this one (I have no experience in direct WebGL with Javascript, but I think I get what's going on):
    • "mat4.multiply(mvMatrix, moonRotationMatrix)" should be the same as "mvMatrix = matrix_multiply(moonRotationMatrix, mvMatrix)", since the rotation should probably happen before the translation. If it acts a little wierd (i.e, the moon rotates around a point, but seemingly off-center), try swapping the arguments.
  • I see mat4.create() a few times, this isn't necessary in GM as matrix_build_* functions create the matrix anyway
  • The last mat4 bit I see is "mat4.rotate(target Matrix, angle, axis)" - this is a bit more complex - the equivelant function is d3d_transform_add_rotation_axis(), but that's a compatibility script - However, the demo only rotates about perfect x and y axes, which is a little easier.
    • Instead of creating a bunch of new matrices, the whole "newRotatinMatrix" stuff can probably be replaced with "moonRotationMatrix = matrix_multiply(moonRotationMatrix, matrix_build(0,0,0,deltaY / 10,deltaX / 10,0,1,1,1));" (Note that matrix build takes angle arguments in degrees, not radians) - this is assuming of course the camera aims from above (+Z), looking down (-Z) with the up-vector along the Y axis.
    • You could still convert the compatibility script into a useful matrix one for other uses though, even though it isn't necessary in this specific case.

Hopefully this helps make some sense of the mat4 stuff!

I'm considering updating the guide at some point and may include some more camera variations - perhaps including this orbital camera and a basic 3rd person and 1st person implementation. It might be in a little while though, when I've got some extra time.
I don't know what it was, but something about your explanation just made it click in my head!!

Got this camera to work with a nicely colored cube example:

http://tinyurl.com/l2cfgzb (set to GL and press RUN)

Thanks for the help!
 
Last edited by a moderator:

mMcFab

Member
That looks super nice! I'm glad you got it worked out.

Any idea why the alpha is not working on all faces?

http://tinyurl.com/ny9c5mn (set to GL and press RUN - arrow keys to move, space to change colors)
As for the alpha not working on all faces, that's actually because of something a little different - it's not a problem with your code, and not a problem with GameMaker either.
If you draw a background behind all the faces, you'll see that all the faces are in fact transparent (GMLive version: http://tinyurl.com/lkd9x6q)
The problem is really to do with alpha and the depth buffer.
Basically, if something transparent that is closer to the camera is drawn before something further away, it will clip the further thing out of the drawing (in this case, some sides aren't drawn because they are drawn after the near side)
This is an annoying issue with 3D development in general, and usually involves rendering anything transparent after all the solid stuff and using depth sorting to make sure the transparencies overlay nicely.

A working method is to enable culling again and render the interior faces and exterior faces separetely, rendering the interiors first (GMLive verion: http://tinyurl.com/mn8xc8m) (this is probably easier in GMS2 as you can set the cull order to render the interior, then reverse the cull order and draw the exterior in the exact same way). You'd still need depth sorting if you render more of these, and I expect it may still end up looking wrong if meshes intersect, though I don't know what can be done about that yet.

Another thing to rememeber is that even things with 0 alpha can result in clipping further objects out of the picture if they are drawn first - setting the alpha_ref_test or discarding pixels in a fragment/pixel shader can help.
I hope that makes some level of sense? I know what I'm trying to say, but I sometimes have a hard time explaining it.
 

Carsten

Member
Hello,

thanks for the tutorial, this is really a great help! :) I'm sorry to ask a question, but I don't know how to actually draw something at a position defined in the room with correct rotation. This is my code:

//get the direction from the camera to the object and the pitch
if (instance_exists(oCamera))
{
var cdir = point_direction(oCamera.x, oCamera.y, x, y);
var cdirtan = tan(oCamera.cz/point_distance(x, y, oCamera.x, oCamera.y)) + 90;
}
var z = 100;
var mat = matrix_build(x, y, z, 0, -90, direction + cdir, 1, 1, 1);
matrix_set(matrix_world, mat);
vertex_submit(vertex_buffer, pr_trianglelist, -1);
matrix_set(matrix_world, matrix_build_identity());


>The floor (tiles) are at z=100.
>The vertex is the plane from the example above

I've tried using the direction from the camera to the instance and then draw the instance with that data but that doesn't solve it because it then obviously draws the instance bound to the rotation of the camera. I'm a bit clueless now and would appreciate any help.

This is a video demonstrating the problem:
https://twitter.com/Intoxicant79/status/865633640763359233

Thanks, Carsten
 
Last edited:

Shut

Member
Thanks for the tutorial! I was just experimenting with 3D and got the basics, I'm trying to get the top down 3D view and having trouble with mouse positions. Any idea why mouse_x and mouse_y suddenly change to different values when using the following code?

projMat = matrix_build_projection_perspective_fov(60, 1920/1080, 1, 32000);
camera_set_proj_mat(camera, projMat);
 

mMcFab

Member
**UPDATE 24/09/17**

Finally, an update! First one in months. Here's the tweaks:
  • Removed normal information from the basic vertex buffer guide - it was kinda superfluous
  • Added info about d3d_set_shading being gone
  • Added more info about the projection matrix and it's weird handedness conversion
  • Updated the included project to reflect changes
  • Added links to my Blender model export/import tool
  • Added some basic info about vertex batching - I'm not super confident in this area, but I think what I've said is true - any corrections, let me know
  • Probably some other stuff, but I can't remember now.
 

slojanko

Member
I've written the following code for updating the camera position through the step event:
Code:
camera_x += movement_speed * (keyboard_check(ord("D")) -  keyboard_check(ord("A")));
camera_y += movement_speed * (keyboard_check(ord("S")) - keyboard_check(ord("W")));
camera_z -= movement_speed * (keyboard_check(ord("E")) - keyboard_check(ord("Q")));

camera_rotation += window_get_width()/2 - window_mouse_get_x();
camera_pitch += window_get_height()/2 - window_mouse_get_y();

camera_pitch = clamp(camera_pitch, -88, 88);
if (!keyboard_check(vk_space))
    window_mouse_set(640, 360);

//Build a matrix that looks from the camera location above, to the room center. The up vector points to -z
mLookat = matrix_build_lookat(camera_x, camera_y, camera_z, camera_x + dcos(camera_rotation), camera_y - dsin(camera_rotation), camera_z - dtan(camera_pitch), 0, 0, -1);

//Assign the matrix to the camera. This updates were the camera is looking from, without having to unnecessarily update the projection.
camera_set_view_mat(view_camera[0], mLookat);
It takes into account the Y axis increasing down (2D) and Z depth being inverted (-Z points up). My only question is why projectionMatrix requires Fov_y -60
Code:
projectionMatrix = matrix_build_projection_perspective_fov(-60, 1280/720, 1.0, 32000.0);
 

mMcFab

Member
<snip>
It takes into account the Y axis increasing down (2D) and Z depth being inverted (-Z points up). My only question is why projectionMatrix requires Fov_y -60
Code:
projectionMatrix = matrix_build_projection_perspective_fov(-60, 1280/720, 1.0, 32000.0);
Okay, basically this is a thing that has bugged me since the release of the GMS2 open beta. But I can sort of explain why.

Long story short, pre-GMS2 used a left-handed coord system in 3D. GMS2's proj matrix building function effectively changes it to a Right-Handed coordinate system, to fall inline with 2D mode (I think it's to make the transition to 3D from 2D a bit easier, which is odd since 3D gets so little love in GM). An unfortunate side-effect is that it seems to invert the up-vector used in the lookat matrix (which can cause some issues down the line, particularly in stuff like flight sims where the camera up-vector must point up relative to the player/cockpit, not the world)
In order to make the matrix function like 1.X and prior, making the y-fov and aspect negative returns everything to left-handed space and makes the up-vector act correctly.
If you're happy with the coordinates being right handed and up vector being inverted, you can just ditch the negative values. I just like my up-vector to function as expected.

I found this out by investigating the compatibilty script for d3d_set_projection_ext, which does function as I expected:
Code:
/// @description d3d - set projection
/// @param xFrom    x of from position
/// @param yFrom    y of from position
/// @param zFrom    z of from position
/// @param xTo        x of to position
/// @param yTo        y of to position
/// @param zTo        z of to position
/// @param xUp        x of up vector
/// @param yUp        y of up vector
/// @param zUp        z of up vector
/// @param fov        field of view angle
/// @param aspect    aspect ration
/// @param zmin        z buffer min
/// @param zmax        z buffer max

var mV = matrix_build_lookat( argument0, argument1, argument2,
                            argument3, argument4, argument5,
                            argument6, argument7, argument8 );
//This is the important line!! - notice that it uses negative values for the FOV and aspect
var mP = matrix_build_projection_perspective_fov( -argument9, -argument10, argument11, argument12 );

camera_set_view_mat( camera_get_active(), mV );
camera_set_proj_mat( camera_get_active(), mP );
camera_apply( camera_get_active() );
Hopefully this kinda answers your question? I know what I'm trying to say, but I think I'm having trouble actually explaining it.

I do wonder though if the inverted up-vector is a bug and should be reported. I may give it a submit. Worst case, they say it's intended and functioning properly.
 
R

Rukola

Guest
Cool tutorial!

When I found out that turning around the z axis flips the screen I went searching through 3d tutorials and found one where the math also rotates the x,y, and z-up. This prevents the camera from flipping.

Check it out!

I'm trying to create the same rotation Lemth does with awesome looking cube, but instead rotate the camera and not the cube. Any suggestions?

@MaddeMichael
I don't know what it was, but something about your explanation just made it click in my head!!

Got this camera to work with a nicely colored cube example:

http://tinyurl.com/l2cfgzb (set to GL and press RUN)

Thanks for the help!
 
Last edited by a moderator:
N

NeZvers

Guest
Am I only one who has a problem with inverted controls? I have 2d car object that rotated around its origin point, but with this camera, it's turning to opposite side. It looks like view is inverted vertically.
Also whole picture is waving like water.
Code:
mLookat = matrix_build_lookat(xx,yy,zz, xx+0,yy,0, 0, 1, 0);
 

Attachments

Last edited:

slojanko

Member
I've got a bit of a problem with transparency. I've talked to MishMash he suggested writing my own shader for dealing with these stuff but I would like to ask if there's a direct way of doing it, maybe I'm not using some gpu_set functions properly?

Untitled.png
There's currently 2 issues: overlapping triangles in the torus not stacking properly and the terrain behind the cube not rendering at all but just showing the transparent water behind it.
I use the following 3 setters:
gpu_set_zwriteenable(true);
gpu_set_ztestenable(true);
gpu_set_cullmode(cull_counterclockwise);
while the models have the alpha attribute set to 0.5 in the vertex buffer.
 
Last edited:

mMcFab

Member
I've got a bit of a problem with transparency. I've talked to Misu and he suggested writing my own shader for dealing with these stuff but I would like to ask if there's a direct way of doing it, maybe I'm not using some gpu_set functions properly?

View attachment 18848
There's currently 2 issues: overlapping triangles in the torus not stacking properly and the terrain behind the cube not rendering at all but just showing the transparent water behind it.
I use the following 3 setters:
gpu_set_zwriteenable(true);
gpu_set_ztestenable(true);
gpu_set_cullmode(cull_counterclockwise);
while the models have the alpha attribute set to 0.5 in the vertex buffer.
I'm gonna preface this rather bluntly - transparency in 3D is a pain in the butt. There's not really one fits-all solution, but I can suggest something to help out.

Using your own shader *might* help, but it's really not necessary and is likely rather complicated (I imagine you'd have to essentially implement Order-Independent-Transparency, which is unlikely to be a simple process - especially in GameMaker).

Basically, this all comes down to render order and z-write.

In general, you want to render opaque objects before anything else - and preferably things closer to the camera before things further away (so hidden pixels are skipped - this is not super important when your starting out or if you just target desktop platforms, but if you have a particularly complex fragment shader, it can be a good optimisation)

Then, you want to render transparent stuff after. This will stop the transparent stuff from hiding the terrain.

In this situation, for example:
Code:
Draw The Terrain
Draw The Water
Draw The Cube
Draw the knot
For best results, you want to render transparent stuff far from the camera first and stuff closer last. It's probably also a good idea to keep the number of transparent things as low as possible, with fairly simple geometry and keep them fairly spread out to reduce weird artifacts.

The next problem is the knot drawing over itself

The best thing to do to fix this is to disable z-write when drawing transparent objects, while leaving z-test enabled. This will keep things that are behind other things hidden, while still giving a nice colour blend.

Do this with gpu_set_zwriteenable(false);

For this situation:
Code:
Enable z-write
Draw The Terrain

Disable z-write
Draw The Water
Draw The Cube
Draw the knot
From the looks of it, this is kinda how Unity sorts stuff as well - it has 3 main passes, it seems: Solid Geometry then AlphaTest and finally Transparent, based on a little shader test I did recently.
This can have weird results when using meshes that mix opaque and transparent colours, so it's probably a good idea to break stuff up as necessary.
If you draw opaque stuff after the transparent stuff in the same place, it can look weird, given the transparent stuff doesn't write it's depth, so it compares with the depth of the opaque stuff drawn before, when z-write was active.

Proof of Implementation:

For a demo (All in GameMaker, so I can be sure it works, but the process is fundementally the same for any engine), here's a Mario with 30% transparency and no culling drawn before his environment:
marioBefore.PNG
As you can see, he blocks out the terrain (much like you see in your scene), blending only with the background colour. This is rather undesirable

Here he is drawn after the environment:
marioAfter.PNG
That's better as he's actually blending with the world he's in, but he's still "erasing" bits of himself and looking weird. Let's disable z-write with gpu_set_zwriteenable(false):
marioDone.PNG
Ah, much better. Now he looks like a real ghost!

Whether you want backface culling or not is purely situational - it depends on the look you are trying to achieve. Here's that same Mario but with backface culling on (Generally less visible, and you can't see the dungarees straps on his back among other things):
culling.PNG

Hopefully this helps out a bit. I think I've covered the important stuff, but if I've been unclear about anything or you need me to elaborate on something, do let me know and I'll try to do a better job!

PS: As another little tip, I've been using gpu_push/pop_state() a lot recently - it's incredibly useful for switching back to a standard state after some specific drawing.
 
Last edited:

Zhanghua

Member
Thank you. This made it easy to get started with the new camera system.

I tested it with a sculpt I did in Blender.
1.338.288 vertices. No problems.
The artifacts are from the texture itself (baking and uv stretch).

Could you teach me what kind of 3D file format you export from the Blender?
 
G

Gabriel

Guest
I followed this tutorial once ago to build a 3D Dungeon Crawler (just for fun) in GMS 1.4:

Of course with GMS2 it looks a mess if I import it.
But I've been trying to adapt all the obsolete functions so it works in the new version of the engine as it did in the old one. Thanks to this topic I managed to set the camera projection properly (I used my old settings, though - the same ones referred in the video tutorial).

The one thing I'm having trouble with is drawing the walls (as blocks).
If I keep the old d3d_draw_block function (which GMS2 automatically adapted into a compatibility script on import) the walls are built, but as the player (aka the camera) walks around, the view gets through the walls.

I'm not sure how to fix this. Any idea?

I don't want to make this into a profissional game. As I said before, I'm just messing around for fun, but I would like to make it work as nice as it did before.
 

Posho

Member
I followed this tutorial once ago to build a 3D Dungeon Crawler (just for fun) in GMS 1.4:

Of course with GMS2 it looks a mess if I import it.
But I've been trying to adapt all the obsolete functions so it works in the new version of the engine as it did in the old one. Thanks to this topic I managed to set the camera projection properly (I used my old settings, though - the same ones referred in the video tutorial).

The one thing I'm having trouble with is drawing the walls (as blocks).
If I keep the old d3d_draw_block function (which GMS2 automatically adapted into a compatibility script on import) the walls are built, but as the player (aka the camera) walks around, the view gets through the walls.

I'm not sure how to fix this. Any idea?

I don't want to make this into a profissional game. As I said before, I'm just messing around for fun, but I would like to make it work as nice as it did before.
Bumping for this.
 

mMcFab

Member
I followed this tutorial once ago to build a 3D Dungeon Crawler (just for fun) in GMS 1.4:

Of course with GMS2 it looks a mess if I import it.
But I've been trying to adapt all the obsolete functions so it works in the new version of the engine as it did in the old one. Thanks to this topic I managed to set the camera projection properly (I used my old settings, though - the same ones referred in the video tutorial).

The one thing I'm having trouble with is drawing the walls (as blocks).
If I keep the old d3d_draw_block function (which GMS2 automatically adapted into a compatibility script on import) the walls are built, but as the player (aka the camera) walks around, the view gets through the walls.

I'm not sure how to fix this. Any idea?

I don't want to make this into a profissional game. As I said before, I'm just messing around for fun, but I would like to make it work as nice as it did before.
Bumping for this.
Heya, and uhh sorry for the late response! I kinda fell off the GMC forum for a while...
Do you or anyone else need me to check this out still? I feel like it could still be worth me filling the gaps here

how do I do this in gamemaker for education or "1.4" because the code isn't working for m
This code does not work in 1.4 versions, I'm afraid. Some vertex buffer stuff should exist, but nothing with cameras or gpu_ functions. I can recommend checking out d3d_ functions for GameMaker studio 1.4 (which is what the cameras, GPU and vbuffers replace), but I can't justify writing a tutorial for that version at the moment, sorry šŸ™
 

zorth

Member
Heya, and uhh sorry for the late response! I kinda fell off the GMC forum for a while...
Do you or anyone else need me to check this out still? I feel like it could still be worth me filling the gaps here


This code does not work in 1.4 versions, I'm afraid. Some vertex buffer stuff should exist, but nothing with cameras or gpu_ functions. I can recommend checking out d3d_ functions for GameMaker studio 1.4 (which is what the cameras, GPU and vbuffers replace), but I can't justify writing a tutorial for that version at the moment, sorry šŸ™
thanks but I'm good now
 
Top