GameMaker [Solved] Does this look right to you?

Hi All,

You probably know me lately as the guy that keeps reviving that Projection Math thread... hehe, sorry.

Anyway, as a result of all that, I've come across a slightly separate issue: the cube I'm drawing isn't displaying quite right. Right?

This is what I want:


And this is what I'm getting:

Where's my front face? And why is the outline disappearing behind something else? It's not that I haven't turned on ztest/write, because I have.


Here's the relevant code:
Code:
/// @function object0/Create
// 3D vertex format
vertex_format_begin();
vertex_format_add_position_3d();
vertex_format_add_colour();
vertex_format_add_texcoord();
vformat = vertex_format_end();

// Vertex buffer
vbuffer = vertex_create_buffer();

// Draw a cube
vertex_begin(vbuffer, vformat);
draw_cube(0, 0, 0, 12, true);
vertex_end(vbuffer);

// Graphics settings
gpu_set_zwriteenable(true);
gpu_set_ztestenable(true);
gpu_set_cullmode(cull_noculling);
Code:
/// @function object0/Draw_GUI
vertex_submit(vbuffer, pr_trianglelist, -1);
Code:
///@function draw_cube(x, y, z, len, outline?)
var _cx = argument0,
    _cy = argument1,
    _cz = argument2,
    _len = argument3,
    _outline = argument4,
    _hlen = _len/2,
    _left = _cx-_hlen,
    _right = _cx+_hlen,
    _back = _cy-_hlen,
    _front = _cy+_hlen,
    _top = _cz-_hlen,
    _bot = _cz+_hlen,
    _b = 0.1,
    _hb = _b/2,
    _col_b = c_black,
    _col_x = c_red,
    _col_bx = merge_color(_col_x, _col_b, 0.5),
    _col_y = c_green,
    _col_by = merge_color(_col_y, _col_b, 0.5),
    _col_z = c_blue,
    _col_bz = merge_color(_col_z, _col_b, 0.5),
 
    ;

// Draw left and right face (fixed X)
draw_set_colour(_col_x);
draw_rect(_left, _left, _back, _front, _top, _bot);
draw_rect(_right, _right, _back, _front, _top, _bot);
if _outline {
    draw_set_colour(_col_bx);
    draw_rect(_left-_hb, _left+_hb, _back, _front, _top, _top);
    draw_rect(_left-_hb, _left+_hb, _back, _front, _bot, _bot);
    draw_rect(_left-_hb, _left+_hb, _back, _back, _top, _bot);
    draw_rect(_left-_hb, _left+_hb, _front, _front, _top, _bot);
 
    draw_rect(_right-_hb, _right+_hb, _back, _front, _top, _top);
    draw_rect(_right-_hb, _right+_hb, _back, _front, _bot, _bot);
    draw_rect(_right-_hb, _right+_hb, _back, _back, _top, _bot);
    draw_rect(_right-_hb, _right+_hb, _front, _front, _top, _bot);
}

// Draw front and back face (fixed Y)
draw_set_colour(_col_y);
draw_rect(_left, _right, _back, _back, _top, _bot);
draw_rect(_left, _right, _front, _front, _top, _bot);
if _outline {
    draw_set_colour(_col_by);
    draw_rect(_left, _left, _back-_hb, _back+_hb, _top, _bot);
    draw_rect(_right, _right, _back-_hb, _back+_hb, _top, _bot);
    draw_rect(_left, _right, _back-_hb, _back+_hb, _top, _top);
    draw_rect(_left, _right, _back-_hb, _back+_hb, _bot, _bot);
 
    draw_rect(_left, _left, _front-_hb, _front+_hb, _top, _bot);
    draw_rect(_right, _right, _front-_hb, _front+_hb, _top, _bot);
    draw_rect(_left, _right, _front-_hb, _front+_hb, _top, _top);
    draw_rect(_left, _right, _front-_hb, _front+_hb, _bot, _bot);
}

// Draw top and bottom face (fixed Z)
draw_set_colour(_col_z);
draw_rect(_left, _right, _back, _front, _top, _top);
draw_rect(_left, _right, _back, _front, _bot, _bot);
if _outline {
    draw_set_colour(_col_bz);
    draw_rect(_left, _left, _back, _front, _top-_hb, _top+_hb);
    draw_rect(_right, _right, _back, _front, _top-_hb, _top+_hb);
    draw_rect(_left, _right, _back, _back, _top-_hb, _top+_hb);
    draw_rect(_left, _right, _front, _front, _top-_hb, _top+_hb);
 
    draw_rect(_left, _left, _back, _front, _bot-_hb, _bot+_hb);
    draw_rect(_right, _right, _back, _front, _bot-_hb, _bot+_hb);
    draw_rect(_left, _right, _back, _back, _bot-_hb, _bot+_hb);
    draw_rect(_left, _right, _front, _front, _bot-_hb, _bot+_hb);
}
Code:
/// @function draw_rect(left, right, back, front, top, bot)
var _left = argument0,
    _right = argument1,
    _back = argument2,
    _front = argument3,
    _top = argument4,
    _bot = argument5,
    ;
// Tri #1
draw_vert(_left, _back, _top);
draw_vert(_right, _front, _top);
draw_vert(_left, _front, _bot);

// Tri #2
draw_vert(_right, _back, _top);
draw_vert(_right, _front, _bot);
draw_vert(_left, _back, _bot);
Code:
/// @function draw_vert(x, y, z)
var _x = argument0,
    _y = argument1,
    _z = argument2,
    _col = draw_get_colour(),
    _alpha = draw_get_alpha(),
    _vb = vbuffer;
vertex_position_3d(_vb, _x, _y, _z);
vertex_colour(_vb, _col, _alpha);
vertex_texcoord(_vb, 0, 0);
I'm obviously also applying a perspective projection, but haven't included it because I don't think it'd be that relevant. Regardless, the full code is over in the aforementioned thread.

UPDATE: As this seems to be more curious than first expected, I've uploaded the project's code for your perusal: https://app.box.com/s/v111mxriomfm4ukv4ou8ubcv0slexzm1.

Thanks GMC!
 
Last edited:

TheSnidr

Heavy metal viking dentist
GMC Elder
You need to enable ztest and zwrite, look for them in the gpu_ functions! I noticed you're also using your own projection system, that may be part of the problem as well, but try that first.
 
Oh, sorry, I was confusing a few things. It was a bum hunch. If the culling was backwards, the face should be invisible anyway.

EDIT: So you are using matrix_build_lookat to do your view matrix, right?

And you are using matrix_build_projection_perspective_fov to build your projection matrix, right?
 
EDIT: So you are using matrix_build_lookat to do your view matrix, right?
And you are using matrix_build_projection_perspective_fov to build your projection matrix, right?
Yes to both :)
Code:
STEP:
view_matrix = matrix_build_lookat(
    camera_x, camera_y, camera_z,
    camera_x + _lookx*focal_length, camera_y + _looky*focal_length, camera_z + _lookz*focal_length,
    _upx, _upy, _upz
);
proj_matrix = matrix_build_projection_perspective_fov(focal_length, room_width/room_height, _near, _far);
Code:
DRAW_GUI:
matrix_set(matrix_projection, proj_matrix);
matrix_set(matrix_view, view_matrix);
vertex_submit(vbuffer, pr_trianglelist, -1);
The plot thickens!
 
Update: The order I draw the faces changes the way it looks. E.g. If I put the top face first, everything will draw over the top face. It appears as though the Z testing isn't working correctly? Is there a zfunc I should be using as opposed to the default?
 
I'm not detecting the same problem when I test your code in GMS1.4. Although I used different view and projection methods.

I did notice that your faces, at least some of them, appear to be backwards. I might have been right with my initial hunch, that the problem is caused with backwards facing faces. Make sure all vertices have a consistent winding order when viewed from outside of the cube.
 
Last edited:
so it might not be a problem with your code but the sequence its drawn in

think of it like

the closer to the top of the code window is farther back it is
the closer to the bottom is the closest to your face
Yes, but not when you turn on the depth buffer.
I'm not detecting the same problem when I test your code in GMS1.4. Although I used different view and projection methods.

I did notice that your faces, at least some of them, appear to be backwards. I might have been right with my initial hunch, that the problem is caused with backwards facing faces. Make sure all vertices have a consistent winding order when viewed from outside of the cube.
Alright, will do. Thanks for confirming.
 
I did notice that your faces, at least some of them, appear to be backwards.
I've updated them to be clockwise from the direction of outside the cube. If I turn clockwise culling on, this addresses the problem - but this isn't really a fix. It's just band-aiding over the fact that Z testing isn't working. I should be able to have no culling active, and still see the faces closest to me first, right?
Code:
///@function draw_cube_the_long_way(x, y, z, len, outline?)
var _cx = argument0,
   _cy = argument1,
   _cz = argument2,
   _len = argument3,
   _outline = argument4,
   _hlen = _len/2,
   _left = _cx-_hlen,
   _right = _cx+_hlen,
   _back = _cy-_hlen,
   _front = _cy+_hlen,
   _top = _cz-_hlen,
   _bot = _cz+_hlen,
   _b = 0.1,
   _hb = _b/2,
   _col_b = c_black,
   _col_x = c_red,
   _col_bx = merge_color(_col_x, _col_b, 0.5),
   _col_y = c_green,
   _col_by = merge_color(_col_y, _col_b, 0.5),
   _col_z = c_blue,
   _col_bz = merge_color(_col_z, _col_b, 0.5),
   ;

// Draw front and back face (fixed Y)
draw_set_colour(_col_y);

// Front face - clock
// left top, right top, left bot.
draw_vert(_left, _front, _top);
draw_vert(_right, _front, _top);
draw_vert(_left, _front, _bot);
// right top, right bot, left bot.
draw_vert(_right, _front, _top);
draw_vert(_right, _front, _bot);
draw_vert(_left, _front, _bot);

// Back face - counterclock
// left bot, right top, left top.
draw_vert(_left, _back, _bot);
draw_vert(_right, _back, _top);
draw_vert(_left, _back, _top);
// left bot, right bot, right top.
draw_vert(_left, _back, _bot);
draw_vert(_right, _back, _bot);
draw_vert(_right, _back, _top);

// Draw left and right face (fixed X)
draw_set_colour(_col_x);

// Left face - clock
// back top, front top, front bot
draw_vert(_left, _back, _top);
draw_vert(_left, _front, _top);
draw_vert(_left, _front, _bot);
// back top, front bot, back bot.
draw_vert(_left, _back, _top);
draw_vert(_left, _front, _bot);
draw_vert(_left, _back, _bot);

// Right face - counterclock
// front bot, front top, back top.
draw_vert(_right, _front, _bot);
draw_vert(_right, _front, _top);
draw_vert(_right, _back, _top);
// back bot, front bot, back top.
draw_vert(_right, _back, _bot);
draw_vert(_right, _front, _bot);
draw_vert(_right, _back, _top);

// Draw top and bottom face (fixed Z)
draw_set_colour(_col_z);

// Top face - clock
// left back, right front, left front.
draw_vert(_left, _back, _top);
draw_vert(_right, _front, _top);
draw_vert(_left, _front, _top);
// right back, right front, left back.
draw_vert(_right, _back, _top);
draw_vert(_right, _front, _top);
draw_vert(_left, _back, _top);

// Bot face - counterclock
// left front, right front, left back.
draw_vert(_left, _front, _bot);
draw_vert(_right, _front, _bot);
draw_vert(_left, _back, _bot);
// left back, right front, right back.
draw_vert(_left, _back, _bot);
draw_vert(_right, _front, _bot);
draw_vert(_right, _back, _bot);
Would someone please mind confirming for me that the ztest and zwrite functions actually work as intended in the latest version of GMS:2? I'm starting to suspect a bug.
 
Last edited:
Looking at the pictures you posed feels like trying to figure out an M.C. Escher painting.

Have you tried turning on z testing and writing in the draw event, direclty before drawing your cube?
 
Looking at the pictures you posed feels like trying to figure out an M.C. Escher painting.
Haha it does, hey. The biggest takeaway from staring at it, for me, has been that it is indeed drawing quads earlier in the code toward the back, and what we're seeing most in front are the quads at the end of the code.
Have you tried turning on z testing and writing in the draw event, direclty before drawing your cube?
Yep, no dice o_O Worth mentioning also that the debugger under the graphics tab says they're both turned on.
 
Last edited:

Joe Ellis

Member
Out of everything the only reason I can think this is happening is if your drawing in the gui draw event's, cus the z write gets disabled and isn't able to be abled,
The problem is completely the draw order of the faces, but this wouldn't matter if the z buffer is in use
 
Out of everything the only reason I can think this is happening is if your drawing in the gui draw event's, cus the z write gets disabled and isn't able to be abled,
The problem is completely the draw order of the faces, but this wouldn't matter if the z buffer is in use
Wait, really? Why on earth is that the case?
 

Joe Ellis

Member
Wait, really? Why on earth is that the case?
Cus the z buffer gets disabled when the gui draw event begins, its just what happens, I wouldn't want it to tbh but..
It'll be so gui stuff, can be drawn in an order, like windows and menus, and they all just draw on top of each other, it works out well for gui, but it's impossible to draw 3d stuff in that event
 
Cus the z buffer gets disabled when the gui draw event begins, its just what happens, I wouldn't want it to tbh but..
Oh my God, you've solved it! I always draw in the GUI event, it's the only way to guarantee no funny business goes on with my rendering. Perfect example is I've changed it over to the Draw Event, and it works, but now the aspect ratio is 3:4.

Anyway I can fix that, but just, wow. Thanks dude! I'm going to go and write my own depth buffer code now.
 

Joe Ellis

Member
Yeah, I don't want to use the normal draw events :p
Why not? You can draw in any event, like the step event, you can still draw to a surface, but the draw event is ideal cus of how the application surface is cleared and everything in gm's engine is tuned for draw functions to be fastest during the draw event, for fastest performance, any draw functions are best done in the draw events
 
Why not? You can draw in any event, like the step event, you can still draw to a surface, but the draw event is ideal cus of how the application surface is cleared and everything in gm's engine is tuned for draw functions to be fastest during the draw event, for fastest performance, any draw functions are best done in the draw events
It's just the way I choose to use GM ¯\_(ツ)_/¯
  1. I don't use the built-in draw functions - everything is drawn to a singular vbuffer.
  2. I don't use surfaces unless I have to, and have disabled the app surface. The Draw GUI event, to my knowledge, seems to be the only way to draw directly to the screen. Or at least, GM doesn't use surfaces to affect the render pipeline in Draw GUI Events.
  3. A Text-Dump Rationale for Aether Edit.
Edit: Also, reiterating because it's no small matter: it's the only way to guarantee no funny business goes on with my rendering.
 
Last edited:

Joe Ellis

Member
The gui event first draws the app surface to the screen, then anything else you choose, so yeah it's drawing directly to the screen, but why isn't the normal draw event?, it draws to the app surface, then the app surfaces gets printed on the screen, it doesn't make any difference, the same pixels have the same colors, plus you can have depth sorted things if you don't draw them in the gui event
But the part where you build everything into a single vbuffer sounds good

In any case, you do what you wanna do, I'm not telling you to do anything at all, I'm just saying a couple of facts about gm's engine, which might make some difference
 
The gui event first draws the app surface to the screen, then anything else you choose, so yeah it's drawing directly to the screen, but why isn't the normal draw event?, it draws to the app surface, then the app surfaces gets printed on the screen, it doesn't make any difference, the same pixels have the same colors, plus you can have depth sorted things if you don't draw them in the gui event
True, but GM will twist and distort that surface through dozens of different settings, found in dozens of different places in the editor, before it actually gets to drawing it. Personally, I'd prefer to have all my settings coded the way I want :) Plus, I have an easier time getting my head around how my computer renders stuff, when I know 0,0 is the top left of my window, and not some arbitrary offset derived from view pos, scale, rotation, etc.
But the part where you build everything into a single vbuffer sounds good
Yeah, it just takes a lot of the guess work out of how the draw functions operate. For example, did you know that draw_text() winds its vertices counterclockwise? So if you cull clockwise, they'll disappear. Very hard to find when you don't know what you're looking for. And the z buffer might be disabled in the GUI Event, but culling isn't.
In any case, you do what you wanna do, I'm not telling you to do anything at all, I'm just saying a couple of facts about gm's engine, which might make some difference
Oh, yeah I get you, man - and definitely appreciate the advice. It would apply in nearly any other situation, I think.
 

Ido-f

Member
The Draw GUI event, to my knowledge, seems to be the only way to draw directly to the screen
It's possible to disable the application surface and draw everything directly to the screen buffer.
See here: https://docs2.yoyogames.com/index.html?page=source/_build/3_scripting/4_gml_reference/drawing/surfaces/application_surface_enable.html
I haven't ever done it myself, but I imagine it would be very similar to working exclusively with the GUI event.
You'd need to do aspect ratio and black bars handling yourself, as the automatic ones are done through the application surface.
You'd also probably have to manually clear the screen buffer to a background color at the start of each frame (as GM automatically does).
 
It's possible to disable the application surface and draw everything directly to the screen buffer.
I don't use surfaces unless I have to, and have disabled the app surface.
I generally prefer to do my own work. Aspect ratios aren't hard to configure, and it gives you more control over the style of scaling you want. For example, I have a setting to switch between black bars, textured bars, bars with menus (like the inventory), and no bars at all (with either scaling or stretching).
You'd also probably have to manually clear the screen buffer to a background color at the start of each frame (as GM automatically does).
Nah, if there's no surface, you don't need to clear it :) It's got to do with the way vbuffers work in GM. Once you write to it, it resets all the previous contents - as opposed to surfaces, which do need clearing.

Edit: Just tested it and something GM does sticks itself between drawing and rendering in the Draw Event. Even without the app surface, views will still muddle with your coordinates.
 

Ido-f

Member
Nah, if there's no surface, you don't need to clear it :) It's got to do with the way vbuffers work in GM. Once you write to it, it resets all the previous contents - as opposed to surfaces.
I think you misunderstood me.
I was suggesting a method through which you can do your own work but in the draw event where the zbuffer isn't disable.
I was talking about the screen buffer, not vbuffers.
The screen buffer, I believe, is the final draw destination as far as GM is concerned before your computer outputs it to your monitor.
If you don't have background clear every frame, each frame will be drawn over the last one (producing the solitaire win effect when the frames are changing).
If you disable the application surface, I thought you'd need to do that background clear yourself.
But come to think of it, it makes sense that simply having a background layer in the room would still do that clear, even with the application surface disabled.

I think handling the aspect ratio and black bars in the normal draw event with the application surface disabled would be surprisingly non-trivial, though.
In the draw-GUI event it's not an issue as the drawing coordinated are automatically translated by GM into the correct screen coordinates,factoring in the aspect ratio and black bar dimensions.
But in the normal draw event, drawing to the screen buffer without a helper surface you can stretch, you'd need each draw command to take into account that drawing at (0,0) actually means drawing at (black_bar_width, black_bar_height), drawing at (10, 0) actually means drawing at (black_bar_width+10*aspect_ratio_correction_scale, black_bar_height), etc.

Maybe it's possible to factor that in with one of the matrices. Or maybe there is a native GM function that solves it.
 
I think you misunderstood me. I was talking about the screen buffer, not vbuffers.
I didn't misunderstand you... we're talking about the same thing. A screen buffer is just a space in memory where vertices are held, just like a vbuffer. When you draw a surface to the screen - even the app surface - all you're doing is submitting a quad with the texture assigned for that surface. So, when you submit the application surface each frame, the verts override the previous verts, because that's just how vbuffers work. Then, those verts go into the shader and the GPU renders it to the screen.

At any rate, if you had to clear the screen buffer, then the Draw GUI Event wouldn't work, because it draws directly to the screen too. Now, I usually stick a warning label with my words, because I'm much more familiar with how games work, than I am with how GameMaker works. It makes sense to me that sending vertices to the screen won't leave them there permanently - you have to actively keep updating it if you want it to stay there. So this business of wiping the screen is only a thing because we're actively submitting the surface to the screen every time - and it's the contents of that surface that we're wiping. Having said that, I don't fully understand how GM works, so it's possible there are other parts at play too.
In the draw-GUI event it's not an issue as the drawing coordinated are automatically translated by GM into the correct screen coordinates,
It's actually the other way around. The Draw Event uses the application surface to translate 0,0 by the view x/y offset, rotation and scale, as well as a bunch of other things from various settings. You can picture it like this: You tell the computer to draw a point at 0,0, but all it knows is the top left of the screen. If you want to tell it to draw somewhere beyond that point, say, where room_xy is, then you'll have to offset it even further... probably with something like a surface.

Edit: To achieve an 'aspect ratio' - that is, a value for the xy start and end of the screen space where it can be other than 0 and the size of the display (respectively), all you really need to do is not draw outside of those coordinates. It's when you want to stretch/scale your screen to fit, that you start looking at surfaces, but even then, you can always just draw the vertices further apart..

I think you'll find that the real value for surfaces comes from texturing. The ability to dynamically draw to a bitmap in memory, then reference it as a lookup table later, is invaluable for certain effects. Like, depth buffers :p
 
Last edited:

Ido-f

Member
Ok.
First of all, the screen buffer is not a vbuffer.
From wikipedia: "The information in the buffer typically consists of color values for every pixel to be shown on the display".
So you really shouldn't use decisive phrasing about details you're not sure about, that's misleading.
Beyond that, this discussion is being very unproductive in the sense of keeping accurate and to the point of the suggestion I was making, so I am going to let it stay where it were.
 
For anyone with this same niche problem: it turns out that while the depth buffer gets hard-code disabled for the Draw GUI event, the depth buffer for surfaces does not. I discovered this when using a surface to draw my own depth buffer, and the surface was rendering correctly before I ever actually did anything... lol.

Just make sure you've got 'surface_depth_disable(false)', as well as the z write and test functions actually turned on (GM must require both checks for the surface buffer).


Sometimes, you have to build your own depth buffer before you can use someone else's.
 
Top