GameMaker almost good in 3D

Stephan

Member
I was working on a little dungon crawler, like dungeon master in the early version

beign bored by the cubes for the walls, i have decided to use 3d models, (i import the fbx in blender, then i export it to .obj file, with the normals and the uv coordinates). the obj file are easy to parse.

- I draw the map using the map editor,
- the scene renderer read the map and generate the scene according to the different types of cells (wall, door, stairs, etc...)
- there can be multiple levels of floor, to handle this, i used multiples layers in the map editor (for each floor there is a collision tile map and a tile map for the walls,floor, doors, etc...)
- when the camera is on a stairs cell, it computes the z-position according to the position inside the cell and the direction of the stairs, when it leaves the stairs, it take the nearest floor level, so i know the camera z-position according to the level, and wich collision map to use
- i can also add 3d objects (they use a square as sprite to be displayed in the map editor, when drawn to the scene it use the object files and the associated texture)
- the scene is rendered to a separate surface, so i dont have to have to care about the mode (3D or 2D) when i draw the interface
- for the collision with the wall i use the collision map (tile base collision)
- for the collision with the objects i usethe bounding box of the object. I think i could use "place metting" or something like

- at last but not least: when you have quite close to an object you can click on it, and use the classic "mouse click" event to hande this event (in my example i can click on switches to open/close a door). To do this, it computes the 2d surface on the display and check if the mouse has clicked on that zone. (i think that for a dungeon crawler it should be suffisent)

I used the map editor, to draw the the walls , the doors, and also the stairs. and i can define multiple stairs in the same room (to do this, i use a couple of layers (for the map and for the collision) per floor)



I may publish this demo in the game maker store if you are interested.

YOu can test it at: https://slashio.codinfinity.net/3dtest/

There is the result after 3 days of work:
1588808722196.png

1588808812775.png

1588808196340.png
 

shortbread

Member
Great work so far!
Are you combining the static geometry into a single vertex buffer? (performance appears a bit slow for the amount of vertices)
You'll get a significant performance boost if you do, and freezing all vertex buffers that don't need to be modified will also help.
 

kraifpatrik

Member
The title "GameMaker almost good in 3D" is an insult to GM's 3D capabilities! :D But I do support any GM3D project and I do like the assets, so I'm interested to see how far you're going to push the project :)
 

Stephan

Member
Great work so far!
Are you combining the static geometry into a single vertex buffer? (performance appears a bit slow for the amount of vertices)
You'll get a significant performance boost if you do, and freezing all vertex buffers that don't need to be modified will also help.
i only create the vertext buffer one time (so, the same v_buffer is shared for all same objects), for the wall, doors, stairs, etc... i store the v_buffer and the transformation matrix to use in a data structe at the create event, so i dont have to recompute the matrices, and there is not too much v_buffer to store in memory.

in the draw event : i loop trought the object to be drawn, for each item, i apply the correct matrix, then send the v_buffer to the gpu, and voila.

Do you know a way to merge multiples vbuffer at once, so i would only have to send 1 vertex buffer?
 
Last edited:

Stephan

Member
I have found a way to generate the terain mesh (floor, walls, stairs, doorway) into one single vbuffer. so in the draw event of the map, i only have to send 2 vbuffer (one for the floor and one for the walls, because they use different textures) instead of sending [map width] * [map_height]*2 (in the worth case) vertex buffers

i dont feel that the perfs are better, another idea?
 

Stephan

Member
Great work so far!
Are you combining the static geometry into a single vertex buffer? (performance appears a bit slow for the amount of vertices)
You'll get a significant performance boost if you do, and freezing all vertex buffers that don't need to be modified will also help.
thank you a lot,
using vertex_freeze after the vertex_end, when the models are ready , improved the performances impressively, i run now in fps_real = 500 and more
 
Last edited:

Neptune

Member
So is there something in particular that GM lacks that makes it not viable for 3D games?
Or is it simply lacking tools, that could be created yourself?
 

Stephan

Member
So is there something in particular that GM lacks that makes it not viable for 3D games?
Or is it simply lacking tools, that could be created yourself?
i think it could be more viable if he had at least a 3d room designer, and a build-in 3d object format
in my case i had to write the obj/mtl parser, and i used the tilemap editor to draw my map => Each tiles represents a mesh
For the height, i also used an second tile layer => the tile number represent the height
For the collision, i did like in 2D => a third collision layer for collision with the environmenent + using bounding box (in 3D) to check collision with decorations object

then you still have to develop your controller to check the collision with the environment (like in a top down game) , and the collision with the ground (same as in a platformer game)

once you prepared all necessary thing, it's easy to draw a new dungeon, like you would do in 2D
 

Sybok

Member
So is there something in particular that GM lacks that makes it not viable for 3D games?
Or is it simply lacking tools, that could be created yourself?
Mostly the tools. But there are a few things missing like depth buffer access and environment mapping shader attributes, to name a couple.

You can do these yourself but you loose a ton of frame time due to having to handle these manually.
 

Fanatrick

Member
Mostly the tools. But there are a few things missing like depth buffer access and environment mapping shader attributes, to name a couple.

You can do these yourself but you loose a ton of frame time due to having to handle these manually.
Pretty much this, no depth-buffer access is especially limiting. There's few other things like no texture sampling inside vertex shaders and overall ancient shader support. GLSL ES v1.00 will be 11 years old this Tuesday...

Wish we could use Vulkan in the future, considering ANGLE has been supporting it for quite some time now.
 

Stephan

Member
I added support for height map for the outdoor terrain.
but it make the gameslower (less than 60 fps) with an heightmap of 1024x1024 .

Any idea how to implement height map efficientely?
 

Sybok

Member
I wouldn't have thought a height map of 1024 x 1024 should have had too much of a performance hit.

How are you going about it?
 

Stephan

Member
First, i load put the heightmap to a surface, to get the buffer; then i put it in a 2D array.
At the initialization , at the same than the generation of the wall; i generate the terrain mesh,


GML:
if (has_terrain)
{
   
    var terrain_csize = global.cell_size;
   
    water_vbuffer = vertex_create_buffer();
    vertex_begin(water_vbuffer,format_floor);
    var wx1 = 0;
    var wy1 = 0;
    var wx2 = heightmap_width*terrain_csize;
    var wy2 = heightmap_height*terrain_csize;
    var wz  =  csize-(water_level*csize);;
    mesh_add_triangle(water_vbuffer,
                [wx1,wy1,wz,0,0,c_blue],
                [wx2,wy1,wz,1,0,c_blue],
                [wx1,wy2,wz,0,1,c_blue],
                0,0,-1,c_white);
       
    mesh_add_triangle(water_vbuffer,
                [wx2,wy2,wz,1,1,c_blue],
                [wx1,wy2,wz,0,1,c_blue],
                [wx2,wy1,wz,1,0,c_blue],
                0,0,-1,c_white);
    vertex_end(water_vbuffer);
    vertex_freeze(water_vbuffer);
   
    terrain_vbuffer = vertex_create_buffer();
    vertex_begin(terrain_vbuffer,format_floor);

    var terrain_tex_width = sprite_get_width(spr_terrain);
    var terrain_tex_height = sprite_get_height(spr_terrain);
    var tw = terrain_tex_width / heightmap_width;
    var th = terrain_tex_height/heightmap_height;
    var ground_color = make_color_rgb(242,235,221);
    for(var ax=0;ax< heightmap_width-1;ax++)
    {
        for(var ay=0;ay<heightmap_height-1;ay++)
        {
            //computing the texture coord
            var tx1 = ((ax)*tw)/terrain_tex_width;
            var ty1 = ((ay)*th)/terrain_tex_height;
            var tx2 = ((ax+1)*tw)/terrain_tex_height;
            var ty2 = ((ay+1)*th)/terrain_tex_height;
       
            //scalling the terrain
            var x1 = (ax)*terrain_csize;
            var y1 = (ay)*terrain_csize;
            var x2 = (ax+1)*terrain_csize;
            var y2 = (ay+1)*terrain_csize;
       
            //getting the height in units
            var az1 = terrain_grid[ax,ay];
            var az2 =  terrain_grid[ax+1,ay];
            var az3 = terrain_grid[ax,ay+1];
            var az4 = terrain_grid[ax+1,ay+1];
           
            //scalling the height
            var z1         = csize-(az1*csize);
            var z2         = csize-(az2*csize);
            var z3         = csize-(az3*csize);
            var z4         = csize-(az4*csize);
           
            //computing the normals
            var n1         = get_triangle_normal(ax,ay,az1,        ax+1,ay,az2,    ax,ay+1,az3);
            //adding the 2 triangles of the square
            mesh_add_triangle(terrain_vbuffer,
                [x1,y1,z1,tx1,ty1,ground_color],
                [x2,y1,z2,tx2,ty1,ground_color],
                [x1,y2,z3,tx1,ty2,ground_color],
                n1[0],n1[1],n1[2],c_white);
            mesh_add_triangle(terrain_vbuffer,
                [x2,y2,z4,tx2,ty2,ground_color],
                [x1,y2,z3,tx1,ty2,ground_color],
                [x2,y1,z2,tx2,ty1,ground_color],
                n1[0],n1[1],n1[2],c_white);
       
        }
    }

    vertex_end(terrain_vbuffer);
    vertex_freeze(terrain_vbuffer);
}
in result there is 1023*1023*6 points for the mesh

then in the draw event, i do a vertex_submit at the same time than the walls and the floors of the building
 

Sybok

Member
That's probably it.

I normally just pass the height map texture to the vertex shader and let it do everything from there. The only geometry you'd need is a flat quad with only x & y vertex attributes.
 

RujiK

Member
Unfortunately passing a texture into the vertex shader (vertex texture fetching) is not supported in GMS. (I really wish it was :()

At a glance, I don't think there is anything wrong with your height map code, it's just that you are rendering over 2 million (1023x1023x2) polygons every frame. You will need to break your terrain into chunks and only render what's visible.

I just recently made some 3d height map terrain and got ~250 fps even while using some extremely unoptimized shaders. I think you already know how to do everything I did, but you can read more about it here if you want:
It looked like this:


EDIT: I'd also be curious if you are using a custom shader for your terrain or just the default one.
 

Stephan

Member
Thanks,

i have splited the terrain mesh into different chunks with size of 64 and display only the visible (according to the position relative to the camera, and the distance), it displays now about 30 chunks/256 (almost a thenth)

the fps is back to 60, and the real fps is more decent:
1589311519305.png
 

CMAllen

Member
This is also where modern engines use LOD techniques to reduce the amount of geometry and data usage, in addition to view-frustrum culling.
 
All of this is far and WELL beyond any 3D stuff I've ever seen out of Game Maker ever. Have you guys tried deacting objects outside of view range using LOD for optimisation? Also can you inplement lighting (let alone dynamic lighting)? I've got a billion questions, but I'll restrain myself!! It's just so amazing to see something like THIS run on GMS, it's just... Unreal (I did a funny) :p
 

Dot-chan

Member
For your rotate function, did you rotate the camera or the model? (I ask because looking at the camera commands, it doesn't seem to support moving through a 3D space.)
 

Stephan

Member
i rotate the model, but since the camera is following the model, we see only the character's back
 
Last edited:

Stephan

Member
I am now working on the level editor.

with this tool i'll be able to import .obj file, select the texture file,
you can move the camera using the mouse middle button
you can rotate the camera using the right button
you can zoom in/out usng the mouse wheel

you can select and move the objects in the grid using the left mouse (x and y position ; and z position if you press the left control key while draging the object)

the goal, is then to make a little extension to import the map within my game.

to be more egonomic, when the user move the objects, it automaticaly align it to the grid (the aligmennt is setted to 1/2 cell)



for the export format i think to do a simple text file like that
#of texture files
texture_id_name 0
texture_id_name ...
texture_id_name_N

#of base object
object_id_name 0 , min_x,min_y,min_z,max_x,max_y,max_z,obj_attribute (like wall, stair, door, etc...)
object_id_name 1
object_id_name N

# of object in map
for each object:
Object_index,Texture_index,position_x,position_y,position_z,rotation,scale

thanks to that i think i have everything i need to do a base room and a basic box colision that support the doorway and the stairs


1589839456091.png
 
Top