A better method for rendering sprites semi-opaque when behind a model?

I am looking to set sprites into a semi-opaque state when they’re behind a model.

I have (a very slow) method which compares the distance from the camera to each sprite in the scene vs the distance from the camera to the model intersection (using the raycaster) to change the opacity of the sprites. If the sprite distance is shorter than the model distance, the sprite stays fully visible, otherwise the sprite is made sightly transparent because the model is closer in the line-of-sight. This method takes up too much resource in the render loop and it’s not much better even if it’s run less frequently using a listener on the controls. Not to mention, the more sprites there are in the scene, the slower it gets :man_facepalming:. Overall the method isn’t great and I haven’t been able to figure an accurate method without raycasting.

I’d kindly appreciate any clues for a better method I could use. I’m new to Three.js/WebGL in general, but wonder if there’s some kind of option or available shader that could achieve this? :thinking:

I should add: the concept behind wanting to do this is to make model viewing with annotations less visually cluttered. By making the sprites semi-opaque the user can still see and interact with the sprite while remaining relatively out of sight.

Maybe need add simplified invisible model and raycast with it. And maybe be use something like BVH, Octree.

What about completely eliminating raycasting?

  • first render the scene with all sprites set to opacity=0.2 and depthTest=false
  • then render only the sprites with opacity=1 and depthTest=true




@PavelBoytchev nice trick! For more performance the same concept can be used with post processing and camera layers, but it’s definitely more complex than your example and probably an overkill.

1 Like

This is genius. Thank you. I spent far too much energy thinking about how to calculate the solution, rather than just think of this as a layering problem.

On my end all I’ve needed to do is generate two sprites instead of one, each with their own material. With no noticeable impact on performance.

// generate texture, let spriteTexture = new THREE.Texture(canvas);

let material1 = new THREE.SpriteMaterial({
  map: spriteTexture,
  depthTest: false,
  depthWrite: false,
  sizeAttenuation: false,
  opacity: 0.3,
  transparent: true

let sprite1 = new THREE.Sprite(material1);
// then scale, position and add sprite 1 to scene

let material2 = new THREE.SpriteMaterial({
  map: spriteTexture,
  depthTest: true,
  depthWrite: true,
  sizeAttenuation: false,
  opacity: 1,
  transparent: true

let sprite2 = new THREE.Sprite(material2);
// then scale, position and add sprite 2 to scene