The text on the label is the same size always, period.
The label does not exist in 3D space, it has no concept of depth.
You already know how to convert a point from 3D space to 2D.
I present a few ways to go about this.
By the way, I remind you that the current camera can change.
Scale by the camera projection
Here is an idea: convert another point slightly offset vertically.
Let us start with your code:
var offset = Vector2(label.get_size().x / 2, 0)
label.rect_position = camera.unproject_position(point) - offset
We need another point just offset on y axis:
var offset = Vector2(label.get_size().x / 2, 0)
label.rect_position = camera.unproject_position(point) - offset
var another_point = Vector3(point.x, point.y + v_size, point.z)
Where v_size
is, I don't know, 1
? You can make it the vertical size of the label in 3D.
And we "unproject" that too:
var offset = Vector2(label.get_size().x / 2, 0)
label.rect_position = camera.unproject_position(point) - offset
var another_point = Vector3(point.x, point.y + v_size, point.z)
var another_point_2d = camera.unproject_position(another_point_2d) - offset
Then we take the distance, and use that as scale factor:
var offset = Vector2(label.get_size().x / 2, 0)
label.rect_position = camera.unproject_position(point) - offset
var another_point = Vector3(point.x, point.y + v_size, point.z)
var another_point_2d = camera.unproject_position(another_point_2d) - offset
label.rect_scale = base_scale * label.rect_position.distance_to(another_point_2d)
Where base_scale
is some Vector2
that has the size normal scale of the label. If v_size
is the height you want the label to have in 3D space, then base_scale
is simply Vector2(1, 1)
. However, if you let v_size
be 1
, you would want to tweak base_scale
to something you like. You can either tweak base_scale
or v_size
or both.
Scale by field of view
To be fair, that is not the only way to go about it. Since you know both the position of the camera and the position of the object the label is supposed to be attached to…
You can easily compute the distance:
var distance = camera.global_transform.origin.distance_to(global_transform.origin)
By the way, if you want to use the distance along the direction the camera is looking, you can project over the the z basis from its transform:
var difference = global_transform.origin - camera.global_transform.origin
var distance = difference.project(camera.global_transform.basis.z).length()
Either way, you just use some scale factor divided by the distance:
var scale = factor * get_viewport().size.y / distance
label.rect_scale = Vector2(scale, scale)
And you tweak the factor
to your liking.
What about the field of view? Right.
Computing the screen size of 3D object given its real size, the field of view and distance took me some figuring out. This is how I made sense of it:
First we will find the size the screen would take on the 3D world at the given distance from the camera. Let us consider half the size. So we have a right triangle with the camera one the corner, one side is in the direction the camera is looking and its size is the distance, the angle there is half the field of view:
3d_screen_size/2
┌────────────
│ ⟋
│ ⟋
distance │ ⟋
│ ⟋
│ ⟋
│⟋ <- FOV/2
camera
So that 3d_screen_size
is distance * 2 * tan(FOV * 0.5)
.
We can then compute what proportion of that our object occupies:
proportion = object_size / 3d_screen_size
And scale that with the view port size:
size.y = viewport.size.y * proportion
That is:
size.y = viewport.size.y * object_size / (distance * 2 * tan(FOV * 0.5))
Actually, let us get a scale:
scale = viewport.size.y / (distance * 2 * tan(FOV * 0.5))
So we can do this:
var scale = factor * get_viewport().size.y / (distance * 2 * tan(camera.fov * 0.5))
label.rect_scale = Vector2(scale, scale)
Again I have added a factor for you to tweak, because honestly, it is going to look huge on normal settings (considering that the size of the label in pixels is being taken as a size in 3D).
By the way, since you have some factor
and - assuming no changes in the camera field of view - 2 * tan(camera.fov * 0.5)
is just some constant. Then you probably can simply have some constant. And trigonometry stuff is not needed. Yeah, all that figuring out is just throw a constant factor at it.
Errata: The above method works until the field of view reaches 90º (a quarter turn). Then the tan
formula will give you negatives.
Addendum: So here is yet another way to figure out the scale that does not have the problem I mentioned above:
var difference = global_transform.origin - camera.global_transform.origin
var depth = difference.project(camera.global_transform.basis.z).length()
var ref = camera.project_position(Vector2.ZERO, 1)
var scaled_ref = camera.project_position(Vector2.ZERO, depth)
var regular_size = ref - ref.project(camera_transform.basis.z)
var scaled_size = scaled_ref - scaled_ref.project(camera_transform.basis.z)
var scale = regular_size.length() / scaled_size.length()
rect_scale = scale_factor * Vector2(scale, scale)
The code projects the top left corner of the viewport to 3D space using the camera projection at two different depths: 1, and the distance to the object along the view line of the camera (depth).
That should give you points in the top left line of the viewing frustum (within rounding error), at depth 1, and at the depth of the object.
Then the code projects them into the view line of the camera, and take the difference. This gives you two vectors normal to the view line of the camera, so we an use their relative size for scaling.
Render to a texture
And those aren't the only ways to go about this.
You can render the Label
to a texture by placing it inside a Viewport
node with its own Camera2D
. And then make a ViewportTexture
from it and set it to a 3D object.
See the official 2D in 3D demo project