Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Label3D node. #60386

Merged
merged 1 commit into from
Apr 25, 2022
Merged

Implement Label3D node. #60386

merged 1 commit into from
Apr 25, 2022

Conversation

bruvzg
Copy link
Member

@bruvzg bruvzg commented Apr 20, 2022

Implements godotengine/godot-proposals#3219

Screenshot 2022-04-20 at 10 29 17

  • Implements Label3D node, with most of the Label features (except overrun, and visible characters).
  • Adds generate_mipmap font import option.
  • Adds some missing flags to the Sprite3D.

A minor convenience for normal fonts, but required to properly render MSDF fonts at arbitrary scale without 2D-in-3D viewport resolution restrictions.

MSDF font zoom - click to expand
Screen.Recording.2022-04-20.at.10.24.46.mov

@timothyqiu
Copy link
Member

  • If the Font's data changes, i.e. replaced with another ttf file, the Label3D is not updated.
  • if I reimport the font in-use, Label3D won't show anything until I reopen the scene.
  • Clearing Font property crashes the editor.

@bruvzg
Copy link
Member Author

bruvzg commented Apr 20, 2022

This line crashes when I create a new Label3D, the new label has a null Font.
If the Font's data changes, i.e. replaced with another ttf file, the Label3D is not updated.
if I reimport the font in-use, Label3D won't show anything until I reopen the scene.
Clearing Font property crashes the editor.

All should be fixed now.

@Chaosus
Copy link
Member

Chaosus commented Apr 20, 2022

And it's possible to control a "depth" (volume on z axis) for the symbols in that label? If not, that would be interesting to add.

@bruvzg
Copy link
Member Author

bruvzg commented Apr 20, 2022

And it's possible to control a "depth" (volume on z axis) for the symbols in that label? If not, that would be interesting to add.

No, with this approach, each glyph is a flat quad with a character texture, so there's no depth.

There's a way to get Bézier shapes from the font (if it's a dynamic font), but they should be converted to polygons, triangulated and "extruded" to form a mesh. And last time I have checked, the triangulation algorithm included in the Godot wasn't working well on polygons with holes, so it might need much more work. Also, intersections of the chars probably will need some care to seamlessly connect.

I was thinking about implementing this as well, but probably as a separate TextMesh.

@Calinou
Copy link
Member

Calinou commented Apr 20, 2022

I did a quick test, it works great so far!

image

With custom fonts generated using tools such as Fontello, it's possible to use arbitrary monochrome vector graphics:

image

Using the generated mesh AABBs, it's possible to position Label3Ds relative to each other via code. Make sure to use call_deferred() to ensure the meshes are updated before using their AABBs for positioning:

simplescreenrecorder-2022-04-20_15.51.25.mp4

Example with MSDF fonts (Godot icon is broken here, but font is OK):

image

MSDF fonts look better when up close, but also when far away as they don't get grainy like DynamicFonts do.

Testing project: test_label_3d_1.zip


With the latest revision of this PR, creating a Label3D for the first time results in the following error message:

scene/3d/label_3d.cpp:423 - Condition "font.is_null()" is true.

Some more feedback:

  • I think it would be good if the default project DynamicFont was used when no font is defined. This would allow for prototypes and level designers to quickly use Label3D for labeling various areas in levels during the blockout phase without having to specify a font.
  • Also, while alpha blending should remain the default, there should be a property to use alpha scissor instead to avoid transparency sorting issues. This will look less smooth, but temporal antialiasing or MSAA alpha to coverage will be able to smooth it out. This property may also be relevant when rendering MSDF fonts, not just DynamicFonts.
  • In the Flags section, there could be On Top and Fixed Size properties to force drawing the Label3D on top of other 3D nodes, and/or force drawing at a fixed size regardless of the distance to the camera.
  • The Material Override property in the GeometryInstance3D section should be hidden on Label3D nodes, as setting it will break font rendering. Material Overlay doesn't break font rendering, but it looks weird out of the box, so it might be worth hiding too.
  • Unlike 2D Label, writing characters not present in the font will not result in Unicode "tofu" appearing. This should be addressed if possible for consistency.
  • Should Outline Modulate be black instead of white by default? Most people will probably be using black outlines. See also Use black for font outlines by default instead of white #54641.

Edit: Disregard the accidental assignment below.

@Calinou Calinou assigned Calinou and unassigned Calinou Apr 20, 2022
scene/3d/label_3d.cpp Outdated Show resolved Hide resolved
scene/3d/label_3d.cpp Outdated Show resolved Hide resolved
@reduz
Copy link
Member

reduz commented Apr 20, 2022

@bruvzg You are just too awesome.

@bruvzg
Copy link
Member Author

bruvzg commented Apr 21, 2022

  • Added fallback to default project theme font, if no Font (or font with no FontData) is set.
  • Now it's drawing invalid chars (as a solid rect, without hex-code texture).
  • Added alpha cut modes (same as Sprite3D), alpha scissor is working fine with MSDF, but not so much with anti-aliased non MSDF fonts (needs alpha_scissor_threshold adjustment).
  • Material Override and Material Overlay properties are hidden.
  • Added auto wrap / fill align support.
  • Added no_depth_test and fixed_size flag to draw it always on top, and without depth scaling. Also added both flags to Sprite3D for consistency. And changed base material cache from Vector to HashMap to avoid it growing too big
  • Add BiDi override support and moved default BiDi override function from Control to TextServer
Screen.Recording.2022-04-21.at.15.54.39.mov

@bruvzg
Copy link
Member Author

bruvzg commented Apr 21, 2022

Example with MSDF fonts (Godot icon is broken here, but font is OK):

That's a common issue most SVG to font / MSDF tools have, and related to the multiple overlapping color layers in the source icon. Converting source SVG to the single monochrome path usually helps:

<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg"><path style="fill:#478cbf;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.33333" d="M417.615 76.875c-42.392 9.424-84.326 22.545-123.642 42.334.899 34.716 3.143 67.98 7.693 101.768-15.268 9.782-31.315 18.177-45.576 29.628-14.49 11.148-29.29 21.813-42.41 34.85-26.212-17.337-53.955-33.63-82.535-48.012C100.337 270.6 71.53 306.383 48 346.428c18.493 29.029 38.33 58.205 56.7 80.959v245.765c.449.004.898.021 1.343.063l150.67 14.527a16.223 16.223 0 0 1 14.627 15.024l4.646 66.51 131.43 9.376 9.055-61.384a16.222 16.222 0 0 1 16.05-13.858h158.961c8.047 0 14.873 5.899 16.047 13.858l9.055 61.384 131.434-9.377 4.642-66.51a16.23 16.23 0 0 1 14.627-15.023l150.611-14.527c.446-.042.89-.059 1.34-.063v-19.611l.063-.02V427.387c21.216-26.71 41.307-56.172 56.699-80.96-23.523-40.044-52.345-75.828-83.152-108.984-28.573 14.382-56.325 30.675-82.537 48.012-13.117-13.037-27.89-23.702-42.4-34.85-14.258-11.45-30.324-19.846-45.563-29.628 4.537-33.788 6.78-67.052 7.683-101.768-39.32-19.79-81.249-32.91-123.662-42.334-16.933 28.46-32.42 59.28-45.906 89.408-15.993-2.672-32.06-3.662-48.149-3.853v-.026c-.112 0-.216.026-.312.026-.1 0-.205-.026-.305-.026v.026c-16.117.191-32.17 1.18-48.168 3.853-13.478-30.129-28.955-60.948-45.914-89.408zM298.416 436.398c50.151 0 90.799 40.618 90.799 90.752 0 50.168-40.648 90.809-90.799 90.809-50.126 0-90.787-40.64-90.787-90.809 0-50.134 40.66-90.752 90.787-90.752zm427.178 0c50.122 0 90.779 40.618 90.779 90.752 0 50.168-40.657 90.809-90.78 90.809-50.159 0-90.806-40.64-90.806-90.809 0-50.134 40.647-90.752 90.807-90.752zm-418.498 35.87c-33.285 0-60.27 26.993-60.27 60.27 0 33.275 26.985 60.245 60.27 60.245 33.3 0 60.271-26.97 60.271-60.246s-26.97-60.27-60.271-60.27zm409.78 0c-33.275 0-60.235 26.993-60.235 60.27 0 33.275 26.96 60.245 60.236 60.245 33.31 0 60.271-26.97 60.271-60.246s-26.962-60.27-60.271-60.27zm-204.882 17.24c16.143 0 29.254 11.908 29.254 26.56v83.59c0 14.665-13.111 26.563-29.254 26.563-16.142 0-29.226-11.898-29.226-26.563v-83.59c0-14.652 13.084-26.56 29.226-26.56zM104.451 705.66c.063 14.561.248 30.514.248 33.69 0 143.085 181.512 211.86 407.026 212.65h.553c225.513-.79 406.962-69.565 406.962-212.65 0-3.235.195-19.12.262-33.69l-135.43 13.063-4.668 66.865c-.562 8.059-6.972 14.472-15.03 15.05l-160.49 11.452c-.39.029-.782.043-1.17.043-7.975 0-14.856-5.853-16.034-13.862l-9.203-62.416H446.525l-9.203 62.416c-1.236 8.4-8.746 14.44-17.205 13.819L259.63 800.639c-8.059-.579-14.47-6.992-15.031-15.051l-4.666-66.865-135.48-13.063z"/></svg>

Screenshot 2022-04-21 at 11 00 36

@Calinou
Copy link
Member

Calinou commented Apr 21, 2022

Awesome 🙂

Would it be possible to expose the texture filtering mode as is done in BaseMaterial3D? This would make it possible to enable mipmaps, preventing DynamicFonts from looking grainy at a distance when they're not using a fixed size. This would also allow for enabling anisotropic filtering to improve text appearance at oblique angles (when billboarding is disabled).
For people working on pixel art games, they could also opt for nearest-neighbor filtering for the text.

Of course, the above isn't a concern when using MSDF, but I assume some people will still favor DynamicFonts in Label3D for various reasons. (The filter mode property should probably be ignored when a MSDF font is in use, unless there's a good reason not to.)

This texture filtering property is also a change that could be done for Sprite3D: #50592

@@ -2479,6 +2558,7 @@ void BaseMaterial3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "albedo_color"), "set_albedo", "get_albedo");
ADD_PROPERTYI(PropertyInfo(Variant::OBJECT, "albedo_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_texture", "get_texture", TEXTURE_ALBEDO);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "albedo_tex_force_srgb"), "set_flag", "get_flag", FLAG_ALBEDO_TEXTURE_FORCE_SRGB);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "albedo_tex_msdf"), "set_flag", "get_flag", FLAG_ALBEDO_TEXTURE_MSDF);
Copy link
Member

@Calinou Calinou Apr 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should those properties be hidden from the BaseMaterial3D inspector, as this will be mainly used by Label3D? I'm not sure if people will actually be specifying MSDF textures to use directly in a BaseMaterial3D. Same for msdf_pixel_range and msdf_outline_size.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if there are any real use cases for it, but it's possible to generate MSDF texture using an external tool like msdfgen and apply it to anything.

@bruvzg
Copy link
Member Author

bruvzg commented Apr 21, 2022

Would it be possible to expose the texture filtering mode as is done in BaseMaterial3D? This would make it possible to enable mipmaps

I'll check it tomorrow. In addition to exposing the material settings, this probably will require mipmap generation to be enabled in the TextServer.

@albinaask
Copy link
Contributor

Looks awsome! I'd perhaps suggest a bill board option to always show the text towards you but to stay in place. It would be useful for waypoints, area markers, signs etc?

If I recall correctly it can be done through the transformation matrix somehow... did it ages ago for a particle system i think.

@Calinou
Copy link
Member

Calinou commented Apr 21, 2022

Fun fact: overlapping Label3Ds with slight offset and color changes for each layer can be used for a pseudo-3D effect 🙂

image


Looks awsome! I'd perhaps suggest a bill board option to always show the text towards you but to stay in place. It would be useful for waypoints, area markers, signs etc?

There's already a Billboard enum property available in Label3D. Am I missing a difference with the current billboard modes (Billboard and Y-Billboard)?

Are you asking for a billboard mode relative to the camera camera instead of the view plane? This could be added in the future, but it's not the best fit for text.


I took some time to test the latest revision of this PR.

  • There's a good performance benefit to using alpha scissor over alpha blending, both with DynamicFont and MSDF rendering. It was worth implementing in the end 🙂
  • Alpha Scissor works as expected, but the default value (0.98) seems pretty high:

image

In comparison, here it is with alpha scissor set to 0.5:

image

  • Regardless of the alpha scissor value, fonts with outlines tend to break due to Z-fighting. It seems that the outline is rendered at the same depth as the main font:

image

The wireframe view seems to confirm this:

image

To resolve this issue, could the outline vertices be slightly offset from the main font when the Alpha Cut property is Discard? The outline would then be drawn behind the main font. Using an offset of pixel_size units should work well enough most of the time.

  • Normals seem to be inverted on the generated mesh. When Shaded is enabled, faces that should be bright are dark and vice versa:
label3d-normals.mp4
  • Long texts can cause mesh generation to fail with the following error message.
    This occurs even with the default font at the default size:
servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp:243 - Condition "mesh->surface_count == RenderingServer::MAX_MESH_SURFACES" is true.

(This is the reason for the text on the screenshot below being cut off.)

  • The Width property should be hidden if Autowrap Mode is set to Off. The value should still be Off by default, but I suggest making the default width a value like 500, and set a property hint to disallow entering values below 1. With the default font size and a width of 500, arbitrary wrapping looks like this:

image

  • I'd recommend overriding GeometryInstance3D's Cast Shadow property to Off by default, so that Label3D doesn't cast shadows by default. This should improve performance a little by excluding the geometry from shadow passes, resulting in fewer draw calls.

@bruvzg bruvzg requested review from a team as code owners April 22, 2022 12:00
Copy link
Member

@fire fire left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a label and aside from not having a default font, the Label3D worked.

Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested the latest revision of this PR, everything seems to be working great 🙂

image

Testing project: test_label_3d_2.zip

@bruvzg
Copy link
Member Author

bruvzg commented Apr 25, 2022

And it's possible to control a "depth" (volume on z axis) for the symbols in that label? If not, that would be interesting to add.

No, with this approach, each glyph is a flat quad with a character texture, so there's no depth.

There's a way to get Bézier shapes from the font (if it's a dynamic font), but they should be converted to polygons, triangulated and "extruded" to form a mesh. And last time I have checked, the triangulation algorithm included in the Godot wasn't working well on polygons with holes, so it might need much more work. Also, intersections of the chars probably will need some care to seamlessly connect.

I was thinking about implementing this as well, but probably as a separate TextMesh.

I think I got glyph outlines convex decomposition / triangulation working (at least with the proper fonts, without self-intersection), so the most complex part is done. Still needs a bit of work to get rid of glyph-to-glyph intersections, but from this point, mesh generation should be simple.

Screenshot 2022-04-25 at 11 35 30

Probably will make a second PR for the TextMesh later this week.

@bruvzg bruvzg mentioned this pull request Apr 25, 2022
3 tasks
@bruvzg
Copy link
Member Author

bruvzg commented Apr 25, 2022

Probably will make a second PR for the TextMesh later this week.

Text mesh generation PR draft - #60507

Copy link
Member

@akien-mga akien-mga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, and amazing feature!

@akien-mga akien-mga merged commit 63a052d into godotengine:master Apr 25, 2022
@akien-mga
Copy link
Member

Thanks!

@golddotasksquestions
Copy link

golddotasksquestions commented Apr 29, 2022

Wow, this is really an incredible new feature! I just gave it a quick test and there is really nothing I could think of left desired! Huge thanks to @bruvzg (also to @Calinou for the continuous testing)
Everything just works super easy out of the box and even the most important Inspector properties (like Font and Size) are right there at the top of the Inspector where you would expect them from a general user perspective, super easy and intuitive to find and change.

May I ask what the reason was for limiting the font size to 127?

I'm not sure where to ask for this, because this seems like too small of a request to open a proposal for, but would it be possible to have the Font, Font Size, Font Outline properties of the Label3D also at the same location in the Inspector for the Label2D?
image

@bruvzg
Copy link
Member Author

bruvzg commented Apr 29, 2022

May I ask what the reason was for limiting the font size to 127?

The limit is copy-pasted from the importer settings, probably should be changed to 127,or_greater in both cases (like a normal Label it should not have any issues with the font sizes at least up to 4K).

I'm not sure where to ask for this, because this seems like too small of a request to open a proposal for, but would it be possible to have the Font, Font Size, Font Outline properties of the Label3D also at the same location in the Inspector for the Label2D?

It's separate properties in the Label3D, since it's not Control and do not have Theme and theme overrides, but current theme overrides definitely need some UI improvement. Redesigning font and font properties selection is on my TODO list, but I have not decided how exactly it should look (In addition to picking size and outline, I want to have easy access to OpenType features and variable font properties in the one place).

@Calinou
Copy link
Member

Calinou commented Apr 29, 2022

The limit is copy-pasted from the importer settings, probably should be changed to 127,or_greater in both cases (like a normal Label it should not have any issues with the font sizes at least up to 4K).

For rasterized DynamicFonts, this might cause a freeze if the user enters a very large value like 1000. For MSDF fonts, this should not be a problem as the MSDF is generated at import-time (at a 48 pixel size by default).

@golddotasksquestions
Copy link

golddotasksquestions commented Apr 30, 2022

For rasterized DynamicFonts, this might cause a freeze if the user enters a very large value like 1000.

Because Godot has to render (rasterize) each Glyph of the font?
Tbh I'd rather be able to have to wait a little and have a progress bar pop up (with the rest of the Editor tinted dark so it's clear it's inactive), than not being able to choose larger (= higher res) fonts sizes.

@Calinou
Copy link
Member

Calinou commented Apr 30, 2022

Because Godot has to render (rasterize) each Glyph of the font?

Yes.

Tbh I'd rather be able to have to wait a little and have a progress bar pop up (with the rest of the Editor tinted dark so it's clear it's inactive), than not being able to choose larger (= higher res) fonts sizes.

The issue is that this can cause stuttering during gameplay, especially on slower CPUs (font rasterization is done by FreeType on the CPU). This is especially noticeable on mobile devices, and it can result in difficult-to-diagnose slowdowns for people who aren't aware of this.

Enabling prerendering of the ASCII range by default for all fonts would alleviate this issue in most situations, at the cost of increased startup times. I'm personally in favor of it, as I feel the benefits outweigh the downsides.

@golddotasksquestions
Copy link

golddotasksquestions commented Apr 30, 2022

The issue is that this can cause stuttering during gameplay,

I don't think this is an issue at all. Setting font size is typically an editor usecase. All it needs is proper documentation which mentions something like:

"If you set the fontsize during runtime, Godot needs to render (rasterize) the every glyph of the font. For large font sizes, this will lead to a freeze until the work is done. Consider using separate threads when you want to set large font sized during gameplay. -> links to the background loading and threads docs"

@Calinou
Copy link
Member

Calinou commented Apr 30, 2022

I don't think this is an issue at all. Setting font size is typically an editor usecase. All it needs is proper documentation which mentions something like:

What I mean is that if you use a rasterized DynamicFont, each glyph will be rasterized the first time it is used. During gameplay, if a string changes and requires many new glyphs to be rasterized in a single frame, this can take a long time with large font sizes.

To make things worse, if the strings displayed depend on user input, the developer may never notice this issue during playtesting. For instance, a player entering a long chat message in a multiplayer game could trigger this issue for all other players. This stuttering would only occur once per gameplay session, but it's still something we should try to eliminate.

This problem has already been encountered by numerous users, especially on mobile platforms. This is why DynamicFont::get_available_chars() was originally implemented (back when font prerendering wasn't available in master). There's more background information in godotengine/godot-proposals#2021 too.

Note that due to how FreeType works, font rasterization performance largely depends on the power of 2 of the font size. Font sizes between 16 and 31 are fast to render, 32 to 63 is average, 64 to 127 is slow. Sizes between 128 and 255 will be very slow, and sizes above 256 should probably not be used in a game. Even if you prerendered such a font, it'd probably take several seconds, so it's not viable unless a font disk caching system is implemented.

@unfa
Copy link

unfa commented May 31, 2022

Is it possible to use BBcode formatting? I guess that'd require a separate node - RichLabel3D?

@unfa
Copy link

unfa commented May 31, 2022

Enabling "No Depth Test" makes the outline always render in front of the text, no matter what render priority is used and what side do you look at it from:
image

More extreme offsets:
image

Looking from behind (double-sided required) still has the outline obscuring the text:
image

@Calinou
Copy link
Member

Calinou commented Jun 1, 2022

Is it possible to use BBcode formatting? I guess that'd require a separate node - RichLabel3D?

No, and implementing something like RichTextLabel3D would be a lot of work. I don't think it's on the cards for 4.0. You can use the workaround I mentioned above if you need text with multiple colors within.

As for the depth test issue, please open a new issue with a minimal reproduction project attached.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants