Show/Hide CSS2D Text Behind the GLB Model While Rotating

Hi everyone,

Is there a way to dynamically show and hide the CSS2d text when the user rotates the model?

const initialize = () => {
  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera(
    70,
    window.innerWidth / window.innerHeight,
    1,
    1500
  );
  camera.position.set(0, 0, 25);
  scene.add(camera);

  // RENDERER
  renderer = new THREE.WebGL1Renderer({
    antialias: true,
    alpha: true,
  });
  labelRenderer = CSS2DRenderer()
  labelRenderer.setSize(window.innerWidth, windowInnerHeight)
  labelRenderer.domElement.style.position = 'absolute'
  labelRenderer.domElement.style.top = '0'
  document.body.appendChild(labelRenderer.domElement)

  renderer.setSize(canvasContainer.clientWidth, canvasContainer.clientHeight);
  canvas.appendChild(renderer.domElement);

   const textPar = document.createElement('p')
   const textParLabel = new CSS2DObject(textPar)
   textParLabel.textContent = "I'm a text"
   textParLabel.position.set(1,2,0)

  const loader = new GLTFLoader();
  loader.load("/mars.glb", (file) => {
    model.scale.set(5, 5, 5);
    model.add(textParLabel)
    scene.add(model);
  });

  controls = new OrbitControls(camera, renderer.domElement);

  animation();
};

const animation = () => {
  requestAnimationFrame(animate)
  renderer.render(scene, camera)
  labelRenderer.render(scene,camera)
};

In this sample code, I’m able to make it follow the globe when I rotate the globe, but when the text is suppose to be behind the planet model, it’s still showing on the top of the model. Any tips on how can I hide it when the text is suppose to be behind the model?

short answer no, if you use react yes — and easily, otherwise you gotta make it yourself, see:

I found this codepen CodePen - Globe of Points: Markers + Label

this codepen uses an image to load the globe but in my case, it’s .glb file. I hope there’s a way to implement this kind of approach to a .glb model

If raycast between label position and camera position have intersection then hide else show.

yep, this uses raycasting for fake occlusion. each annotation has to shoot a ray towards the camera and pierce the whole scene just to figure out if something’s in front. and it can’t hide behind objects, it just disappears.

it’s complex to implement and can be very expensive (ray casting is on the cpu). if you want to dig through all this code more power to you but if you look into the post i linked you’ll see this is not necessary at all. you can get perfect occlusion with blending. this has zero expense, looks better, html is an actual part of your scene now — no ifs or buts, and it’s less code.

see this example

in the post i explain how it works. it just requires a plane and a blend mode. all you need to do is bring that plane to the annotations whereabouts and it’s done.

Do you have a sample for this for me to have a better glimpse? That would be very helpful

Thank you for this resource. But do you think this is possible in non-react example?

did you click the discourse link i posted? it will cost you a day of work, like anything else, but yes of course!

let mars;
const initialize = () => {
  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera(
    70,
    window.innerWidth / window.innerHeight,
    1,
    1500
  );
  camera.position.set(0, 0, 25);
  scene.add(camera);

  // RENDERER
  renderer = new THREE.WebGL1Renderer({
    antialias: true,
    alpha: true,
  });
  labelRenderer = CSS2DRenderer()
  labelRenderer.setSize(window.innerWidth, windowInnerHeight)
  labelRenderer.domElement.style.position = 'absolute'
  labelRenderer.domElement.style.top = '0'
  document.body.appendChild(labelRenderer.domElement)

  renderer.setSize(canvasContainer.clientWidth, canvasContainer.clientHeight);
  canvas.appendChild(renderer.domElement);

   const textPar = document.createElement('p')
   const textParLabel = new CSS2DObject(textPar)
   textParLabel.textContent = "I'm a text"
   textParLabel.position.set(1,2,0)

  const loader = new GLTFLoader();
  loader.load("/mars.glb", (file) => {
    model.scale.set(5, 5, 5);
    model.add(textParLabel)
    scene.add(model);
mars=model;
  });

  controls = new OrbitControls(camera, renderer.domElement);

  animation();
};

var raycast=new THREE.Raycaster();

const animation = () => {
  requestAnimationFrame(animate)


textParLabel.getWorldPosition(raycast.ray.origin);
var rd=camera.position.clone().sub(raycast.ray.origin).normalize();
raycast.ray.direction.set(rd.x,rd.y,rd.z);
var hits=raycast.intersectObjects([mars]);
if(hits.length>0){ textParLabel.visible=false; }
else{ textParLabel.visible=true; }

  renderer.render(scene, camera)
  labelRenderer.render(scene,camera)
};

2 Likes

Thank you so much! This codepen will be also very helpful to others. :slight_smile:

1 Like