3D Flipping a Layer?

Posho

Member
Hello :)

I've been trying to figure out how to do 3D with billboarded sprites. I've been reading a bit into how GMS2 works and found out everything is already being rendered in 3D. Turns out you can draw regular GM sprites in the XY plane and adjust their "Z" by using the layer's depth instead, which is useful if you want to draw regular sprites and be able to use all of GameMaker's sprite functions instead of having to rely on vertex buffer indexing.
2020-08-31_20-50-42.gif
The only thing I need to figure out is how to turn the layers so I can make a "billboard" effect with some sprites. I've looked everywhere I could think of, but it seems it's not really possible to rotate layers. Felt like I should at least ask the forum before giving up.

Does anyone know if it's possible at all?
 

TheouAegis

Member
If it's a sprite_layer you can flip the sprite. If it's a tilemap layer, you could loop through all tiles and flip them.

But no modifying the layer itself.
 

Yal

🍋 *lemon noises*
GMC Elder
Entities that use the default drawing can be flipped with image_xscale / image_yscale built-in variables (set them to -1 for inversion along that axis) and rotated with image_angle. If you draw the sprites yourself, you'd use draw_sprite_ext() to be able to provide scales and angles.
 

Posho

Member
Hey everyone, thank you for your replies. In the end I managed to achieve the effect I was looking for but using an approach with shaders rather than layer functions. The solution came from a video that was uploaded today by our good samaritan, @Dragonite.

I would still like to know if my issue is doable just to try to understand the intrinsics of GameMaker: Studio 2, however.


Can you explain what you mean by turn the layers?
Sure! Okay, so we know this is how a regular GameMaker sceene looks:
camscanner-09-05-2020-22-58-41.jpg

Ever since Studio 2 came, the rooms/scenes started to get drawn with layers, which are secretly 3D planes. And each layer's "Z" axis can be altered with the depth variable:
Webp.net-resizeimage.jpg

For instance, the duckie I was showing in the OP was a regular object with a regular sprite and no vertex functions being used. I was moving it by just altering the object's x, y and depth variables. The red line defines the X+ axis and the blue line is the Y- axis.
2020-08-31_20-50-42.gif

My initial question is if it is posible to rotate the sprite along the Y axis, like so:
Webp.net-resizeimage (1).jpg

If it's a sprite_layer you can flip the sprite. If it's a tilemap layer, you could loop through all tiles and flip them.

But no modifying the layer itself.
You mean the regular functions such as image_xscale, variables like image_angle and such?

Entities that use the default drawing can be flipped with image_xscale / image_yscale built-in variables (set them to -1 for inversion along that axis) and rotated with image_angle. If you draw the sprites yourself, you'd use draw_sprite_ext() to be able to provide scales and angles.
Yeah, I tried that. You can transform the image like that and it almost gives a convincing "turning" feel until you move the 3D camera to another angle.
 

TheouAegis

Member
I mean modifying sprites on a layer, not on instances. Look up Sprite Layer functions in the manual. It's the same principle, though, assuming you just have a sprite on a layer and not on an instance.

Rotating a sprite around an axis without venturing into real 3D convincingly is difficult. You'd have to take perspective scaling into account as well. Pixels need to scale up on the half closer to the camera, and scale down on the half farther from the camera, and as the sprite rotates more, the scaling would need to be adjusted to match.

It sounds like you are squishing the xscale by a static amount dependent on just the sprite doing the moving and such. You would need to track the ducky's "angle" and the camera's angle, then base the xscale on the relationship of the two angles. If the camera angle is 0 and ducky's angle is 180, you can see ducky at normal scale. If camera angle is 0 and ducky's angle is 135, ducky should be at half scale or whatever. (Or √2/2 scale, perhaps.)
 

Yal

🍋 *lemon noises*
GMC Elder
My initial question is if it is posible to rotate the sprite along the Y axis, like so:
Ah, that makes things more clear. If you want arbitrary transformations in 3D space, you're better off reading through the manual page on matrix functions - there's one that lets you build a transformation matrix with 3D shift, rotation (around all 3 axes) and scaling; set the world matrix to a transformation like that before you draw and it'll affect all drawing, then just change back to an identity matrix afterwards.

(And yes, this is about as complicated as it sounds, but GM's layers are basically forever cursed to be parallel to the XY plane and you'll need matrix transformations if you want an actual Z component to your drawing)


A slightly easier solution would be to draw the stuff you want to a surface, then map that surface to a 3D primitive (draw two textured 3D triangles that form a rectangle together; use some trigonometry to compute corners that are rotated into 3D space). Less work to pull off, but it's heavier on maths. (Matrix transforms are easier conceptually, but you need a lot of setup to get the entire pipeline set up)
 

Bart

WiseBart
There are quite a few ways to achieve the effect that you're after.
If you want to transform the entire layer then Yal's solution with the surface is the way to go. You then only have one primitive per layer and thus one pivot point (at the origin). As a consequence, things that have a high y value will appear to float somewhere high in the sky.
The matrix transform way will put all sprites upright along the z axis. You'll need to override the Draw event for that in each instance that needs to be drawn that way (parent objects could be useful here). Draw each sprite at (0, 0, 0) and put the translation to its (x, y, z) in the matrix transform so the sprite's origin is properly maintained, if that is the behavior you need (can't verify at the moment but I think the x, y, z offset is included in the vertex attribute in_Position which isn't what you want in this case, so you can't just use the default draw or its equivalent in code draw_self).
A last way would be to override the default shader by using layer_shader.
In the shader you can either add a 90° rotation about y or swap the y and z coordinates of in_Position.
Again, you'll need to determine whether the offset can be included in the actual vertex data or not.
But in this case I'd guess not since you're trying to turn the individual sprites into billboards (not putting the entire layer upright along the z axis using a primitive+surface) and the offset to the sprite's location needs to go in the matrix transform.
 
If you want to rotate individual sprites around their y axis, you'll need to have a point around which to rotate. Unfortunately, this means you'll need to add extra information that isn't contained in the vertex format that gamemaker uses by default. which means default sprite functions aren't going to work.

You don't want to do an individual matrix transform for every sprite, if there are a lot of sprites to be drawn in this way, because every time you set a matrix it interrupts the current draw batch. If you do that enough times it will degrade performance. However, if there aren't a huge number of sprites to be drawn this way, it will be alright.

Anything that doesn't change over time (or only changes in simple deterministic ways), can all be put into one vertex buffer which only needs to be written once. To do y-axis billboarding, the vertex format will need to contain a) the 3d point around which to rotate), and b) the 2d offset of each vertex from that point. The shader just adds the 2d offset transformed by some rotation to the 3d point position.
 

Posho

Member
I mean modifying sprites on a layer, not on instances. Look up Sprite Layer functions in the manual. It's the same principle, though, assuming you just have a sprite on a layer and not on an instance.

Rotating a sprite around an axis without venturing into real 3D convincingly is difficult. You'd have to take perspective scaling into account as well. Pixels need to scale up on the half closer to the camera, and scale down on the half farther from the camera, and as the sprite rotates more, the scaling would need to be adjusted to match.

It sounds like you are squishing the xscale by a static amount dependent on just the sprite doing the moving and such. You would need to track the ducky's "angle" and the camera's angle, then base the xscale on the relationship of the two angles. If the camera angle is 0 and ducky's angle is 180, you can see ducky at normal scale. If camera angle is 0 and ducky's angle is 135, ducky should be at half scale or whatever. (Or √2/2 scale, perhaps.)
Ah, that makes things more clear. If you want arbitrary transformations in 3D space, you're better off reading through the manual page on matrix functions - there's one that lets you build a transformation matrix with 3D shift, rotation (around all 3 axes) and scaling; set the world matrix to a transformation like that before you draw and it'll affect all drawing, then just change back to an identity matrix afterwards.

(And yes, this is about as complicated as it sounds, but GM's layers are basically forever cursed to be parallel to the XY plane and you'll need matrix transformations if you want an actual Z component to your drawing)


A slightly easier solution would be to draw the stuff you want to a surface, then map that surface to a 3D primitive (draw two textured 3D triangles that form a rectangle together; use some trigonometry to compute corners that are rotated into 3D space). Less work to pull off, but it's heavier on maths. (Matrix transforms are easier conceptually, but you need a lot of setup to get the entire pipeline set up)
There are quite a few ways to achieve the effect that you're after.
If you want to transform the entire layer then Yal's solution with the surface is the way to go. You then only have one primitive per layer and thus one pivot point (at the origin). As a consequence, things that have a high y value will appear to float somewhere high in the sky.
The matrix transform way will put all sprites upright along the z axis. You'll need to override the Draw event for that in each instance that needs to be drawn that way (parent objects could be useful here). Draw each sprite at (0, 0, 0) and put the translation to its (x, y, z) in the matrix transform so the sprite's origin is properly maintained, if that is the behavior you need (can't verify at the moment but I think the x, y, z offset is included in the vertex attribute in_Position which isn't what you want in this case, so you can't just use the default draw or its equivalent in code draw_self).
A last way would be to override the default shader by using layer_shader.
In the shader you can either add a 90° rotation about y or swap the y and z coordinates of in_Position.
Again, you'll need to determine whether the offset can be included in the actual vertex data or not.
But in this case I'd guess not since you're trying to turn the individual sprites into billboards (not putting the entire layer upright along the z axis using a primitive+surface) and the offset to the sprite's location needs to go in the matrix transform.
If you want to rotate individual sprites around their y axis, you'll need to have a point around which to rotate. Unfortunately, this means you'll need to add extra information that isn't contained in the vertex format that gamemaker uses by default. which means default sprite functions aren't going to work.

You don't want to do an individual matrix transform for every sprite, if there are a lot of sprites to be drawn in this way, because every time you set a matrix it interrupts the current draw batch. If you do that enough times it will degrade performance. However, if there aren't a huge number of sprites to be drawn this way, it will be alright.

Anything that doesn't change over time (or only changes in simple deterministic ways), can all be put into one vertex buffer which only needs to be written once. To do y-axis billboarding, the vertex format will need to contain a) the 3d point around which to rotate), and b) the 2d offset of each vertex from that point. The shader just adds the 2d offset transformed by some rotation to the 3d point position.
Yeah, I assumed it would be like that. I have read the layer functions and was dumbfounded that you can pretty much alter a layer's X, Y and depth and the angle along XY but not with depth. Vertex buffering seems a lot more ideal if you want to convincingly rotate a 2D sprite in a 3D space rather than awkwardly defining transformations from a base layer itself. Just wanted to check if it was doable without having to rely entirely on 3D functions.

Thanks everyone for your replies!
 

Yal

🍋 *lemon noises*
GMC Elder
Anything that doesn't change over time (or only changes in simple deterministic ways), can all be put into one vertex buffer which only needs to be written once. To do y-axis billboarding, the vertex format will need to contain a) the 3d point around which to rotate), and b) the 2d offset of each vertex from that point. The shader just adds the 2d offset transformed by some rotation to the 3d point position.
Just to clarify, you can have a non-frozen vertex buffer and edit it whenever you want, but freezing a buffer (which means, give it to the GPU and tell it it won't ever change in the future) has much better performance in the long run, since you only transfer it to the GPU once (while a volatile buffer needs to be transferred in its entireity every time you draw it). From my experiments, a full 3D model of a stage takes about as much time to draw as a single sprite if it's in a frozen buffer, so we're talking pretty huge savings (though of course that depends on how good your GPU is - more vertices per draw call means it can handle a larger model in the same time)

Basically, GPUs are optimized around doing a small amount of really big draw calls these days (high-def 3D models and such) and they suck at making tons of really small draw calls (e.g. individually drawing GM sprites from within a GML for loop).
 

Dragonite

Member
Oh so that's why you were asking me about these.
Just to clarify, you can have a non-frozen vertex buffer and edit it whenever you want, but freezing a buffer (which means, give it to the GPU and tell it it won't ever change in the future) has much better performance in the long run, since you only transfer it to the GPU once (while a volatile buffer needs to be transferred in its entireity every time you draw it). From my experiments, a full 3D model of a stage takes about as much time to draw as a single sprite if it's in a frozen buffer, so we're talking pretty huge savings (though of course that depends on how good your GPU is - more vertices per draw call means it can handle a larger model in the same time)

Basically, GPUs are optimized around doing a small amount of really big draw calls these days (high-def 3D models and such) and they suck at making tons of really small draw calls (e.g. individually drawing GM sprites from within a GML for loop).
I guess this is getting kind of off-topic, but in my experience the gains from frozen vertex buffers are negligible at small scales (drawing two triangles for a quad) and I've seen people make the argument that it may actually be slower, but the time savings quickly adds up for bigger and bigger meshes. For static level geometry, I've found much the same thing that you have - drawing ten thousand triangles in a frozen vertex buffer might as well be as fast as drawing ten triangles in a frozen vertex buffer. Obviously, computers are machines with a finite capacity, and eventually you will run into the limitations, especially on integrated GPUs, so if you can get away with smaller meshes you should do it.

GameMaker *appears* to do some kind of sprite batching in "normal" 2D mode though. I'm not exactly sure where the position arguments in draw_sprite go, but drawing sprites in a loop that way certainly seem quite a bit faster than drawing sprites in a loop at (0, 0) with a matrix transform.
 

Yal

🍋 *lemon noises*
GMC Elder
drawing ten thousand triangles in a frozen vertex buffer might as well be as fast as drawing ten triangles in a frozen vertex buffer.
It should be basically exactly the same speed (from the CPU's point of view, at least) since you just tell the GPU "draw vertex buffer 43" (fixed size message without the vertex buffer data - that's what is sent over when you freeze it) and all the data already is GPU-side...

GameMaker *appears* to do some kind of sprite batching in "normal" 2D mode though. I'm not exactly sure where the position arguments in draw_sprite go, but drawing sprites in a loop that way certainly seem quite a bit faster than drawing sprites in a loop at (0, 0) with a matrix transform.
It's possible all drawing gets put into a vertex buffer behind the scenes, with separate buffers between layers (you can have layer-specific shaders now so the layers definitely break batches up) and also when you have a different texture page for the next thing to draw. That's just wild guessing, though. (Doing a matrix transform instantly breaks a batch so it's possible the slowdown you see is because of basically ruining batching entirely for yourself)
 
GameMaker *appears* to do some kind of sprite batching in "normal" 2D mode though. I'm not exactly sure where the position arguments in draw_sprite go, but drawing sprites in a loop that way certainly seem quite a bit faster than drawing sprites in a loop at (0, 0) with a matrix transform.
The position argument in draw_sprite is not made available through any vertex attribute using the default sprite drawing methods. The sprite position is added to the transformed vertex offsets, and that is what is put into in_Position.

Every matrix set will break the current vertex batch when it comes to the automatic batching that gamemaker does when drawing sprites and some other things. Same will happen when setting shader uniform variables.
 

Dragonite

Member
It should be basically exactly the same speed (from the CPU's point of view, at least) since you just tell the GPU "draw vertex buffer 43" (fixed size message without the vertex buffer data - that's what is sent over when you freeze it) and all the data already is GPU-side...
Nono, I mean the act of telling GameMaker to draw a frozen vertex buffer takes some constant amount of time - particularly if you're setting the world transform or shader uniforms each time - vs baking all of that information into a single vertex buffer, freezing it, and drawing it in one shot. Frozen vertex buffers are great.

It's possible all drawing gets put into a vertex buffer behind the scenes, with separate buffers between layers (you can have layer-specific shaders now so the layers definitely break batches up) and also when you have a different texture page for the next thing to draw. That's just wild guessing, though. (Doing a matrix transform instantly breaks a batch so it's possible the slowdown you see is because of basically ruining batching entirely for yourself)
My wild guess would be that it's something along the lines of this, with batch-breaking operations (color, texture page, matrices, etc) ending the batch, then doing this, and then starting over. I've never seen YYG talk about how they render sprites from the inside though, and I'm not that curious that I want to try to reverse engineer it ¯\_(ツ)_/¯

And yeah, setting the world matrix kinda sucks and should probably be done sparingly if possible - I just don't know what else might be going on in addition.
 
Top