three.jsshaderbuffer-geometry

buffergeometry groups with individual shaders


I think showing a code example is the best. Therefore i have created a small example that shows the problem. I want to create a buffer geometry in which each group has its own shader. although i always create a new instance in the material array, i cannot use the uniforms of the individual shaders independently. what i adjust in the uniform of one shader in the array always has the same effect on all other shaders in the material array. Before i ask, i try to advance through research, but here i have reached a point where i can not get any further. Does anyone know why the individual shaders in the material array are dependent on each other and how to avoid this?

var camera, controls, scene, renderer, container;

var PI = Math.PI;
var clock = new THREE.Clock(); 

var plane;
var MAX_Planes = 100;
var velocity = [];
var geometry;

var test1, test2, test3;


function init() {

    renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true} );
    renderer.setPixelRatio( window.devicePixelRatio ); 
    renderer.shadowMap.enabled = true; 
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    //renderer.sortObjects = true;
             
    container = document.getElementById('container');
    renderer.setSize(container.clientWidth, container.clientHeight);
    container.appendChild( renderer.domElement );

    var aspect = container.clientWidth / container.clientHeight; 
    scene = new THREE.Scene();
    scene.background = new THREE.Color( 0x000000 );
    
    camera = new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 1, 100000 );
    camera.position.set(0, 0, 4000);

    controls = new THREE.OrbitControls( camera, renderer.domElement );
    controls.enableZoom = true;
    controls.enabled = true;
    controls.target.set(0, 0, 0);
    
//---------------shaders--------------- 

    var BasicVertexShader = `
        void main() {
        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
    }`;

    var BasicFragmentShader = `
        void main() {
        gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0); 
    }`;


    var VertexShader = `
    varying vec3 sPos;
    uniform vec3 pos;
    uniform float stretch;
        void main() {

        float rotation = 0.0; 
        sPos = position; 

        vec3 scale;
        scale.x = 1.0*stretch;
        scale.y = 1.0*stretch;
        scale.z = 1.0*stretch;

        vec3 alignedPosition = vec3(position.x * scale.x, position.y * scale.y, 0.0);

        vec3 rotatedPosition;
        rotatedPosition.x = cos(rotation) * alignedPosition.x - sin(rotation) * alignedPosition.y;
        rotatedPosition.y = sin(rotation) * alignedPosition.x + cos(rotation) * alignedPosition.y;
        rotatedPosition.z = alignedPosition.z;

        vec4 finalPosition;

        finalPosition = modelViewMatrix * vec4( 0, 0, 0, 1.0 );
        finalPosition.xyz += rotatedPosition;
        finalPosition = projectionMatrix * finalPosition;
        gl_Position = finalPosition;

    //  gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

    }`;

    var FragmentShader = `
    varying vec3 sPos;
        void main() {

        vec3 nDistVec = normalize(sPos); 
        float dist = pow(sPos.x, 2.0) + pow(sPos.y, 2.0); 
        float magnitude = 1.0/dist * pow(4.0, 2.0);

    //  gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0) * magnitude; 
        gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0); 
    }`;

    var uniform = { 
        stretch: {type: 'f', value: 1.0},
        pos: { value: new THREE.Vector3(0,0,0) },
    } 

        Shader = new THREE.ShaderMaterial( {                    
        uniforms: uniform,                  
        vertexShader: VertexShader,
        fragmentShader: FragmentShader, 
        transparent: true,
        depthTest: false,   
        depthWrite: false
    }); 
    
    //just for tests
    var Shade = new THREE.ShaderMaterial( {                                 
        vertexShader: BasicVertexShader,
        fragmentShader: BasicFragmentShader, 
        side:THREE.DoubleSide
    }); 

//-------------------------------------------------
//create a plane: points, normals, uv
    const vertices = [
        { pos: [-10, -10,  0], norm: [ 0,  0,  1], uv: [0, 1], },
        { pos: [ 10, -10,  0], norm: [ 0,  0,  1], uv: [1, 1], },
        { pos: [-10,  10,  0], norm: [ 0,  0,  1], uv: [0, 0], },
        { pos: [ 10,  10,  0], norm: [ 0,  0,  1], uv: [1, 0], },
    ];

    const numVertices = vertices.length;
    const positionNumComponents = 3;
    const normalNumComponents = 3;
    const uvNumComponents = 2;
    //arrays for buffergeometry
    const positions = new Float32Array(numVertices * positionNumComponents * MAX_Planes);
    const normals = new Float32Array(numVertices * normalNumComponents * MAX_Planes);
    const uvs = new Float32Array(numVertices * uvNumComponents * MAX_Planes);
  
  //fill arrays with vertices
    var posPointer = 0;
    var nrmPointer = 0;
    var uvPointer = 0;
  
    for(var i = 0; i <= MAX_Planes; i++) {
        var posNdx = 0;
        var nrmNdx = 0;
        var uvNdx = 0;
            for (const vertex of vertices) {
                positions.set(vertex.pos, posNdx + posPointer);
                normals.set(vertex.norm, nrmNdx + nrmPointer);
                uvs.set(vertex.uv, uvNdx + uvPointer);
                posNdx += positionNumComponents;
                nrmNdx += normalNumComponents;
                uvNdx += uvNumComponents;
            }
        posPointer = i * posNdx;
        nrmPointer = i * nrmNdx;
        uvPointer = i * uvNdx;
    }

    //create buffergeometry and assign the attribut arrays
    geometry = new THREE.BufferGeometry();
    geometry.setAttribute('position', new THREE.BufferAttribute(positions, positionNumComponents));
    geometry.setAttribute('normal', new THREE.BufferAttribute(normals, normalNumComponents));
    geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, uvNumComponents));

    var ndx = 0;
    var indices = [];
    //instead 6 vertices for the both triangles of a plane i used 4, so reindication is neccessary
    for(var i = 0; i < MAX_Planes; i++){
        indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3);
        ndx += 4;
    }
    geometry.setIndex(indices);


    var materials = [];
    geometry.clearGroups(); 

    for(var i = 0; i < MAX_Planes; i++){
        geometry.addGroup( 6*i, 6, i );
        materials.push(Shader);     
    }
    plane = new THREE.Mesh(geometry, materials);
    scene.add(plane); 


    plane.material[0].uniforms.stretch.value = 2;
    plane.material[1].uniforms.stretch.value = 3;
    test1 = Object.keys(plane.material);
    test2 = plane.material[0].uniforms.stretch.value; //why this returns 3 and not 2?
    test3 = plane.material[1].uniforms.stretch.value;
    //the goal is that each group has its own Shader without effecting the other ones
    

//----------------------velocity---------------------------

    for(var i = 0; i < MAX_Planes; i++){
        velocity[i] = new THREE.Vector3(
        Math.random()*2-1,
        Math.random()*2-1,
        Math.random()*2-1);
    }
    
}//-------End init----------


function animate() {

    requestAnimationFrame( animate );  
    render();
    
}//-------End animate----------


function render() {


    document.getElementById("demo1").innerHTML = test1;
    document.getElementById("demo2").innerHTML = test2;
    document.getElementById("demo3").innerHTML = test3;

    for(var i = 0; i < MAX_Planes; i++){
        for(var j = 0; j < 4; j++){
        
            plane.geometry.attributes.position.array[i*12+3*j] = plane.geometry.attributes.position.array[i*12+3*j] + velocity[i].x;
            plane.geometry.attributes.position.array[i*12+3*j+1] =    plane.geometry.attributes.position.array[i*12+3*j+1] + velocity[i].y;
            plane.geometry.attributes.position.array[i*12+3*j+2] =    plane.geometry.attributes.position.array[i*12+3*j+2] + velocity[i].z;
        }
    }
    plane.geometry.attributes.position.needsUpdate = true;

    camera.updateMatrixWorld();
    camera.updateProjectionMatrix(); 
    renderer.render(scene, camera); 
    
}//-------End render----------

Solution

  • this did not leave me in peace and it occurred to me that if i prealocate the buffer geometry, i would have to do that with the shaders for each group. I would now have made this much more complicated than with ".clone ()". good that i checked again. Your advice works wonderfully. I have corrected the positioning update and that is exactly the result that i had in mind. Here is the customized code for anyone interested. I will now combine this with my particle emitter.

    var camera, controls, scene, renderer, container;
    
    var PI = Math.PI;
    var clock = new THREE.Clock(); 
    
    var plane;
    var MAX_Planes = 2000;
    var velocity = [];
    var geometry;
    
    
    function init() {
    
        renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true} );
        renderer.setPixelRatio( window.devicePixelRatio ); 
        renderer.shadowMap.enabled = true; 
        renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        //renderer.sortObjects = true;
                 
        container = document.getElementById('container');
        renderer.setSize(container.clientWidth, container.clientHeight);
        container.appendChild( renderer.domElement );
    
        var aspect = container.clientWidth / container.clientHeight; 
        scene = new THREE.Scene();
        scene.background = new THREE.Color( 0x000000 );
        
        camera = new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 1, 100000 );
        camera.position.set(0, 0, 4000);
    
        controls = new THREE.OrbitControls( camera, renderer.domElement );
        controls.enableZoom = true;
        controls.enabled = true;
        controls.target.set(0, 0, 0);
        
    //---------------shader---------------  
    
        var VertexShader = `
        varying vec3 sPos;
        uniform vec3 pos;
        uniform float stretch;
            void main() {
    
            float rotation = 0.0; 
            sPos = position; 
    
            vec3 scale;
            scale.x = 1.0*stretch;
            scale.y = 1.0*stretch;
            scale.z = 1.0*stretch;
    
            vec3 alignedPosition = vec3(position.x * scale.x, position.y * scale.y, 0.0);
    
            vec3 rotatedPosition;
            rotatedPosition.x = cos(rotation) * alignedPosition.x - sin(rotation) * alignedPosition.y;
            rotatedPosition.y = sin(rotation) * alignedPosition.x + cos(rotation) * alignedPosition.y;
            rotatedPosition.z = alignedPosition.z;
    
            vec4 finalPosition;
    
            finalPosition = modelViewMatrix * vec4( pos, 1.0 );
            finalPosition.xyz += rotatedPosition;
            finalPosition = projectionMatrix * finalPosition;
            gl_Position = finalPosition;
    
        }`;
    
        var FragmentShader = `
        varying vec3 sPos;
            void main() {
    
            vec3 nDistVec = normalize(sPos); 
            float dist = pow(sPos.x, 2.0) + pow(sPos.y, 2.0); 
            float magnitude = 1.0/dist * pow(3.0, 2.0);
            
            float alpha = 1.0;
            
            if(magnitude  < 0.01){
            alpha = 0.0;
            }
            
            gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), alpha) * magnitude; 
        //  gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0); 
        }`;
    
        var uniform = { 
            stretch: {type: 'f', value: 1.0},
            pos: { value: new THREE.Vector3(0,0,0) },
        } 
    
            var Shader = new THREE.ShaderMaterial( {                    
            uniforms: uniform,                  
            vertexShader: VertexShader,
            fragmentShader: FragmentShader, 
            transparent: true,
            depthTest: false,   
            depthWrite: false
        }); 
        
    
    //-------------------------------------------------
    //create a plane: points, normals, uv
        const vertices = [
            { pos: [-20, -20,  0], norm: [ 0,  0,  1], uv: [0, 1], },
            { pos: [ 20, -20,  0], norm: [ 0,  0,  1], uv: [1, 1], },
            { pos: [-20,  20,  0], norm: [ 0,  0,  1], uv: [0, 0], },
            { pos: [ 20,  20,  0], norm: [ 0,  0,  1], uv: [1, 0], },
        ];
    
        const numVertices = vertices.length;
        const positionNumComponents = 3;
        const normalNumComponents = 3;
        const uvNumComponents = 2;
        //arrays for buffergeometry
        const positions = new Float32Array(numVertices * positionNumComponents * MAX_Planes);
        const normals = new Float32Array(numVertices * normalNumComponents * MAX_Planes);
        const uvs = new Float32Array(numVertices * uvNumComponents * MAX_Planes);
      
      //fill arrays with vertices
        var posPointer = 0;
        var nrmPointer = 0;
        var uvPointer = 0;
      
        for(var i = 0; i <= MAX_Planes; i++) {
            var posNdx = 0;
            var nrmNdx = 0;
            var uvNdx = 0;
                for (const vertex of vertices) {
                    positions.set(vertex.pos, posNdx + posPointer);
                    normals.set(vertex.norm, nrmNdx + nrmPointer);
                    uvs.set(vertex.uv, uvNdx + uvPointer);
                    posNdx += positionNumComponents;
                    nrmNdx += normalNumComponents;
                    uvNdx += uvNumComponents;
                }
            posPointer = i * posNdx;
            nrmPointer = i * nrmNdx;
            uvPointer = i * uvNdx;
        }
    
        //create buffergeometry and assign the attribut arrays
        geometry = new THREE.BufferGeometry();
        geometry.setAttribute('position', new THREE.BufferAttribute(positions, positionNumComponents));
        geometry.setAttribute('normal', new THREE.BufferAttribute(normals, normalNumComponents));
        geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, uvNumComponents));
    
        var ndx = 0;
        var indices = [];
        //instead 6 vertices for the both triangles of a plane i used 4, so reindication is neccessary
        for(var i = 0; i < MAX_Planes; i++){
            indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3);
            ndx += 4;
        }
        geometry.setIndex(indices);
    
    
        var materials = [];
        geometry.clearGroups(); 
    
        for(var i = 0; i < MAX_Planes; i++){
            geometry.addGroup( 6*i, 6, i );
            materials.push(Shader.clone()); 
        }
        plane = new THREE.Mesh(geometry, materials);
        scene.add(plane); 
    
    
    //----------------------velocity---------------------------
        
        for(var i = 0; i < MAX_Planes; i++){
            velocity[i] = new THREE.Vector3(
            Math.random()*2-1,
            Math.random()*2-1,
            Math.random()*2-1);
        }   
        
    }//-------End init----------
    
    
    function animate() {
    
        requestAnimationFrame( animate );  
        render();
        
    }//-------End animate----------
    
    var loop = 0;
    function render() {
    
        loop = loop + 0.5;
    
        for(var i = 0; i < MAX_Planes; i++){
        
        var pos = new THREE.Vector3(0, 0, 0);
            
            pos.x += velocity[i].x*loop;
            pos.y += velocity[i].y*loop;
            pos.z += velocity[i].z*loop;
            
            plane.material[i].uniforms.pos.value = pos;
    
        }
        plane.geometry.attributes.position.needsUpdate = true;
    
        camera.updateMatrixWorld();
        camera.updateProjectionMatrix(); 
        renderer.render(scene, camera); 
        
    }//-------End render----------
    

    I set 2000 rectangles here and am pleasantly surprised at how smoothly the code runs. Even with 5000 rectangles everything went smoothly, even though each rectangle has its own shader. buffer geometries are really cool.

    Thank you TheJim01

    without your advice i would have preinitialized all shaders in a for loop. Such as vertexshader[i], fragmentshader[i], uniforms[i], Shader[i]. Using ".clone()" is of course much better đź‘Ť