Is it posible in Three.js to have a mesh always rendered on top of the scene, even if it\'s position is behind all objects? I\'m implementing a lasso selection with a mesh a
Yes.
First do this:
renderer.autoClear = false;
Then create a second scene that contains just the objects you want to be on top. Then, in your render loop:
renderer.clear(); // clear buffers
renderer.render( scene, camera ); // render scene 1
renderer.clearDepth(); // clear depth buffer
renderer.render( scene2, camera ); // render scene 2
EDIT: Another solution is to have just one scene, but use this pattern:
mesh.renderOrder = 999;
mesh.onBeforeRender = function( renderer ) { renderer.clearDepth(); };
If the mesh has a single material, it will render "on top".
On top of setting object.renderOrder you have to set material.depthTest to false on the relevant objects.
var spriteMaterial = new THREE.SpriteMaterial( { map: texture1, depthTest: false} );
this.context1 = context1;
this.texture1 = texture1;
var sprite1 = new THREE.Sprite( spriteMaterial );
sprite1.scale.set(30,15,1);
sprite1.center.x=0;
sprite1.center.y=0;
sprite1.position.set( 0, 0, 0 );
this.scene.add( sprite1 );
The following is a working example that has a VisualLayers
class for managing any number of layers, and it uses the renderer.autoClear = false
and clearing-depth technique like West Langley mentioned in his answer.
This approach is nice because renderOrder
of objects is not being modified (that's another approach) and thus will not introduce other different issues, and you can save it for other use cases decoupled from layering.
Try playing with the options in the UI to see what it does:
// @ts-check
////////////////////////
// LAYER SYSTEM
////////////////////////
/** @typedef {{name: string, backingScene: THREE.Scene, order: number}} Layer */
class VisualLayers {
/**
* @type {Array<Layer>}
* @private
*/
__layers = [];
constructor(
/** @private @type {THREE.WebGLRenderer} */ __renderer,
/** @private @type {typeof THREE.Scene} */ __Scene = THREE.Scene
) {
this.__renderer = __renderer;
this.__Scene = __Scene;
}
defineLayer(/** @type {string} */ name, /** @type {number=} */ order = 0) {
const layer = this.__getLayer(name);
// The default layer always has order 0.
const previousOrder = layer.order;
layer.order = name === "default" ? 0 : order;
// Sort only if order changed.
if (previousOrder !== layer.order)
this.__layers.sort((a, b) => a.order - b.order);
return layer;
}
/**
* Get a layer by name (if it doesn't exist, creates it with default order 0).
* @private
*/
__getLayer(/** @type {string} */ name) {
let layer = this.__layers.find((l) => l.name === name);
if (!layer) {
layer = { name, backingScene: new this.__Scene(), order: 0 };
layer.backingScene.autoUpdate = false;
this.__layers.push(layer);
}
return layer;
}
removeLayer(/** @type {string} */ name) {
const index = this.__layers.findIndex((l) => {
if (l.name === name) {
l.backingScene.children.length = 0;
return true;
}
return false;
});
if (index >= 0) this.__layers.splice(index, 1);
}
hasLayer(/** @type {string} */ name) {
return this.__layers.some((l) => l.name === name);
}
/** @readonly */
get layerCount() {
return this.__layers.length;
}
addObjectToLayer(
/** @type {THREE.Object3D} */ obj,
/** @type {string | string[]} */ layers
) {
if (Array.isArray(layers)) {
for (const name of layers) this.__addObjectToLayer(obj, name);
return;
}
this.__addObjectToLayer(obj, layers);
}
addObjectsToLayer(
/** @type {THREE.Object3D[]} */ objects,
/** @type {string | string[]} */ layers
) {
for (const obj of objects) {
this.addObjectToLayer(obj, layers);
}
}
/** @private @readonly */
__emptyArray = Object.freeze([]);
/** @private */
__addObjectToLayer(
/** @type {THREE.Object3D} */ obj,
/** @type {string} */ name
) {
const layer = this.__getLayer(name);
const proxy = Object.create(obj, {
children: { get: () => this.__emptyArray }
});
layer.backingScene.children.push(proxy);
}
removeObjectFromLayer(
/** @type {THREE.Object3D} */ obj,
/** @type {string | string[]} */ nameOrNames
) {
if (Array.isArray(nameOrNames)) {
for (const name of nameOrNames) {
const layer = this.__layers.find((l) => l.name === name);
if (!layer) continue;
this.__removeObjectFromLayer(obj, layer);
}
return;
}
const layer = this.__layers.find((l) => l.name === nameOrNames);
if (!layer) return;
this.__removeObjectFromLayer(obj, layer);
}
/** @private */
__removeObjectFromLayer(
/** @type {THREE.Object3D} */ obj,
/** @type {Layer} */ layer
) {
const children = layer.backingScene.children;
const index = children.findIndex(
(proxy) => /** @type {any} */ (proxy).__proto__ === obj
);
if (index >= 0) {
children[index] = children[children.length - 1];
children.pop();
}
}
removeObjectsFromAllLayers(/** @type {THREE.Object3D[]} */ objects) {
for (const layer of this.__layers) {
for (const obj of objects) {
this.__removeObjectFromLayer(obj, layer);
}
}
}
render(
/** @type {THREE.Camera} */ camera,
/** @type {(layerName: string) => void} */ beforeEach,
/** @type {(layerName: string) => void} */ afterEach
) {
for (const layer of this.__layers) {
beforeEach(layer.name);
this.__renderer.render(layer.backingScene, camera);
afterEach(layer.name);
}
}
}
//////////////////////
// VARS
//////////////////////
let camera, stats, geometry, material, object, object2, root;
let time = 0;
/** @type {THREE.Scene} */
let scene;
/** @type {THREE.WebGLRenderer} */
let renderer;
/** @type {VisualLayers} */
let visualLayers;
const clock = new THREE.Clock();
const greenColor = "#27ae60";
const options = {
useLayers: true,
showMiddleBox: true,
rotate: true,
layer2Order: 2
};
//////////////////////
// INIT
//////////////////////
~(function init() {
setup3D();
renderLoop();
})();
////////////////////////////////
// SETUP 3D
////////////////////////////////
function setup3D() {
const container = document.createElement("div");
container.id = "container";
document.body.appendChild(container);
// CAMERA
camera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
1,
10000
);
camera.position.x = 0;
camera.position.z = 500;
camera.position.y = 0;
scene = new THREE.Scene();
// RENDERERS
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setClearColor(0x111111);
container.appendChild(renderer.domElement);
// LAYERS
visualLayers = new VisualLayers(renderer);
// Layers don't have to be defined. Adding an object to a layer will
// automatically create the layer with order 0. But let's define layers with
// order values.
visualLayers.defineLayer("layer1", 1);
visualLayers.defineLayer("layer2", 2);
visualLayers.defineLayer("layer3", 3);
// LIGHTS
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(300, 0, 300);
scene.add(directionalLight);
visualLayers.addObjectToLayer(directionalLight, [
"layer1",
"layer2",
"layer3"
]);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);
visualLayers.addObjectToLayer(ambientLight, ["layer1", "layer2", "layer3"]);
// GEOMETRY
root = new THREE.Object3D();
scene.add(root);
geometry = new THREE.BoxGeometry(100, 100, 100);
material = new THREE.MeshPhongMaterial({
color: greenColor,
transparent: false,
opacity: 1
});
object = new THREE.Mesh(geometry, material);
root.add(object);
visualLayers.addObjectToLayer(object, "layer1");
object.position.y = 80;
object.position.z = -20;
// object.rotation.y = -Math.PI / 5
object2 = new THREE.Mesh(geometry, material);
object.add(object2);
visualLayers.addObjectToLayer(object2, "layer2");
object2.position.y -= 80;
object2.position.z = -20;
object2.rotation.y = -Math.PI / 5;
const object3 = new THREE.Mesh(geometry, material);
object2.add(object3);
visualLayers.addObjectToLayer(object3, "layer3");
object3.position.y -= 80;
object3.position.z = -20;
object3.rotation.y = -Math.PI / 5;
// GUI
const pane = new Tweakpane({
title: "VisualLayers"
});
pane.addInput(options, "useLayers", { label: "use layers" });
pane.addInput(options, "showMiddleBox", { label: "show middle box" });
pane.addInput(options, "rotate");
pane
.addInput(options, "layer2Order", {
label: "layer2 order",
options: {
0: 0,
2: 2,
4: 4
}
})
.on("change", () => visualLayers.defineLayer("layer2", options.layer2Order));
// STATS
// SEE: https://github.com/mrdoob/stats.js
stats = new Stats();
stats.domElement.style.position = "absolute";
stats.domElement.style.left = "0px";
stats.domElement.style.top = "0px";
stats.setMode(0);
document.body.appendChild(stats.domElement);
}
//////////////////////
// RESIZE
//////////////////////
(window.onresize = function (event) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
})();
//////////////////////
// RAF RENDER LOOP
//////////////////////
function render() {
stats.begin();
if (options.rotate) {
time += clock.getDelta();
object.rotation.y += 0.02;
root.rotation.y = Math.PI / 2 + (Math.PI / 6) * Math.sin(time * 0.001);
}
object2.visible = options.showMiddleBox;
if (options.useLayers) {
scene.updateWorldMatrix(true, true);
renderer.autoClear = false;
renderer.clear();
visualLayers.render(camera, beforeEachLayerRender, afterEachLayerRender);
} else {
renderer.autoClear = true;
renderer.render(scene, camera);
}
stats.end();
}
function renderLoop() {
render();
requestAnimationFrame(renderLoop);
}
function beforeEachLayerRender(layer) {}
function afterEachLayerRender(layer) {
renderer.clearDepth();
}
html,
body,
#container {
margin: 0px;
padding: 0px;
width: 100%;
height: 100%;
}
canvas {
background: transparent;
display: block;
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
}
<script src="https://cdn.jsdelivr.net/npm/tweakpane@1.5.5/dist/tweakpane.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/stats.js/r11/Stats.min.js"></script>
<script src="//unpkg.com/three@0.121.1/build/three.min.js"></script>
<script src="//unpkg.com/postprocessing@6.17.4/build/postprocessing.js"></script>
(example on codepen)
If you want render only one mesh on front you can also manage by setting depthTest
for the material of that object to false
:
var onTopMaterial = new THREE.MeshStandardMaterial({
color: 'red',
depthTest: false
});
mesh.material = onTopMaterial;
Check a demonstation in this fiddle
Note: Make sure you have renderer.sortObjects
set to the default true
value.