three.jsaframevirtual-reality

How do I set colors for three js instanced objects?


I have an a-frame component that leverages three js instancing to render a large number of spheres efficiently. Based on examples from the three js library such as https://github.com/mrdoob/three.js/blob/master/examples/webgl_instancing_scatter.html, I should be able to set the colors for each of the instanced renderings individually. I thought I had followed the example but my per instance colors are not taking effect.

AFRAME.registerComponent('spheres', {
    schema: {
        count: {type: 'number'},
        radius: {type: 'number'},
        scale: {type: 'number'},
        colors: {type: 'array'},
        positions: {type: 'array'}
    },
    init: function() {
        const {count, radius, scale, colors, positions} = this.data;  

        const material = new THREE.MeshNormalMaterial();

        const geometry = new THREE.SphereBufferGeometry( radius, 3, 2 );
        const instancedGeometry = new THREE.InstancedBufferGeometry().copy(geometry); 
        var instanceColors = [];

        for ( var i = 0; i < count; i ++ ) {

            instanceColors.push( Math.random() );
            instanceColors.push( Math.random() );
            instanceColors.push( Math.random() );

        }
        instancedGeometry.setAttribute("instanceColor", new THREE.InstancedBufferAttribute( new Float32Array( instanceColors ), 3 ))
        
        instancedGeometry.computeVertexNormals();

        material.vertexColors = true;

        const matrix = new THREE.Matrix4();
        const mesh = new THREE.InstancedMesh( instancedGeometry, material, count );

        for ( var i = 0; i < count; i ++ ) {

            this.setMatrix(positions[i], scale)( matrix );
            mesh.setMatrixAt( i, matrix );

        }

        this.el.object3D.add( mesh );
    },

What am I doing wrong?


Solution

  • Got it to work with the following based on @prisoner849 example. I had to use MeshPhongMaterial rather than MeshNormalMaterial. Not quite sure why. Also not sure why I need to use an InstancedBufferAttribute and a BufferAttribute for the colors. maybe @prisoner849 can follow up with details. This solution is a bit ugly, hopefully three will provide a cleaner way to tweak instanced colors soon, or maybe there is a better way already and I just dont know about it

    AFRAME.registerComponent('spheres', {
        schema: {
            count: {type: 'number'},
            radius: {type: 'number'},
            scale: {type: 'number'},
            colors: {type: 'array'},
            positions: {type: 'array'}
        },
        init: function() {
            const {count, radius, scale, colors, positions} = this.data;  
    
            var geometry = new THREE.SphereBufferGeometry(radius);
            var material = new THREE.MeshPhongMaterial({ flatShading: true });
            var colorParsChunk = [
                'attribute vec3 instanceColor;',
                'varying vec3 vInstanceColor;',
                '#include <common>'
            ].join( '\n' );
    
            var instanceColorChunk = [
                '#include <begin_vertex>',
                '\tvInstanceColor = instanceColor;'
            ].join( '\n' );
    
            var fragmentParsChunk = [
                'varying vec3 vInstanceColor;',
                '#include <common>'
            ].join( '\n' );
    
            var colorChunk = [
                'vec4 diffuseColor = vec4( diffuse * vInstanceColor, opacity );'
            ].join( '\n' );
    
            material.onBeforeCompile = function ( shader ) {
    
                shader.vertexShader = shader.vertexShader
                    .replace( '#include <common>', colorParsChunk )
                    .replace( '#include <begin_vertex>', instanceColorChunk );
    
                shader.fragmentShader = shader.fragmentShader
                    .replace( '#include <common>', fragmentParsChunk )
                    .replace( 'vec4 diffuseColor = vec4( diffuse, opacity );', colorChunk );
    
            };
           
            var instanceColors = [];
    
            for ( var i = 0; i < count; i ++ ) {
    
                instanceColors.push( Math.random() );
                instanceColors.push( Math.random() );
                instanceColors.push( Math.random() );
    
            }
    
            const matrix = new THREE.Matrix4();
            const mesh = new THREE.InstancedMesh( geometry, material, count );
    
            var instanceColorsBase = new Float32Array(instanceColors.length);
            instanceColorsBase.set(instanceColors);
            geometry.setAttribute( 'instanceColor', new THREE.InstancedBufferAttribute( new Float32Array( instanceColors ), 3 ) );
            geometry.setAttribute( 'instanceColorBase', new THREE.BufferAttribute(new Float32Array( instanceColorsBase ), 3 ) );
    
            for ( var i = 0; i < count; i ++ ) {
    
                this.setMatrix(positions[i], scale)( matrix );
                mesh.setMatrixAt( i, matrix );
    
            }
    
            this.el.object3D.add( mesh );
        },
        setMatrix: function( pos, scaler ) {
    
            var position = new THREE.Vector3();
            var rotation = new THREE.Euler();
            var quaternion = new THREE.Quaternion();
            var scale = new THREE.Vector3();
    
            return function ( matrix ) {
    
                position.x = pos[0];
                position.y = pos[1];
                position.z = pos[2];
    
                rotation.x = 0;
                rotation.y = 0;
                rotation.z = 0;
    
                quaternion.setFromEuler( rotation );
    
                scale.x = scale.y = scale.z = scaler;
    
                matrix.compose( position, quaternion, scale );
    
            };
    
        }
    });