How to bind texts to 3D objects in an efficient way?


I am working on a 3d fps game. I’m currently using the “Html” function of R3F to display the name of the players above their characters ( each character has of course a different name). The problem is that the “occlude” parameter of “Html” which is necessary to hide the name if there is an object between the camera and the name, is very greedy in performance, too much for the large number of simultaneous players that I would like to display.
Does anyone know another way to display the players’ names in a more efficient way?

Maybe have a look at jeromeetienne/threex.dynamictexture: three.js helper to handle dynamically generated texture. (

1 Like

I would use troika-three-text. It’s a package for rendering text in THREE.js using signed distance fields.
This means that it’ll look extremely crisp at any distance, whereas text rendered to a canvas and used as a texture will always end up blurry, unless you stay far enough from it and / or use a large enough canvas, which can end up using a significant amount of memory and draw calls. Troika uses an instanced mesh, so less draw calls, and a texture atlas for the characters, so uses less memory.

1 Like

Thank you for your answer, these are already better solutions than what I use!

But in both cases, we are still forced to have several draw calls in order to generate all the player texts.
I think it would be possible to have merged geometries with a texture atlas for each player, and that these merged geometries are an instance.
The only thing close to that is “MultiText” from a troika developer, but it seems to only work on a plane now. I don’t know if they were some advancements on the project. I don’t necessarily want to display hundreds of texts in the frustum, but just displaying 50 of them lowers the performance a bit.

occlude can be selective, tag the objects that are relevant. if you’re dealing with lots of objects and annotations html isn’t the right tool.

  // Can be true or a Ref<Object3D>[], true occludes the entire scene
  // Callback when the visibility changes
  onOcclude={(visible) => null}

as for troika, this also exists in drei, though i am not 100% sure if troika can be instanced. i think @lojjic already had drafts for it though. the moment it’s published it’ll work in drei also

import { Text } from '@react-three/drei'



I did everything with Text ( troika)
I tried to reduce the number of created text objects by only displaying a maximum amount and only if a player is in the frustum. This means that if a player enters the frustum, the text object with the “ref” with the lowest index will be assigned to him ( index 0), if a second player enters he will get index 1, etc…
Let’s say there is 6 player and the player 4 leaves. The ref of players 5 and 6 will change to the ref with index -1.
This seems to work quite well except that it seems that there is 1 frame delay for
" refText[textID].current. text = playerId" to change the text, so it displays the id of the previous player for 1 frame.
I did some tests where I removed the frustum condition and changed “refText[textID].current.text = playerId” first, then waited for 2 seconds before displaying it, and it did the same thing, it displayed for 1 frame the id of the old player before displaying the id it should.
Does someone have an idea why the text seems to take 1 frame in the frustum before updating, and know any solution or workaround?

    let textID = 0
    for (let i = 0; i < LENGTH; i++) {
    code handling other things
    // code of interest
    playerId = updatedPlayers[i]
    playersMovement[playerId] || defaultPlayerPos
    if (textID < nbTextRender) {
         vector3.set(playerMovement[0], playerMovement[1], playerMovement[2])
         if (frustum.containsPoint(vector3)) {
             refText[textID].current.text = playerId
             refText[textID].current.position.x = playerMovement[0]
             refText[textID].current.position.y = playerMovement[1]
             refText[textID].current.position.z = playerMovement[2]
    for (let i = textID; i < nbTextRender; i++) {
      refText[i].current.text = ""
        refText[i].current.position.x = -i
        refText[i].current.position.y = -5000

  return (
      { => (<Text key={i} ref={refText[i]} {...fontProps} /> ))}

AFAIK troika handles text updates in a service worker, so updating the text value is an´ asynchronous operation.
Maybe you can just create all the text elements up front and toggle their visibility or add/remove them from the scene instead of updating an existing text element’s value.

The thread starter wants to display the text labels above each respective character. Fortunately we are dealing with a default up-vector which accomodates just that, so we should get away with letting frustum culling do its thing, also with respect to text labels: if a character is frustum culled because it’s outside of the view frustum, so should be its text label (without having to explicitely toggle its visibility).

Ok, I will try that to have a unique text object for each player, thank you!
I was wondering what the impact is of creating a lot of objects even if they are not displayed, on the performance? Should I worry about it? Or will it just impact memory?
I would need a total of 2000-3000 text objects.

Or I just could have read the docs better. There is .sync() method to render changes immediately for critical cases. Anyway, thank you all!