How do I render Multimesh with different depths in 2D?
Asked Answered
A

6

0

Good day all, I write this because recently I've been having some problems with rendering for MultimeshInstance2D. I'm building a topdown game in the 2D engine, and I'm using grass with every blade a mesh rendered with MultiMeshInstance2D.

The problem is that when my character walks through the grass, she's rendered either behind all the blades or in front of all of them, depending of the order of the nodes. This could be an easy fix if I could have access to the depth buffer, how high an objects is in the screen would dictate the depth in the depth buffer. Thing is I don't know how you can write to the depth buffer on Godot 2D shaders. I want to avoid transferring the project to 3D.

So ideally grass that is lower on the screen to a character would be rendered in front of it, and grass higher up would be drawn behind her. How do I render it that way?

Thanks for reading!

Agave answered 11/12, 2023 at 0:50 Comment(0)
F
1

maybe you can make two different objects sharing the texture, one to draw in front of the player, and the other to draw behind. then mask them depending on the position of the player. godot can do masking using Light2D node, but for this case a custom shader might be more convenient

alternatively use a YSort node and have each grass blade be its own node. in that case the player needs to be a child of YSort as well. if the grass blades are all instances of the same texture file, that might be more efficient memory-wise.

Fauces answered 12/12, 2023 at 15:12 Comment(0)
A
0

SOLUTION:

Alright it's been a while, from what I understand you cannot write to depth buffer in Godot 2D, but by looking at Fauces suggestion (thank you very much btw) I came up with a hacky solution that works surprisingly well, didn't have to draw the object twice or switch to 3D or abandon multimeshes. I kept the Multimesh and all those things, but sent to the shader as uniforms my main player's current sprite and position. The idea is that, for pixels of instanced objects whose instance position is behind the character, in the fragment shader, if the pixel overlaps with where the character sprite is on the screen (I calculate this using the unforms I mentioned), I make this pixel completely transparent. So pixels that belong to instanced objects behind the character, with the pixel overlapping a visible pixel in the character sprite are not drawn. Implementing this way I chose the character to be rendered before any of the instanced objects, so the not overlapping and/or not behind pixels are drawn on top of the character. I don't know if anyone would be interested in the code for this, I'm not posting rn because I had to use a lot of arbitrary offsets because I don't really understand Godot positions of scenes relative to each other or things like that probably, so I had to allign some thing by hand. Again thanks eternavoid!

Agave answered 18/12, 2023 at 5:34 Comment(0)
A
1

Agave
that sounds very interesting! would love to take a look at the project. do you think this could work with multiple characters/enemies too?

Apache answered 8/3 at 10:57 Comment(0)
A
2

Apache It's nice to see I'm not the only one interested in this!
First I'll leave the relevant part of the source code, hope it's not too confusing since it's not the cleanest implementation. I clarify the heroine is just the name for the character in this case.

        //Don't draw pixels behind heroine
	//position = position of the fragment in the world
	//heroine_side = side length of the texture (texture is square), could be a uniform but hardcoded here
	//character_present = uniform that is true if the character is in the scene
        //heroine_flip = uniform that is true if the heroine sprite is flipped
        //heroine_position = the position of the character in the world
        //center = position of the bottom of each instanced grass blade

	//Here we calculate the uv of the heroine appropriate for the position of the fragment
	//This so we can sample the heroine/character texture

        //Because the position is the center of the texture, we want the corner of where the texture sprite would start
	vec2 heroine_corner = heroine_position - vec2(heroine_side, heroine_side) * 0.5;
	vec2 heroine_uv = (position - heroine_corner) / heroine_side;
	heroine_uv.x = max(0.0, min(1.0, heroine_uv.x));
	heroine_uv.y = max(0.0, min(1.0, heroine_uv.y));
	
	//This is in case the sprite is flipped
	vec2 flipped_heroine_uv = vec2((1.0 - heroine_uv.x), heroine_uv.y);
	heroine_uv = heroine_flip * flipped_heroine_uv + (1.0 - heroine_flip) * heroine_uv;
	
	//Condition if the blade of grass is actually located behind the character in the world
        //Since it's top down view, grass with a higher y value should be technically behind the character
        //That is the grass that should not be rendered on top of the character
	float behind_character = float(center.y < (heroine_position.y));
	
	//Condition if the FRAGMENT is rendered in the same place as a non invisible pixel on the screen
	//We use the alpha value, the 0.6 is chosen instead of 0 to avoid blurrier edges from the character
	float not_visible = float(texture(heroine_sprite, heroine_uv).w >= 0.6);
	
	//If these three conditions are true, the grass is invisible
	COLOR = COLOR - COLOR * round(behind_character * not_visible * character_present);

It's worth noting that the scenes are ordered so without the shader the grass renders on TOP of the character. This way we technically render all the grass and just make a portion invisible in some conditions.

Here's what it looks like when the character is invisible, you can see a part of the grass is simply not rendered.

As for your question: I absolutely think this could work with a lot of enemies, tho you'd have to be careful with memory bandwidth if optimization is important to the project, because of all the textures that would be transferred to the GPU each frame. Shouldn't be a problem but worth keeping in mind.

Hope this was informative and the code isn't too bad!

Agave answered 8/3 at 20:9 Comment(0)
Z
1

Agave Hope this was informative and the code isn't too bad!

Yeah, it was extremely interesting and informative. 📖

To display the code correctly, you should place three ``` signs above and below it.

``` 
code
``` 
Zosima answered 8/3 at 21:3 Comment(0)
A
1

thanks for the code! 🙂 i tried it out and its interesting for sure. worked quite well for a couple characters, but when i tried like 15+ i started getting less fps. but i have quite large textures for my characters. so not for my project but if you only have a couple of chars and need a lot of y sorted meshes it could be a very viable approach i think!

Apache answered 11/3 at 18:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.