Capítulo 800 de 859

Chapter 800: Aligning HTML Elements to 3D (Manual)

Core Idea

How to project a 3D object's screen-space position so an overlaid HTML/CSS label (e.g. a name tag) tracks it, including handling occlusion and depth ordering.

Key Concepts

  • Vector3.project(camera): converts a world position to normalized device coordinates (-1 to 1) for the current camera.
  • NDC to CSS pixels: the projected x/y is remapped to canvas pixel coordinates to position the HTML label.
  • Occlusion via raycasting: a Raycaster fired from the camera toward the object's position checks whether that object is the first hit, hiding the label if something else is in front.
  • Depth-based z-index: the projected z (-1 front to +1 back) is inverted into a CSS zIndex so nearer labels render on top of farther ones.
  • Frustum culling for labels: checking the projected z against the near/far range hides labels for objects that left the camera's view.
  • Bounding-sphere frustum test: three.js provides faster approximate frustum-intersection checks than per-object raycasting, useful when many objects need occlusion checks.

Code Examples

const tempV = new THREE.Vector3();
const raycaster = new THREE.Raycaster();

cubes.forEach((cubeInfo) => {
  const {cube, elem} = cubeInfo;
  cube.updateWorldMatrix(true, false);
  cube.getWorldPosition(tempV);
  tempV.project(camera);

  raycaster.setFromCamera(tempV, camera);
  const intersectedObjects = raycaster.intersectObjects(scene.children);
  const show = intersectedObjects.length && cube === intersectedObjects[0].object;

  elem.style.display = show ? "" : "none";
  if (show) {
    const x = (tempV.x * .5 + .5) * canvas.clientWidth;
    const y = (tempV.y * -.5 + .5) * canvas.clientHeight;
    elem.style.transform = `translate(-50%, -50%) translate(${x}px,${y}px)`;
  }
});
  • What it demonstrates: projecting a 3D position to screen space and using a raycaster to hide labels for occluded objects.

Key Takeaways

  1. Use Vector3.project(camera) to turn a world position into normalized screen coordinates for CSS placement.
  2. Per-object raycasting from the camera is a simple way to hide labels behind other objects, but it's not free for large object counts.
  3. Convert the projected z value into a CSS zIndex (with a large multiplier) so overlapping labels stack in the correct front-to-back order.
  4. For many objects, prefer three.js's bounding-sphere frustum checks over per-object raycasting for culling.

Connects To

  • Raycaster: used here to detect occlusion between camera and label target.
  • OrbitControls: used in the example scene to move the camera around the labeled objects.