具有单独着色器的缓冲几何组

buffergeometry groups with individual shaders

我认为展示代码示例是最好的。因此,我创建了一个小示例来说明问题。 我想创建一个缓冲区几何体,其中每个组都有自己的着色器。虽然我总是在 material 数组中创建一个新实例,但我不能独立使用各个着色器的制服。我在数组中的一个着色器的制服中调整的内容总是对 material 数组中的所有其他着色器具有相同的效果。 在我问之前,我试图通过研究取得进展,但在这里我已经到了无法再进一步的地步。有谁知道为什么 material 数组中的各个着色器相互依赖以及如何避免这种情况?

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----------

您正在将对单个 ShaderMaterial 的引用推送到 materials 数组的每个索引中。

materials.push(Shader);

因为每个索引都是一个引用,更改一个索引中对象的属性自然会更改所有其他索引中的对象。

如果您希望每个组都有自己的属性,那么您需要为每个索引提供唯一的 material。您仍然可以通过只创建一个原始定义,然后使用 Material.clone 创建副本来做到这一点。

for(var i = 0; i < MAX_Planes; i++){
  geometry.addGroup( 6*i, 6, i );
  materials.push( Shader.clone() ); // creates a unique copy for each index     
}

这并没有让我平静下来,我突然想到,如果我预先分配缓冲区几何形状,我将不得不为每个组使用着色器。我现在会让这比使用“.clone ()”复杂得多。很好,我再次检查了。你的建议非常有效。我已经更正了定位更新,这正是我想要的结果。这是为任何感兴趣的人定制的代码。我现在将把它与我的粒子发射器结合起来。

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----------

我在这里设置了 2000 个矩形,并且对代码运行的流畅程度感到惊喜。即使有 5000 个矩形,一切都很顺利,即使每个矩形都有自己的着色器。缓冲区几何形状真的很酷。

谢谢 TheJim01

如果没有您的建议,我会在 for 循环中预初始化所有着色器。比如vertexshader[i], fragmentshader[i], uniforms[i], Shader[i]。使用 “.clone()”当然更好