3DHow do vertex buffers handle the 'z' in vertex_position_3d? [GMS2]

Gizmo199

Member
I posted this on the sub-reddit but I figured id try here as well:

So I have a vertex buffer drawing a bunch of sprites that are stacked one on top of the other in a vertex buffer. Each sprite has a "height" that I put into the vertex_position_3d 'z' function. Now everything within the vertex buffer itself looks great! Its all depth sorted correctly. The problem is if I want an outside object (player object) to say walk in front or behind a tree it seems that the 'z' parameter in vertex buffers don't correlate to 'depth' like other objects do.

so for example my player is in between 2 trees. My player's depth is set to 0. Each tree individually has a stack of say 32 sprites (one on top of the other). So when I make my vertices I have the 'z' param set to each height accordingly per tree. So the base of the tree's 'z' is set to 0, the next sprite up is set to 1, next 2, ect. The problem is that if I say set my player's 'depth' to 3 he is drawn under EVERYTHING or visa versa wherein I set my players 'depth' to -3 he is drawn on top of EVERYTHING.

So my question is, how do vertex buffers handle the 'z' parameter in the function, because it seems that it is a completely separate thing from 'depth'. And as far as I thought, 'depth' was written to the z-buffer. So I am confused as to why it seems that every sprite in my vertex buffer is being drawn at 0 depth?

flyingsaucerinvasion

Member
What actually gets written to your depth buffer depends on the workings of your shader. In a typical shader, the value written to the depth buffer would be based on the depth of a fragment in view space.... i.e. its position along the camera's look axis. By the way, that is not at all how depth worked in the little example project I sent you a while ago. What I did instead was more akin to the depth = -y scheme people tend to use for games with oblique projections, except modified to work with rotating views.

Gizmo199

Member
What actually gets written to your depth buffer depends on the workings of your shader. In a typical shader, the value written to the depth buffer would be based on the depth of a fragment in view space.... i.e. its position along the camera's look axis. By the way, that is not at all how depth worked in the little example project I sent you a while ago. What I did instead was more akin to the depth = -y scheme people tend to use for games with oblique projections, except modified to work with rotating views.
Thank you! And yeah I noticed that was how you had it working in that example. So now I don't have any projections working with it any more and its just straight up drawn to the screen (when porting to gms2 it didn't seem to work the same as it did in the gms1 example even when converted to how projections are made now). And I see. So I will probably need to look into how to modify the shader to render to the zbuffer some way? I just assumed everything 'z' related would be drawn to the z-buffer that gm uses for its depth management.

flyingsaucerinvasion

Member
I don't know if there is any d3d_set_depth counterpart in GMS2. You might be able to just set the depth of an instance and end up with the same result. I can't test it in GMS2, but in GMS1, "depth" will only be put into the z component of in_Position, if d3d_start() has been used. Hopefully, that isn't the case in GMS2.

What is written to the z buffer depends on how your shader works. Initially, I did what you're trying to do. I had the depth written to the buffer be based on the height of sprites above zero on the z world axis. However, this did not result in acceptable results. So what I changed to is the depth = -y style thing we were talking about.

Gizmo199

Member
Yeah there is no d3d_start in gms2 now. It acts a lot differently then it did in gms1 . Yeah its just wierd cause in the gms1 example you gave me it works perfectly and looks great! But when I imported it into gms2 the depth doesn't change like it did in the gms1 example. Idk exactly how gms1 handled the d3d_depth thing but all it was in gms2 when I imported it was a global variable and basically depth handling was handled with zwriteenable and ztestenable. And I can't seem to find any real concrete documentation of how it works exactly aside from what it does but not how its being drawn. I could just be confused but I've tried 1001 things and I can't seem to get the depth with the vertex buffer to match depth used in objects / layers. :/

flyingsaucerinvasion

Member
I think there's probably an easy fix. If we could see the draw event just as it looks upon a fresh import, I think we'd be able to sort it out.

Gizmo199

Member
Alright I will post it but is a lot of mess. :/

Here it is @flyingsaucerinvasion
Compatibility Scripts:
__init_global:
Code:
``````gml_pragma( "global", "__init_global();");

// set any global defaults
layer_force_draw_depth(true,0);        // force all layers to draw at depth 0
draw_set_colour( c_black );``````
d3d_set_depth:
Code:
``global.__d3dDepth=argument0;``
d3d_set_hidden:
Code:
``gpu_set_ztestenable( argument0 );``
d3d_set_projection_ortho:
Code:
``````/// @description d3d - set orthographic
/// @param x        x of tl corner
/// @param y        y of tl corner
/// @param w        w of view
/// @param h        h of view
/// @param angle    rotation angle of the projection

var xx = argument0;
var yy = argument1;
var ww = argument2;
var hh = argument3;
var angle = argument4;

var mV = matrix_build_lookat( xx+ww/2, yy+hh/2, -16000,
xx+ww/2, yy+hh/2, 0,
dsin(-angle), dcos(-angle), 0 );
var mP = matrix_build_projection_ortho( ww, hh, 1, 32000 );

//camera_set_view_mat( global.__d3dCamera, mV );
//camera_set_proj_mat( global.__d3dCamera, mP );
//camera_apply( global.__d3dCamera );
camera_set_view_mat( camera_get_active(), mV );
camera_set_proj_mat( camera_get_active(), mP );
camera_apply( camera_get_active() );``````
d3d_set_zwriteenable:
Code:
``gpu_set_zwriteenable( argument0 );``
d3d_start:
Code:
``````/// @description d3d - enable 3d

var ret = global.__d3d;
global.__d3d = true;
//camera_apply(global.__d3dCamera);
gpu_set_ztestenable(true);
gpu_set_zwriteenable(true);
return ret;``````
draw_set_alpha_test:
Code:
``gpu_set_alphatestenable( argument0 );``
draw_set_alpha_test_ref_value:
Code:
``gpu_set_alphatestref( argument0 );``
__init_d3d: ( This initiates all of the different d3d constraints)
Code:
``````gml_pragma( "global", "__init_d3d();");
// setup the depth variable to a sensible default
global.__d3d=false;
global.__d3dDepth=0;
global.__d3dCamera=camera_create();
global.__d3dPrimKind = -1;
global.__d3dPrimTex = -1;
global.__d3dPrimBuffer=vertex_create_buffer();
vertex_format_begin();
global.__d3dPrimVF=vertex_format_end();
global.__d3dDeprecatedMessage = [ false ];

enum e__YYM
{
PointB,
LineB,
TriB,
PointUVB,
LineUVB,
TriUVB,
PointVB,
LineVB,
TriVB,
Texture,
Colour,
NumVerts,
PrimKind,
NumPointCols,
NumLineCols,
NumTriCols,
PointCols,
LineCols,
TriCols,

// these are used when building model primitives
V1X,
V1Y,
V1Z,
V1NX,
V1NY,
V1NZ,
V1C,
V1U,
V1V,

V2X,
V2Y,
V2Z,
V2NX,
V2NY,
V2NZ,
V2C,
V2U,
V2V,
};

enum e__YYMKIND
{
PRIMITIVE_BEGIN,
PRIMITIVE_END,
VERTEX,
VERTEX_COLOR,
VERTEX_TEX,
VERTEX_TEX_COLOR,
VERTEX_N,
VERTEX_N_COLOR,
VERTEX_N_TEX,
VERTEX_N_TEX_COLOR,
SHAPE_BLOCK,
SHAPE_CYLINDER,
SHAPE_CONE,
SHAPE_ELLIPSOID,
SHAPE_WALL,
SHAPE_FLOOR,
};``````
__init_view:
Code:
``````enum e__VW
{
XView,
YView,
WView,
HView,
Angle,
HBorder,
VBorder,
HSpeed,
VSpeed,
Object,
Visible,
XPort,
YPort,
WPort,
HPort,
Camera,
SurfaceID,
};``````
__view_set:
Code:
``````var __prop = argument0;
var __index = argument1;
var __val = argument2;

__view_set_internal(__prop, __index, __val);

var __res = __view_get(__prop, __index);

return __res;``````
__view_set_internal:
Code:
``````var __prop = argument0;
var __index = argument1;
var __val = argument2;

switch(__prop)
{
case e__VW.XView: var __cam = view_get_camera(__index); camera_set_view_pos(__cam, __val, camera_get_view_y(__cam)); break;
case e__VW.YView: var __cam = view_get_camera(__index); camera_set_view_pos(__cam, camera_get_view_x(__cam), __val); break;
case e__VW.WView: var __cam = view_get_camera(__index); camera_set_view_size(__cam, __val, camera_get_view_height(__cam)); break;
case e__VW.HView: var __cam = view_get_camera(__index); camera_set_view_size(__cam, camera_get_view_width(__cam), __val); break;
case e__VW.Angle: var __cam = view_get_camera(__index); camera_set_view_angle(__cam, __val); break;
case e__VW.HBorder: var __cam = view_get_camera(__index); camera_set_view_border(__cam, __val, camera_get_view_border_y(__cam)); break;
case e__VW.VBorder: var __cam = view_get_camera(__index); camera_set_view_border(__cam, camera_get_view_border_x(__cam), __val); break;
case e__VW.HSpeed: var __cam = view_get_camera(__index); camera_set_view_speed(__cam, __val, camera_get_view_speed_y(__cam)); break;
case e__VW.VSpeed: var __cam = view_get_camera(__index); camera_set_view_speed(__cam, camera_get_view_speed_x(__cam), __val); break;
case e__VW.Object: var __cam = view_get_camera(__index); camera_set_view_target(__cam, __val); break;
case e__VW.Visible: __res = view_set_visible(__index, __val); break;
case e__VW.XPort: __res = view_set_xport(__index, __val); break;
case e__VW.YPort: __res = view_set_yport(__index, __val); break;
case e__VW.WPort: __res = view_set_wport(__index, __val); break;
case e__VW.HPort: __res = view_set_hport(__index, __val); break;
case e__VW.Camera: __res = view_set_camera(__index, __val); break;
case e__VW.SurfaceID: __res = view_set_surface_id(__index, __val); break;
default: break;
};

return 0;``````
__view_get:
Code:
``````var __prop = argument0;
var __index = argument1;

var __res = -1;

switch(__prop)
{
case e__VW.XView: var __cam = view_get_camera(__index); __res = camera_get_view_x(__cam); break;
case e__VW.YView: var __cam = view_get_camera(__index); __res = camera_get_view_y(__cam); break;
case e__VW.WView: var __cam = view_get_camera(__index); __res = camera_get_view_width(__cam); break;
case e__VW.HView: var __cam = view_get_camera(__index); __res = camera_get_view_height(__cam); break;
case e__VW.Angle: var __cam = view_get_camera(__index); __res = camera_get_view_angle(__cam); break;
case e__VW.HBorder: var __cam = view_get_camera(__index); __res = camera_get_view_border_x(__cam); break;
case e__VW.VBorder: var __cam = view_get_camera(__index); __res = camera_get_view_border_y(__cam); break;
case e__VW.HSpeed: var __cam = view_get_camera(__index); __res = camera_get_view_speed_x(__cam); break;
case e__VW.VSpeed: var __cam = view_get_camera(__index); __res = camera_get_view_speed_y(__cam); break;
case e__VW.Object: var __cam = view_get_camera(__index); __res = camera_get_view_target(__cam); break;
case e__VW.Visible: __res = view_get_visible(__index); break;
case e__VW.XPort: __res = view_get_xport(__index); break;
case e__VW.YPort: __res = view_get_yport(__index); break;
case e__VW.WPort: __res = view_get_wport(__index); break;
case e__VW.HPort: __res = view_get_hport(__index); break;
case e__VW.Camera: __res = view_get_camera(__index); break;
case e__VW.SurfaceID: __res = view_get_surface_id(__index); break;
default: break;
};

return __res;``````
obj_control:
Create:
Code:
``````d3d_start();

//IMPORTANT NOTE:  All fake 3d objects should use the same texture.
//If they don't, you'll need to break up the vertex buffer by texture.
//Which also means that you'll need to use the depth buffer to handle occlusion.
//Which also means that you won't be able to properly use partially transparent sprites under some circumstances.
tex_fake3d = sprite_get_texture(spr_tree,0);

var _grid_fake3d_sprites = scr_sort_fake3d_sprites();

vertex_format_begin();
vertex_format_add_custom(vertex_type_float2,vertex_usage_colour);  //going to hold xy position of tree (for setting correct depth)
vf_fake3d = vertex_format_end();

//NOTE: Using colour vertex attribute to hold tree position.  If you need to use the colour attribute for actual colour, we can add a second colour attribute.

scr_fake3d_build_vertex_buffer(_grid_fake3d_sprites);
ds_grid_destroy(_grid_fake3d_sprites);``````
Step:
Code:
``__view_set( e__VW.Angle, 0, __view_get( e__VW.Angle, 0 ) + (2*(keyboard_check(vk_left)-keyboard_check(vk_right)))``
Draw:
Code:
``````d3d_set_projection_ortho(__view_get( e__VW.XView, 0 ),__view_get( e__VW.YView, 0 ),__view_get( e__VW.WView, 0 ),__view_get( e__VW.HView, 0 ),__view_get( e__VW.Angle, 0 ));
d3d_set_zwriteenable(true);
d3d_set_hidden(true);
draw_set_alpha_test(1);
draw_set_alpha_test_ref_value(64);

var _c = dcos(__view_get( e__VW.Angle, 0 ));
var _s = dsin(__view_get( e__VW.Angle, 0 ));
with(obj_character){
d3d_set_depth(-(-_s*x+_c*y));
draw_sprite_ext(sprite_index,image_index,x,y,image_xscale,image_yscale,-__view_get( e__VW.Angle, 0 ),c_white,1);
d3d_set_depth(0);
}

//we don't need alpha testing or the depth buffer as long as static instance sprites are drawn in order according to height (above ground).
draw_set_alpha_test(0);
d3d_set_zwriteenable(false);

//used in shader for orienting instances correctly, and for calculating depth:
-dsin(__view_get( e__VW.Angle, 0 )), dcos(__view_get( e__VW.Angle, 0 ))
);

//used in shader to move swaying objects:
current_time*0.004  //warning: probably better idea to use custom timer
);

vertex_submit(vb_fake3d,pr_trianglelist,tex_fake3d);

The draw GUI event is the same as it was in GMS1.

Thank you again for taking a sec to check it out! I feel like you and others have been above and beyond to help me out and i SUUUUUPPPPERRR appreciate it! This depth sorting thing has been a hassle and a half but I have also learned SOO much so I seriously appreciate any and all help you have given me! Thank you! )))

**Also side note, the scripts and the shader are the same as they were in GMS1

flyingsaucerinvasion

Member
Let's see if we can figure out what's going wrong here.

Oh, by the way, the only reason we needed a d3d_set_projection_ortho function in the draw event was to override the normal behavoir of d3d_start, which we only needed in GMS1 in order to be able to enable the depth buffer. So you can remove both of those command when using gms2.

Now, I suspect the problem might be related to using d3d_set_depth, which in GMS2 is translated into "global.__d3dDepth=argument0;" I can only presume that it is used in a similar manner as in gms1.

The first thing I would try is to substitute with depth = -(-_s*x+_c*y)

The next thing I would do is to verify that the draw event is getting the same "view_angle" value as the value used to draw the actual world. You will need to rely on your own knowledge of GMS2's view and camera system, because my experience is limited to gms1, and from reading the gms2 documentation.

Gizmo199

Member
Let's see if we can figure out what's going wrong here.

Oh, by the way, the only reason we needed a d3d_set_projection_ortho function in the draw event was to override the normal behavoir of d3d_start, which we only needed in GMS1 in order to be able to enable the depth buffer. So you can remove both of those command when using gms2.

Now, I suspect the problem might be related to using d3d_set_depth, which in GMS2 is translated into "global.__d3dDepth=argument0;" I can only presume that it is used in a similar manner as in gms1.

The first thing I would try is to substitute with depth = -(-_s*x+_c*y)

The next thing I would do is to verify that the draw event is getting the same "view_angle" value as the value used to draw the actual world. You will need to rely on your own knowledge of GMS2's view and camera system, because my experience is limited to gms1, and from reading the gms2 documentation.
Yeah I have tried the depth = -(-_s*x+_c*y) thing to no avail. And it doesn't seem to matter how I calculate the depth ( say changing the view_angle var to view_angle+90 or -view_angle or -view_angle+90 ) it still only switches at the same point in rotation. Around 45 degrees and 225

**I think it has something to do with the ztestenable or something...?

flyingsaucerinvasion

Member
You know what? The part of this conversation that pertains specifically to what we were doing with the gms1example project, we should probably carry on that conversation via private conversastion. The reason being that anybody who joins in on that part of the conversation would need to do a lot of catching up to be up to speed. So I'm going to switch to PM when discussing that for now.