Is it possible to set varying opacity for objects inside a InstancedMesh (filled with BoxBufferGeometry + MeshPongMaterial cubes)

Hi, I’m learning Three.js and have built a very basic scene with a 4x4 cubes arranged into a grid like layout (much like the example here: https://threejs.org/examples/?q=instancing#webgl_instancing_raycast, except it’s cubes --BoxBufferGeometry + MeshPhongMaterial with transparency enabled and opacity set to 0.5).

Am trying to set the opacity of the objects farthest from the camera as fully opaque and the objects closest to camera are most transparent. So, the opacity of cubes increases with the farther they’re from the camera. Achieving this doesn’t seem possible anymore after Three.js removed alpha support for THREE.Color class and the alpha component of the hex is now ignored (https://github.com/mrdoob/three.js/commit/dc6e335dc9f7f38f1c79a5a6c112019d6fcce69d). There are a few (very outdated) discussions on this topic about adjusting opacity individually for lines/points (https://github.com/mrdoob/three.js/commit/dc6e335dc9f7f38f1c79a5a6c112019d6fcce69d) but that doesn’t seem to work on an InstancedMesh where opacity is set to the material entirely and not on individual matrices of the instance.

I found this thread on SO (https://stackoverflow.com/questions/12337660/three-js-adjusting-opacity-of-individual-particles) with a similar requirement but unfortunately the example uses a ParticleSystem to show a point cloud with varying alpha component. sadly, that was the closest I got to figuring it out how to do it and am still stuck.

Is there a way to have varying alpha values for each cube inside an InstancedMesh (with objects made from BoxBufferGeometry and MeshPhongMaterial)?

It’s not possible, mainly because if that was allowed the first thing people bump into is that the order in which transparent objects get renderer is incorrect.

In order to render them correctly the instances will need to be reordered every time the camera moves.

How many instances are you dealing with?

2 Likes

Thanks for the response mrdoob. I have 64 instances in the mesh (4 columns, 4 rows, 4 tiers/high). So if I wanted to varying opacity on this kind of setup, I assume I have to drop the idea of using InstancedMesh and use a normal Mesh class, meaning I’d now have 64 mesh objects instead of a single instanced Mesh, right? with that, I assume, I can set the opacity of each mesh conditionally but am somehow worried it’s not the best way forward.

Here is my existing (bad/unclean) code (please excuse it as it’s only to learn three.js and explore it’s possibilities).

function init() {
    var camera, scene, renderer, stats, mesh, geometry, material, orbitCtl, matrix, loader;

    var instancesCount = Math.pow(4, 3);
    matrix = new THREE.Matrix4();
    loader = new THREE.Loader();
    camera = new THREE.PerspectiveCamera(fov, aspect, nearcam, farcam);
    camera.position.set(0, 35, 35);
    camera.lookAt(...initCameraLookAt);
    scene = new THREE.Scene();
    scene.background = new THREE.Color( sceneBg );
    var ambientLight = new THREE.AmbientLight(ambientLightColor, ambientLtIntensity);

    const lightsInstanced = [];
    const dLight1 = makeDirectionalLightInstance(0xffffff, 0.25, [0, 1, -1]);
    const dLight2 = makeDirectionalLightInstance(0xffffff, 0.25, [0, 1, 1]);
    const dLight3 = makeDirectionalLightInstance(0xffffff, 0.25, [1, 1, 0]);

    lightsInstanced.push(...dLight1, ...dLight2, ...dLight3, ...dLight4, ...dLight5);

    geometry = new THREE.BoxBufferGeometry(1, 1, 1);

    material = new THREE.MeshLambertMaterial({
        color: 0xff1411,
        map: new THREE.TextureLoader().load('textures/square-outline-textured.png'),
        opacity: 0.8,
        transparent: true,
        side: THREE.DoubleSide,
    });

    mesh = new THREE.InstancedMesh(geometry, material, count);

    var scene3dObjects = [
        ambientLight,
        ...lightsInstanced,
        mesh,
    ];
    var index = 0;
    var tiers = rows = columns = 4;

    for (var y = 0; y < tiers; y++) {
        for (var z = 0; z < rows; z++) {
            for (var x = columns; x > 0; x--) {
                matrix.setPosition(x, y, z);
                mesh.setMatrixAt(index, matrix);
                mesh.setColorAt(index, color.setHex(colorHex));
                mesh.instanceMatrix.needsUpdate = true;
                index++;
            }
        }
    }

    scene.add(...scene3dObjects);

    renderer = new THREE.WebGLRenderer({ antialias: true, alpha: enableMeshAlphaComponent });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);
}

This is the result of it (again, please excuse my poor editing :sweat_smile:, also I meant to write “Tilted view” instead of “titled view” on the top left segment of the image (silly me, typo)).

Another variation with depthWrite disabled and opacity decreased to 0,5

I don’t think you need InstancedMesh for your this. You can just create 64 Meshes with their own material (and opacity).

1 Like

In general, .depthWrite should usually be set to false if the material has an opacity <1. That would avoid the sorting issues here. But this still requires customizing the shader, or using NodeMaterial, to provide a per-instance opacity.

1 Like

Noted, your inputs are very helpful. Thanks mrdoob and donmccurdy all the same.

Instanced rendering with opacity per instance (but without sorting) is still useful for certain scenarios where sorting doesn’t matter (f.e. object happen not to overlap, etc) but performance improvement is needed.

I made an example of this on top of three-instanced-mesh here:

(Note that the shader patches need slight updates for latest Three.js, or else some features of Three’s latest shaders (like the new vertex color alphas) will be removed by the shader patching).

1 Like

Combining links to related resources from multiple sources:

It’s not possible, mainly because if that was allowed the first thing people bump into is that the order in which transparent objects get renderer is incorrect.

In order to render them correctly the instances will need to be reordered every time the camera moves.