粒子发射器的 webGL 制服问题:"uniform1f: location not for current program"

Problem with webGL uniforms for particle emitter : "uniform1f: location not for current program"

我用 WebGL/JS 创建了一个粒子发射器 class,它在一个实例中工作正常。 问题是当我尝试创建该对象的 2 个实例时。 我从 GPU 收到一条错误消息: WebGL:INVALID_OPERATION:uniform1f:位置不适用于当前程序

所以,我知道这是着色器程序的问题link:每个统一位置一个程序。但是在对该主题进行多次搜索之后,我无法找到解决方案。我对统一位置和属性缓冲区的概念不够熟悉,无法理解该错误。

这是我的 class 的代码。这两个实例位于代码的开头。 https://codepen.io/stephanemill/pen/KKXQJqG

你看到了什么:

window.addEventListener('load',()=>{
    
    new Particle({      
            quantity: 2000,         
            timeFactor: 100,            
            lifetime: 10,           
            fadeOut: .2,            
            size: 2,
            x: 50,          
            y: 70
    });
    
    setTimeout(()=>{            
            
        new Particle({          
                quantity: 2000,             
                timeFactor: 100,                
                lifetime: 5,                
                fadeOut: .2,                
                size: 2,                            
                x: 50,              
                y: 50,              
        });

    },500)
});

    
class Particle {

    static id = 0;
    
    static canvas = document.querySelector("#canvas-webgl");
    
    constructor(settings){
                
        settings = Object.assign({
            
            // Nombre de particules
            quantity: 200,
            
            // Taille des particules
            size: 2,        

            color: {
                r: {
                    min: 255,
                    max: 255,
                },
                g: {
                    min: 255,
                    max: 255,
                },
                b: {
                    min: 0,
                    max: 0,
                },
            },
        
            timeFactor: 1,
            
            x: 50,
            y: 50,
            
            // Durée de vie
            lifetime: 2,

            // Durée du fadeout         
            fadeOut: 1,     
            
            speed: { 
                min: 0, 
                max: 5,
                spread: 0,              
                sigma: .2,  
            },
            
            angle: {
                min: 0,
                max: 90,
                spread: 0,              
                sigma: .2,              
            },          
            
            gravity: 0,
            
            texture: null,
                        
        }, settings);
        
        Object.assign(this, settings);
        
        this.canvas     = this.constructor.canvas;
        
        this.ctx        = this.canvas.getContext("webgl");
        
        this.width      = this.canvas.width;
        
        this.height     = this.canvas.height;
        
        this.ratio      = this.width / this.height;
        
        this.startTime  = Date.now();
        
        if(!this.ctx) {
            alert("WebGL not supported!");
        }       
        
        
        // Séquence de démarrage du flux
        // -------------------------------      
        this.initProgram();
        
        this.initAttributes();
        
        this.initUniforms();
        
        this.initBlending();
        
        // this.initHUD();
        
        this.autokill();
        
        this.render();
        
        this.constructor.id++;
        
        return this;
    }
    
    initProgram(){  
        this.codeVertexShader   = this.getShader('explosion');
        this.codeFragmentShader = this.getFragmentShader();
        
        // Création du vertex shader
        this.vertexShader = this.ctx.createShader(this.ctx.VERTEX_SHADER);
        this.ctx.shaderSource(this.vertexShader, this.codeVertexShader);     
        this.ctx.compileShader(this.vertexShader);

        // Création du fragment shader
        this.fragmentShader = this.ctx.createShader(this.ctx.FRAGMENT_SHADER);
        this.ctx.shaderSource(this.fragmentShader, this.codeFragmentShader);
        this.ctx.compileShader(this.fragmentShader);

        // Création du programme
        this.program = this.ctx.createProgram();
        this.ctx.attachShader(this.program, this.vertexShader);
        this.ctx.attachShader(this.program, this.fragmentShader);
        this.ctx.linkProgram(this.program);
        this.ctx.useProgram(this.program);  
    }

    initAttributes(){       
        /*
        --------------------------------------------
        Mise en mémoire des attributs
        --------------------------------------------
        */
        
        const arraySpeedAngle   = new Float32Array(this.quantity * 2);
        const arrayPosition     = new Float32Array(this.quantity * 2);
        const arrayGravity      = new Float32Array(this.quantity * 2);
        const arrayColor        = new Float32Array(this.quantity * 3);
        
        for(let i=0; i<arraySpeedAngle.length; i+=2){
            const angleMin      = Math.deg2rad(this.angle.min);
            const angleMax      = Math.deg2rad(this.angle.max);
            const angleSpread   = Math.deg2rad(this.angle.spread);  
            
            const angle = Math.gradient(angleMin, angleMax, angleSpread, this.angle.sigma);         
            const speed = Math.gradient(this.speed.min, this.speed.max, this.speed.spread, this.speed.sigma);   
            
            arraySpeedAngle[i] = speed;
            arraySpeedAngle[i+1] = angle;
        }       
        
        for(let i=0; i<arrayPosition.length; i+=2){         
            let x0 = (this.x * 2 / 100) - 1;
            let y0 = 1 - (this.y * 2 / 100);
            x0 *= this.ratio;
            
            arrayPosition[i] = x0;
            arrayPosition[i+1] = y0;
        }
        
        for(let i=0; i<arrayGravity.length; i++){
            arrayGravity[i] = this.gravity;
            // arrayGravity[i] = Math.random()*10;
        }
        
        for(let i=0; i<arrayColor.length; i+=3){            
            const r = Math.rand(this.color.r.min, this.color.r.max);
            const g = Math.rand(this.color.g.min, this.color.g.max);
            const b = Math.rand(this.color.b.min, this.color.b.max);
            arrayColor[i] = r/255;
            arrayColor[i+1] = g/255;
            arrayColor[i+2] = b/255;
        }
        
        // Création du tampon de données sur Vitesse et Angle
        this.bufferSpeedAngle = this.ctx.createBuffer();
        this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.bufferSpeedAngle);
        this.ctx.bufferData(this.ctx.ARRAY_BUFFER, arraySpeedAngle, this.ctx.STATIC_DRAW);  
        const refSpeedAngle = this.ctx.getAttribLocation(this.program, "speed_angle");
        this.ctx.vertexAttribPointer(refSpeedAngle, 2, this.ctx.FLOAT, false, 0, 0);    
        this.ctx.enableVertexAttribArray(refSpeedAngle);        
        
        
        // Création du tampon de données sur la Position
        this.bufferPosition = this.ctx.createBuffer();      
        this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.bufferPosition);
        this.ctx.bufferData(this.ctx.ARRAY_BUFFER, arrayPosition, this.ctx.STATIC_DRAW);
        const refPosition = this.ctx.getAttribLocation(this.program, "position");
        this.ctx.vertexAttribPointer(refPosition, 2, this.ctx.FLOAT, false, 0, 0);  
        this.ctx.enableVertexAttribArray(refPosition);  
        
        
        // Création du tampon de données sur la Gravité
        this.bufferGravity = this.ctx.createBuffer();       
        this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.bufferGravity);
        this.ctx.bufferData(this.ctx.ARRAY_BUFFER, arrayGravity, this.ctx.STATIC_DRAW);
        const refGravity = this.ctx.getAttribLocation(this.program, "gravity");
        this.ctx.vertexAttribPointer(refGravity, 2, this.ctx.FLOAT, false, 0, 0);   
        this.ctx.enableVertexAttribArray(refGravity);           
        
        
        // Création du tampon de données sur la Couleur
        this.bufferColor = this.ctx.createBuffer();     
        this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.bufferColor);
        this.ctx.bufferData(this.ctx.ARRAY_BUFFER, arrayColor, this.ctx.STATIC_DRAW);
        const refColor = this.ctx.getAttribLocation(this.program, "color");
        this.ctx.vertexAttribPointer(refColor, 3, this.ctx.FLOAT, false, 0, 0); 
        this.ctx.enableVertexAttribArray(refColor);     

        // console.log(refSpeedAngle)       
        // console.log(refPosition)     
        // console.log(refGravity)  
        // console.log(refColor)            
    }

    initUniforms(){                     
        /*
        --------------------------------------------
        Mise en mémoire des uniforms
        --------------------------------------------
        */  
        
        // Location of uniforms
        this.refTime = this.ctx.getUniformLocation(this.program, "time");
        this.refTimeFactor = this.ctx.getUniformLocation(this.program, "timeFactor");
        this.refLifetime = this.ctx.getUniformLocation(this.program, "lifetime");
        this.refFadeOut = this.ctx.getUniformLocation(this.program, "fadeOut");
        this.refRatio = this.ctx.getUniformLocation(this.program, "ratio");
        this.refSize = this.ctx.getUniformLocation(this.program, "size");
        
        
        // Assig values to uniforms
        this.ctx.uniform1f(this.refTimeFactor, this.timeFactor);
        this.ctx.uniform1f(this.refLifetime, this.lifetime);
        this.ctx.uniform1f(this.refFadeOut, this.fadeOut);
        this.ctx.uniform1f(this.refRatio, this.ratio);
        this.ctx.uniform1f(this.refSize, this.size);
        
        
        // Uniform des textures
        if(!this.texture)
            return;
        
        if(this.texture)
            this.texture = document.querySelector(this.texture);
        
        var texture = this.ctx.createTexture();
        this.ctx.bindTexture(this.ctx.TEXTURE_2D, texture);
        this.ctx.texImage2D(this.ctx.TEXTURE_2D, 0, this.ctx.RGBA, this.ctx.RGBA, this.ctx.UNSIGNED_BYTE, this.texture);
        this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_S, this.ctx.CLAMP_TO_EDGE);
        this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_WRAP_T, this.ctx.CLAMP_TO_EDGE);
        this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_MIN_FILTER, this.ctx.NEAREST);
        this.ctx.texParameteri(this.ctx.TEXTURE_2D, this.ctx.TEXTURE_MAG_FILTER, this.ctx.NEAREST);
        this.ctx.activeTexture(this.ctx.TEXTURE0);
        this.refTexture = this.ctx.getUniformLocation(this.program, "texture");
        
        this.ctx.uniform1i(this.textureLocation, 0);
        
    }

    initBlending(){     
        /*
        --------------------------------------------
        Paramètres de gestion des couleurs
        --------------------------------------------
        */  
        this.ctx.enable(this.ctx.BLEND);        
        this.ctx.blendFunc(this.ctx.SRC_ALPHA,this.ctx.ONE_MINUS_SRC_ALPHA);
        this.ctx.disable(this.ctx.DEPTH_TEST);
        this.ctx.clearColor(0,0,0,0.0);
    }
    
    initHUD(){
        document.querySelector('#nbr-particles span').innerHTML = this.quantity;
    }

    render() {
        // Rendu du canvas webgl
        this.ctx.clear(this.ctx.COLOR_BUFFER_BIT);          
        let time = (Date.now() - this.startTime)/1000;
        this.ctx.uniform1f(this.refTime, time);     
        
        this.ctx.drawArrays(this.ctx.POINTS, 0, this.quantity);

        
        this.loopID = window.requestAnimationFrame(()=>{
            this.render();
        });
    }
    
    getShader(){
        
            
                
            return `
                precision mediump float;

                attribute   vec2    speed_angle;
                attribute   vec2    position;
                attribute   vec2    gravity;
                attribute   vec3    color;
                            
                uniform     float   time; 
                uniform     float   timeFactor; 
                uniform     float   ratio; 
                uniform     float   size; 

                varying lowp vec3 vColor;
            
                void main(void) {
                    gl_PointSize = size;
                    
                    vColor = color;
                    
                    

                    // float modTime    = mod(time*10., 1.);
                    float modTime   = time / timeFactor;
                    float speed     = speed_angle.x;
                    float angle     = speed_angle.y;
                    float G         = gravity.x;

                    // if(sin((speed*(time))) > .5)                     
                        // gl_PointSize  *= gl_PointSize * sin(speed);

                    float x0 = position.x;
                    float y0 = position.y;

                    // Equations horaires de la parabole
                    // ---------------------------------
                    float x = speed * cos(angle) * modTime + x0;
                    float y = -.5 * G * pow(modTime, 2.) + speed * sin(angle) * modTime + y0;

                    // Ratio canvas
                    // ---------------------------------
                    x /= ratio;
                    
                    // x = x + sin(y)/.2;

                    gl_Position=vec4(    x , y,      0.,1.);      
                }`;
                
            
            
    }
                        
    getFragmentShader(){
        
        return `
            precision mediump float; 
            
            varying lowp vec3 vColor;
            
            uniform float time;
            uniform float lifetime;
            uniform float fadeOut;
            uniform sampler2D texture;
            
            vec4 baseTexture;
            
            void main(void) {
                  // float modTime  = mod(T*10., 1.);
                
                vec3 fragmentColor = vColor;                
                
                // float x = gl_PointCoord.x  * sin(T*10.*V.x);
                // float y = gl_PointCoord.y  * sin(T*10.*V.y);
                
                // vec2 coords = vec2(x,y);
                
                // baseTexture = texture2D( texture,  coords);
                baseTexture = texture2D( texture,  gl_PointCoord);
                
                // fadeOut
                // ----------------------------------
                float opacity = 1.;
                if(time > lifetime)
                        opacity -= (time - lifetime) / fadeOut;
                
                // To the white
                // ----------------------------------
                float whiteDuration = .5;
                if(time < whiteDuration){
                    float deltaColorR = (1. - fragmentColor.x) * (whiteDuration - time);
                    float deltaColorG = (1. - fragmentColor.y) * (whiteDuration - time);
                    float deltaColorB = (1. - fragmentColor.z) * (whiteDuration - time);
                    
                    fragmentColor = vec3(fragmentColor.x + deltaColorR, fragmentColor.y + deltaColorG, fragmentColor.z + deltaColorB);
                }
                
                
                
                gl_FragColor = vec4(fragmentColor.x, fragmentColor.y, fragmentColor.z, opacity);
                
                
                // gl_FragColor = vec4(vColor.x, vColor.y, vColor.z, opacity);
                
                // gl_FragColor = vec4(.6, 1.0, 1.0, opacity);
                
                
                // gl_FragColor = vec4(1.0, 1.0, 1., 1.);
                // gl_FragColor = baseTexture * vec4(.7 * sin(V.x), .0, 1., 1.);
                // gl_FragColor = baseTexture;
                
                
                // rotation
                // https://www.py4u.net/discuss/95325
            }`;
    }
    
    autokill(){
        setTimeout(()=>{
            this.kill();
            // myParticle();
        }, (this.lifetime + this.fadeOut)*1000);
    }

    kill(){
        this.ctx.bindTexture(this.ctx.TEXTURE_2D, null);
        this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, null);
        this.ctx.bindBuffer(this.ctx.ELEMENT_ARRAY_BUFFER, null);
        this.ctx.bindRenderbuffer(this.ctx.RENDERBUFFER, null);
        this.ctx.bindFramebuffer(this.ctx.FRAMEBUFFER, null);
        this.ctx.deleteBuffer(this.bufferSpeedAngle);
        this.ctx.deleteBuffer(this.bufferPosition);
        this.ctx.deleteBuffer(this.bufferGravity);
        this.ctx.deleteProgram(this.program);
        this.ctx.deleteShader(this.fragmentShader);
        this.ctx.deleteShader(this.vertexShader);
        // 
        
        window.cancelAnimationFrame(this.loopID);
        
        this.ctx.clear(this.ctx.COLOR_BUFFER_BIT);
        
        // console.clear();
        // new Particle(1000);
    }

    
} // End of class

您(不必要地)创建了两个着色器程序,每个实例化一个,但您只在 initProgram 方法中调用 useProgram,但是调用 useProgram 会设置一个 global 呈现上下文的状态(在两种情况下都与从同一个 canvas 请求的相同)导致程序保持绑定状态直到更改。因此,当您使用第一个发射器进行渲染时,它会尝试使用第二个发射器(一旦创建)的着色器程序进行渲染。每次渲染时,您都需要 select 要渲染的程序,因此请在 render 方法中调用 useProgram