Getting the angle of billboarded Sprite3D

Godot Version

Godot 3

Question

Hi, I’m making a monster-tamer in Godot that uses 2D sprites for characters in a 3D environment. The monsters are billboarded to always face the camera.

I’m currently adding particle effects for when they suffer status ailments like Burned, Soaked, Poisoned, etc. To do this, I made some particles come off of them. This works by placing the particle object at a Y value equal to the monster’s height. You can see an example with water droplets correctly dripping down this walrus’ head.

This works great when the camera is directly head-on. However, when the camera pans left or right, it becomes obvious that the particle effect is dripping in front of its face, and not directly over it.

Now it makes perfect sense why that is. The sprite is rotating to face the camera, and whatever the object’s Y position is won’t reflect where the top of the sprite actually ends up.

My question is now: how do I get the relative Y and Z position to place my particle system properly? I’ve kind of been reverse-engineering the billboarding method, but some of it is going over my head.

I’m fairly sure the answer lies in normalized vectors, a topic I’m not overly familiar with. But the ultimate goal is to basically apply billboarding logic to the coordinates I’m spawning the particle system on through GDScript. And even better, to rotate the particle system to match the Sprite3D’s rotation.

Any help is appreciated. Thanks!

What you’re describing shouldn’t be an issue if the right settings are selected for your GPUParticles3D node.

This tutorial goes over how to make particles always face the camera. There are plenty of others, but I remembered this one:

So I’m not concerned with getting my particles themselves to face the camera, but rather aligning the translation of the node that they’re in with the height of a separate billboarded Sprite3D.

My issue isn’t that the particles themselves are emitting in any weird way, but rather that their spawn point doesn’t align with the height of the sprite they’re supposed to go on top of because the sprite’s height is skewed relative to the camera.

So for example: if I wanted to spawn these water droplets on top of a cube with a height of 1, I could just spawn them in with a Y value of 1. However, when trying to spawn them on top of a Sprite3D with a height of 1, it gets misaligned when the camera moves because the Sprite3D skews for its billboard effect.

So what I’m looking for is a way to dynamically set the particle node’s Y and Z values in a way that it stays aligned with the top of the Sprite3D relative to the camera. I hope that makes sense.

That does make sense. It would help to see your code. My initial thought would be to just put a Marker3D as a child of the object and move it to the top, and then use its global_position. Should be correct every time.

Thanks for the suggestion. I tried to add a Marker3D but it seems like that’s exclusive to Godot 4 and I’m using 3. Regardless, it gave me the idea to try it with a Spatial node and using the global_translation. Same result I’m afraid, regardless of whether the Sprite3D is its parent or not. I think that’s because the actual dimensions of the sprite are never changing (it’s treated as if it were a straight upward sprite regardless of what Godot is drawing).

My code is pretty simple right now as I’m just placing it at a heightened y value. The sprites are billboarded through a shader if that’s relevant, like this:

void vertex()
{
	MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],CAMERA_MATRIX[1],CAMERA_MATRIX[2],WORLD_MATRIX[3]);
}

And then the particle is spawned in like this via a script:

particle.translation = Vector3(0,info["height"],0)

Using the height variable of the unit to be placed appropriately high up. But I’m suspecting the solution will need to be something like:

particle.translation = Vector3(0,info["height"], [how far back the sprite is leaning] ).normalized()

But I have no idea how to calculate that. The shader multiplies the camera matrix by its inverse, so I guess I’m trying to figure out how to get that info in GDScript? A bit out of my depth here.

I completely missed the Godot 3 part. Which version of 3 are you using? Also, what’s preventing you from upgrading?

I’ve got 3.5.1 - I’ve been on this project since before Godot 4 released. :sweat_smile: I tried upgrading when it first came out, but the transition wasn’t smooth, and I had to actually work on the game, so it just never happened. It’s alright; Godot 3’s been serving me very well so far.

I did a 2.5 game in 4.3 .NET and didn’t have this issue, though none of my particle effects were at the top of the sprite. My other thought would be to use an AnimationPlayer, and use pixel animations instead of a shader.

Unfortunately I haven’t used 3.5 since 2023, so I don’t have a lot of ideas.


This has been solved! The billboarding no longer happens via shader, but instead the whole object rotates to face the camera with this easy code:
global_rotation = get_viewport().get_camera().global_rotation

So now any children of the sprite will inherit the rotation! I can place my particle node at the height I want, and it will move alongside its parent sprite.

1 Like