I want my particles to be behind my sprite - I set the z position to be lower than the sprite, it goes behind in the editor, but in the game, it’s in front.
I’ve read this thread Particles Z-Order and the solution involves separating the sprites from the particles with predicates, or using the GUI.
I could solve my issue by disabling z testing and making various render predicates (I would need to differentiate between background sprites, particles, and foreground sprites), but if z depth should work (as I’ve heard), I want to use it.
Also there is different behaviour for Z sorting for particles, in the Editor vs Runtime.
Editor:
Runtime:
This is a blank project which I’ve just added a sprite and a particle emitter into, and a camera with Orthographic and zoom of 4. I’ve attached the project:
particle test.zip (31.3 KB)
Quick overview:
- Sprite z is
0.1
- ParticleFx z is
-0.1
- Both sprite and particlefx are on the same game object
- Camera component:
- Near:
1.0, Far: -0.1
- Orthographic
- Zoom: 4
1 Like
I recommend learning how basic rendering works in order to solve these kinds of problems.
See line 223 of the default render script:
render.set_depth_mask(false)
This happens just before drawing the tile and particle predicates, where the former refers to your sprite and the latter refers to your particles.
Next, see lines 229 - 230:
render.draw(predicates.tile, draw_options_world)
render.draw(predicates.particle, draw_options_world)
Your sprite is drawn first, then your particles are drawn second. Because the depth mask is disabled, the z buffer is not written to, so your z positions across predicates have no effect.
To fix this, simply enable the depth mask as needed.
Ah, thanks for pointing that out! I thought render.enable_state(graphics.STATE_DEPTH_TEST) at 217 was all I needed to be concerned about, so I missed the render.set_depth_mask(false)line. Since you pointed that out, I was able to better consult the docs (and some AI to further help with that, since they’re a bit sparse), I see that STATE_DEPTH_TEST is for enabling depth comparisons, and render.set_depth_mask()is for enabling depth writing.
But if we simply enable the depth mask, all of the “transparent” parts of the sprite’s quads show up as a black square around the sprite. Eg. here’s what happens if I disable the depth mask to after sprites, and before particles;
render.draw(predicates.tile, draw_options_world)
render.set_depth_mask(false)
render.draw(predicates.particle, draw_options_world)
I think what threw me off is Unity has an extra transparency mode called “Cutout”, which expects alpha to be completely on or off for each pixel, and you don’t have to worry about normal transparency issues in this case because you can use the depth buffer as normal. Really good for pixel art sprites, foliage, etc.
I could disable the depth buffer and group things into different predicates, but AI gave me a tip on how to make the “cutout” approach work in Defold, so I’ll explore this - since I will NEVER have semi-transparent pixels in my pixel art games! Not on my watch…
Mwahahahahahahahaaaa…
“Cutout” fragment shader! No new predicates. Depth testing just “works”, with transparency. Thanks to ChatGPT for speeding things along. I understand what’s written but it saved me a tonne of time since I’m not super used to GPU code yet.
#sprite.fp "Cutout" transparency
#version 140
in mediump vec2 var_texcoord0;
out vec4 out_fragColor;
uniform mediump sampler2D texture_sampler;
uniform fs_uniforms
{
mediump vec4 tint;
// Optional: you can add a configurable cutoff if desired
// mediump float alpha_cutoff;
};
void main()
{
// Sample texture
mediump vec4 tex_color = texture(texture_sampler, var_texcoord0.xy);
// ---- Alpha Cutout ----
// Discard pixels below threshold (0.5 = standard cutoff)
if (tex_color.a < 0.5)
discard;
// Pre-multiply alpha since all runtime textures already are
mediump vec4 tint_pm = vec4(tint.xyz * tint.w, tint.w);
//Final colour
out_fragColor = tex_color * tint_pm;
}
Modified project
particle test.zip (35.6 KB)
2 Likes