Issue with my billboarding / LookAt

Hey everyone! I am refining my projects billboarding. Basically each of these labels currently rotate on their X and Z based on where the camera is - but now I am trying to make them lay flat basically if I look directly down on them. I believe I need to do something with the Y value but unsure if I am missing some kind of trig math, as setting it directly to the Y makes the labels vanish. I am working with 8thwall/vue/aframe/threejs Here is how I am currently handling it from the tick function:

for (var i = 0; i < this.objs.length; i++) {
var pos = document.getElementById('camera').getAttribute('position');
this.objs[i].lookAt(new THREE.Vector3(pos.x,0,pos.z));
}

Here is a video of the current behavior: Screen_Recording_20211217-172131_Chrome.mp4 - Google Drive

Thanks guys!

not sure if it helps but ive had better luck with quaternions: Spherical word-cloud - CodeSandbox

this.objs[i].quaternion.copy(camera.quaternion)

ps

Here is how I am currently handling it from the tick function:

better you don’t create classes inside the render loop (new THREE.Vector3(...)), that will eventually trip the garbage collector.

Thanks for this! I had tried this previously and it doesn’t give me the desired behavior. I think it is related to polling my a-frame camera in this AR app.

If you don’t understand what the function does or requires, it sometimes helps to step back and think about whether you really need that function or might as well build something yourself which you do understand. BTW: I looked at the source code of Object lookat() and frankly: didn’t immediately understand it either.

On the other hand, the math behind orienting a billboard towards the camera isn’t rocket science. My proposed way of doing it “by hand” goes like this:

Billboard position: bx, by, bz
Camera position: cx, cy, cz

Direction from camera to billboard:
dx = bx - cx,
dy = by - cy,
dz = bz - cz

Now you need a sketch like this (source: Wikipedia):

If you take “North” as the positive X, “Zenith” as positive “Y”, “Observer” as your camera and “Star” as your billboard, you can compute the azimuth angle as Math.atan2( dz, dx );

Rotate your billboard around its local y-axis by azimuth.

The altitude angle, sometimes also referred to as “elevation” can be computed as

Math.atan2( dy, Math.sqrt( dx * dx + dz * dz) );

Tilt your billboard around its local x.axis by altitude.

You may have to fiddle a bit until you get the signs of the angles right.

P.S.:
assumption: for altitude = 0 and azimuth = 0, the billboard faces the positive z-axis

1 Like

Thank you so much for this breakdown.

This is what I got so far based on your reply, I’m still not getting the desired behavior but I may be overlooking something simple.

  for (var i = 0; i < this.objs.length; i++) {
  var pos = document.getElementById('camera').getAttribute('position');
  var dir = new THREE.Vector3(this.objs[i].position.x - pos.x, this.objs[i].position.y - pos.y, this.objs[i].position.z - pos.z);
  this.objs[i].rotation.y = Math.atan2(dir.z, dir.x);
  this.objs[i].rotation.x = Math.atan2(dir.y, Math.sqrt( dir.x * dir.x + dir.z * dir.z));
  }

Not sure I understand the behaviour you’re trying to achieve, but have you looked at using / modifying a Sprite?
https://threejs.org/examples/?q=sprite#webgl_sprites

The labels are object3D’s and children of my gltf. I am trying to have it so no matter how I view them I can read them. Currently they will rotate with me based on where I am standing (in AR) but if I look directly down at them, at the floor basically - all I see is the top of them. So I need that axis to basically face the camera.

If you want to be able to read them no matter where the camera is, then using a THREE.Sprite is the easiest way to do so.
Try replacing the labels with Sprites with something like this:

for ( let label of labels ){
  // get the label's texture
  const { map } = label.material
  // create a new sprite using the texture
  const material = new THREE.SpriteMaterial({ map })
  const sprite = new THREE.Sprite( material )
  // position the sprite where the label is
  sprite.position.copy( label.position )
  label.parent.add( sprite )
  // remove the original object
  label.parent.remove( label )
  label.geometry.dispose()
  label.material.dispose()
}

You might need to adjust the sprites’ scales to match the labels’ scales.

You provided a verbatim implementation of my rough suggestion, so that’s my fault.

One simple thing, which occurred to me only on 2nd thought is, that the formula for the azimuth angle is in fact only the angle of the direction vector. However, the billboard’s orientation is at right angles to the direction vector, so you’ll have to subtract PI/2 (90°) from the computed azimuth angle to account for that:

this.objs[i].rotation.y = Math.atan2(dir.z, dir.x) - Math.PI / 2;

Please report back if this fixes the azimuth rotation of the billboard.