Always display mesh on top of transparent object

I’ve got two THREE.Points objects with material.transparent = true and material.opacity = 0.8. There’s also a THREE.Raycaster that displays up to 3 THREE.Mesh at the points that you click on either of the objects. These marker should always be displayed on top of the two objects. If the “marker” option is disabled in the settings, then the marker objects are removed from the scene.

I know that object.renderOrder handles material.transparent = true objects separately from regular objects, so I can’t use it for this.

In another thread it was recommended to render the extra object(s) separately:

renderer.autoClear = false;

function animate(time) {
    controls.update();
    stats.begin();

    if(markerEnabled) {
        requestAnimationFrame(animate);
        renderer.clear();
        renderer.render(scene, camera);
        renderer.clearDepth();
        renderer.render(marker1, camera);
        renderer.render(marker2, camera);
        renderer.render(marker3, camera);
    } else {
        renderer.clear(); //Added by me, not sure if this is correct
        renderer.render(scene, camera);
    }

    stats.end();
}

However, this creates a delay of about a second between when the user clicks and when the marker is actually displayed (this didn’t happen before). Plus, by doing this, you apparently have to handle the depth and color buffers yourself and I honestly don’t know how/when to do that.

Is there another way that always displays the markers on top?

have you considered also putting your opaque objects in the transparent render queue using material.transparent = true on everything and just using full opacity eg material.opacity = 1?

This doesn’t sound right, any chance you could provide a minimal reproduction on codepen.io or similar?

have you considered also putting your opaque objects in the transparent render queue using material.transparent = true on everything and just using full opacity eg material.opacity = 1?

Does that change anything performance-wise or is there any downside of other things that stop working then?
Btw, depthTest is disabled for the markers, not sure if that makes a difference.

This doesn’t sound right, any chance you could provide a minimal reproduction on codepen.io or similar?

I removed requestAnimationFrame(animate) and everything’s still working but there’s no more delay. Is this call actually needed?
Sorry, I can’t, these models are pretty big (no idea if this would also happen with smaller ones - haven’t got any to test it with) and I’m loading them through a link that I can’t post publicly.

double sided transparent materials are rendered in two passes (2 draw calls) you can mitigate this by setting the material to either front side or back side only, or use the flag material.forceSinglePass = true three.js docs it would depend if this setup suits your use case…

was this a duplicate call to raf? if so, raf is only needed once and shouldn’t be called twice on the same callback function…

it’s difficult to tell what’s going on there without seeing the rest of your code but when using raf you only need to manually call animate() once, if you are manually calling animate() after a user clicks that would work without raf but when the raf call is included you’d essentially be queuing a new call to request animation frame every time the user clicks, raf handles the call to the animate() every available next frame and therefore allows you to perform contiguous animation steps within the callback smoothly… again it depends if you need to use animations within the animate function such as orbit controls auto rotate / damping with its update() method or similar…

double sided transparent materials are rendered in two passes (2 draw calls) you can mitigate this by setting the material to either front side or back side only, or use the flag material.forceSinglePass = true three.js docs it would depend if this setup suits your use case…

I’m using a THREE.PointsMaterial, the docs for this specific type don’t mention anything about sides but the points are always facing the camera and I didn’t set material.side, so it’s probably one-sided anyway.
I also don’t change anything about the MeshBasicMaterial that the markers are using, apart from the color and depthTest.
The answer is then probably “no”, right?

was this a duplicate call to raf? if so, raf is only needed once and shouldn’t be called twice on the same callback function…

No, it’s only called once in animate and I don’t touch it otherwise.

it’s difficult to tell what’s going on there without seeing the rest of your code but when using raf you only need to manually call animate() once, if you are manually calling animate() after a user clicks that would work without raf but when the raf call is included you’d essentially be queuing a new call to request animation frame every time the user clicks, raf handles the call to the animate() every available next frame and therefore allows you to perform contiguous animation steps within the callback smoothly… again it depends if you need to use animations within the animate function such as orbit controls auto rotate / damping with its update() method or similar…

Going to edit the post: I also use controls.update(), stats.begin() and stats.end() in animate() but that’s it.
I do renderer.setAnimationLoop(animate) inside my init function that is called right at the start but, other than that, I don’t think I’m calling animate manually.

this is all you need to call once to initiate the animation loop, the extra raf call would be conflicting and is not required

PointsMaterial extends Material which is where side is defined however you are correct that if the points are always facing the camera you’d only ever need one side anyway…

right, I think you should be fine using the transparency render queue without a detrimental effect on performance, otherwise removing the request animation frame call from your animate loop and managing rendering manually would work too, a third option could be to use layers

you probably want depthTest:true, depthWrite:true, alphaTest:0.5,transparent:true
on your main points.material…
and set points.renderOrder = 1;

on the second points.material you want on top of all of them, depthTest:false, depthWrite:true, alphaTest:0.5,
transparent:true
and set points.renderOrder = 2;

otherwise removing the request animation frame call from your animate loop and managing rendering manually would work too

I know that it tells the website “hey, I want to render something” (that’s what google says) but what does it actually do? I removed it and left the rest in the first comment as-is but didn’t notice any differences/problems.

a third option could be to use layers

Layers? Like multiple scenes? That was also suggested somewhere but it also requires you to handle rendering manually.

Why change both THREE.Points objects? They use this material:

new THREE.PointsMaterial({
    color: 0xff0000,
    transparent = true,
    material.opacity = 0.8
});
points.renderOrder = 990;

The markers use this:

new THREE.MeshBasicMaterial({
    color: markerColorOuter,
    depthTest: false/*,
    transparent: true,
    opacity: 1*/ //Change suggested by Lawrence
});
marker.renderOrder = 999;

Marking one transparent and one not, already puts them in separate render passes, so setting ones renderOrder to 998 and one to 999 isn’t really doing anything.

For renderOrder to have meaning between 2 objects, they need to be in the same pass.

There are multiple ways to get it to work, but conceptually I find it cleaner to have them in the same pass and explicitly control with renderOrder.

With your setup what is going to happen is:

new THREE.PointsMaterial({ <--- This renders second since transparent=true
    color: 0xff0000,
    transparent = true,
    material.opacity = 0.8
});
points.renderOrder = 990; <--- this makes it render after the other transparent stuff hopefully as long as nobody else is using renderOrders > 999 + transparent=true

The markers use this:

new THREE.MeshBasicMaterial({    <---This renders first (transparent false)
    color: markerColorOuter,
    depthTest: false/*, <--- This means it will wipe out anything behind it, and since no alpha test, you'll get clipped ghost where the alpha is 0
    transparent: true,
    opacity: 1*/ //Change suggested by Lawrence
});
marker.renderOrder = 999; <--- this makes it render after the other solid stuff hopefully as long as nobody else is using renderOrders > 999

With my setup: They both render in the .transparent = true pass. One is just forced to render after the other.. and it doesn’t test depth, so will render on top of everything already existing in the buffer.

so.. the thing to be aware of.. transparent=true/false controls which internal render pass they happen in:

solid pass = rendered nearest to furthest…

transparent pass: rendered furthest to nearest

then within those passes, the renderOrder controls order.

as suggested originally :sweat_smile::smiling_face:

renderer.setAnimationLoop and window.requestAnimationFrame in essence perform the same functionality (the former being the suggested way in three.js) to synchronise a callback method (in your case animate) with the clients screen refresh rate,

request animation frame quite literally means, request the current animation frame of the client, if the current frame is different from the last requested frame run the callback function again, creating a synchronised loop of animate() for you to perform smooth animated actions inside…

renderer.setAnimationLoop does basically the same thing but performs the loop under the three.js hood and allowing you to use your callback function (animate) to perform smooth animation steps every frame…

setAnimationLoop is sufficient, you do not need to create another loop of the animate function which is what the requestAnimationframe call was doing, leading to timing conflicts between the two…