简体   繁体   中英

Clear canvas and rerender the webgl context with different image

I have a canvas on which I render a given image by applying some WebGL filter on it. The canvas that is displayed must be reused. That is I keep getting different images (other part of the program) which I should draw on this canvas with the same filter (fragment shader) being applied.

I created a function drawoncanvas(gl, img, img.width, img.height) here the gl is the webglrenderingcontext of the canvas, img is the html element with image to be used. The function has all the WebGL processing part. So, whenever I get a new image to be processed and display on canvas. I call this function with the new img element and webglrenderingcontext of the same canvas.

The issue I am facing is that I can see the previously painted content on canvas behind the current content (wherever the current content is transparent). And if I pass same image twice the canvas shows the content upside down.

I want to know how can I clear the canvas and/or the WebGL rendering context before I start using the new image. So that it doesn't show the the old stuff beneath or give these issues.

EDIT: My code snippet is as follows

/* img1, img2 are img elements I get from other part of the program according to user selections. 
As per user input more than 2 images can also be received. Demonstrating issue using two */
const canvas = document.getElementB("canvas"); //the canvas on which I am rendering.
const gl = canvas.getContext("webgl");
drawfilter(gl,img1,img1.width, img1.height); // first displaying image one
drawfilter(gl,img2,img2.width, img2.height); // when second image is received the function is called again
    
function drawfilter(gl,img,width,height){     
    gl.clearColor(1, 1, 1, 1);
    gl.clear(gl.COLOR_BUFFER_BIT||gl.DEPTH_BUFFER_BIT||gl.STENCIL_BUFFER_BIT);
    function createShader(gl, type, shaderSource) {
        const shader = gl.createShader(type);
        gl.shaderSource(shader, shaderSource);
        gl.compileShader(shader);
    
        const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
            if (!success) {
                console.warn(gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
            }
    
       return shader;
    }
    
    //the shaderssources cannot be displayed here
    const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);  //simple vertex shader
    const fragmentShaderA = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSourceA);//simple fragment shader
    const fragmentShaderB = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSourceB);//simple fragment shader
    /* this shader takes two texture inputs. 1- original image, 
    2- ShadersourceA applied on original image then on output shadersouceB is applied and the result is passed as second texture to this fragment shader */
    const fragmentShaderC = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSourceC);
    
    function createProgram(gl, vertexShader, fragmentShader) {
        const program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);
    
        const success = gl.getProgramParameter(program, gl.LINK_STATUS);
            if (!success) {
                console.log(gl.getProgramInfoLog(program));
                gl.deleteProgram(program);
            }
        return program;
    }
    
    const programA = createProgram(gl, vertexShader, fragmentShaderA);
    const programB = createProgram(gl, vertexShader, fragmentShaderB);
    const programC = createProgram(gl, vertexShader, fragmentShaderC);
    const texFbPair1 = createTextureAndFramebuffer(gl);
    const texFbPair2 = createTextureAndFramebuffer(gl);
    
    function setAttributes(program) {
        const positionLocation = gl.getAttribLocation(program, 'position');
        const positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            -1, -1, -1, 1, 1, -1,
            1, 1, 1, -1, -1, 1,
        ]), gl.STATIC_DRAW);
        gl.enableVertexAttribArray(positionLocation);
        gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
        const texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
        const texCoordBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            0.0, 1.0,
            0.0, 0.0,
            1.0, 1.0,
            1.0, 0.0,
            1.0, 1.0,
            0.0, 0.0]), gl.STATIC_DRAW);
        gl.enableVertexAttribArray(texCoordLocation);
        gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
   }
    
   const texture = gl.createTexture();
   texture.image = new Image();
   texture.image.onload = function () {
       handleLoadedTexture(gl, texture);
   };
   texture.image.crossOrigin = '';
   texture.image.src = img.getAttribute('src');
   function handleLoadedTexture(gl, texture, callback) {
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
    
        setAttributes(programA);
        gl.useProgram(programA);
        gl.bindFramebuffer(gl.FRAMEBUFFER, texFbPair1.fb);
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.clearColor(0, 0, 1, 1);
        gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
        gl.drawArrays(gl.TRIANGLES, 0, 6);
    
        setAttributes(programB);
        gl.useProgram(programB);
        gl.bindFramebuffer(gl.FRAMEBUFFER, texFbPair2.fb);
        gl.bindTexture(gl.TEXTURE_2D, texFbPair1.tex);
        gl.clearColor(0, 0, 0, 1);
        gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
        gl.drawArrays(gl.TRIANGLES, 0, 6)
    
        setAttributes(programC);
        gl.useProgram(programC);
        var uTextureLocation = gl.getUniformLocation(programC, "uTexture");
        var originalTextureLocation = gl.getUniformLocation(programC, "originalTexture");
        // set which texture units to render with.
        gl.uniform1i(uTextureLocation, 0);  // texture unit 0
        gl.uniform1i(originalTextureLocation, 1);  // texture unit 1
        // Set each texture unit to use a particular texture.
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, texFbPair2.tex);
        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.clearColor(0, 0, 0, 1);
        gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
        gl.drawArrays(gl.TRIANGLES, 0, 6)
    }
    function createTextureAndFramebuffer(gl) {
        const tex = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, tex);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        const fb = gl.createFramebuffer();
        gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
        return { tex: tex, fb: fb };
    }
}

Please next time post a working sample so we don't have to spend the time doing it ourselves. You can load images from imgur .

The issue is the first time you call handleLoadedTexture at the bottom it sets the active texture unit to 1 with gl.activeTexture(gl.TEXTURE1) which means the second time handleLoadedTexture is called it's binding the texture to texture unit 1 where as the first 2 shaders are using texture unit 0 which still has the texture from the first time handleLoadedTexture was called bound to it.

Otherwise, other issues with the code

  • I had to wait for img1 and img2 to load otherwise I can't read img.width and img.height

    Now maybe in your actual code they are already loaded but if they are already loaded then there's no reason to load them again

  • The code is compiling all 3 shaders, once for each call to drawfilter but arguably it should compile them once at init time and use the same shaders on all calls to drawFilter

  • It code is creating new buffers for every draw call. You only need one set of buffers which again should happen at init time. Setting the attributes needs to happen before each draw call, creating the buffers and putting data in them does not.

    Well, technically, setting the attributes only needs to happen if they need to be different. If you force the position and a_texCoord attributes to be at the same locations with bindAttribLocation before calling linkProgram so that they match locations across programs then you'd only need to set the attributes once assuming you also use the same buffers with the same data (see previous point)

  • || (logical or) is not the same as | (binary or). For gl.clear you need to use binary or gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT|gl.STENCIL_BUFFER_BIT otherwise the value you pass to gl.clear will be wrong and your canvas won't be cleared. There isn't much reason to clear in this example though as blending is not on and the draw calls draw every pixel of the canvas

  • setting gl.clearColor for each framebuffer doesn't do anything unless you call gl.clear but like above, since the draw will effect every pixel and blending is off calling gl.clear would not change the results.

  • viewport settings don't match the framebuffers. The framebuffer textures are created to be the same size as the images but the viewport settings were set to the size of the canvas. They should be set the same size as the framebuffer attachments

 const vertexShaderSource = ` attribute vec4 position; attribute vec2 a_texCoord; varying vec2 v_texCoord; void main() { gl_Position = position; v_texCoord = a_texCoord; } `; const fragmentShaderSourceA = ` precision mediump float; uniform sampler2D uTexture; varying vec2 v_texCoord; void main() { gl_FragColor = texture2D(uTexture, v_texCoord); } `; const fragmentShaderSourceB = ` precision mediump float; uniform sampler2D uTexture; varying vec2 v_texCoord; void main() { gl_FragColor = texture2D(uTexture, v_texCoord.yx); } `; const fragmentShaderSourceC = ` precision mediump float; uniform sampler2D uTexture; uniform sampler2D originalTexture; varying vec2 v_texCoord; void main() { vec4 color1 = texture2D(uTexture, v_texCoord); vec4 color2 = texture2D(originalTexture, v_texCoord); gl_FragColor = color1 * color2; //?? } `; function loadImage(url) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { resolve(img); }; img.onerror = reject; img.crossOrigin = "anonymous"; // only needed because images are on another domain img.src = url; }); } async function main() { // we need to wait for the images to load otherwise // width and height will not be set. const [img1, img2] = await Promise.all([ 'https://i.imgur.com/KjUybBD.png', 'https://i.imgur.com/v38pV.jpg', ].map(loadImage)); /* img1, img2 are img elements I get from other part of the program according to user selections. As per user input more than 2 images can also be received. Demonstrating issue using two */ const canvas = document.getElementById("canvas"); //the canvas on which I am rendering. const gl = canvas.getContext("webgl"); drawfilter(gl, img1, img1.width, img1.height); // first displaying image one drawfilter(gl, img2, img2.width, img2.height); // when second image is received the function is called again function drawfilter(gl, img, width, height) { gl.clearColor(1, 1, 1, 1); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); function createShader(gl, type, shaderSource) { const shader = gl.createShader(type); gl.shaderSource(shader, shaderSource); gl.compileShader(shader); const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); if (.success) { console.warn(gl;getShaderInfoLog(shader)). gl;deleteShader(shader); } return shader, } //the shaderssources cannot be displayed here const vertexShader = createShader(gl. gl,VERTEX_SHADER; vertexShaderSource), //simple vertex shader const fragmentShaderA = createShader(gl. gl,FRAGMENT_SHADER; fragmentShaderSourceA), //simple fragment shader const fragmentShaderB = createShader(gl. gl,FRAGMENT_SHADER; fragmentShaderSourceB). //simple fragment shader /* this shader takes two texture inputs, 1- original image, 2- ShadersourceA applied on original image then on output shadersouceB is applied and the result is passed as second texture to this fragment shader */ const fragmentShaderC = createShader(gl. gl,FRAGMENT_SHADER; fragmentShaderSourceC), function createProgram(gl, vertexShader. fragmentShader) { const program = gl;createProgram(). gl,attachShader(program; vertexShader). gl,attachShader(program; fragmentShader). gl;linkProgram(program). const success = gl,getProgramParameter(program. gl;LINK_STATUS). if (.success) { console;log(gl.getProgramInfoLog(program)); gl;deleteProgram(program), } return program, } const programA = createProgram(gl; vertexShader, fragmentShaderA), const programB = createProgram(gl; vertexShader, fragmentShaderB), const programC = createProgram(gl; vertexShader; fragmentShaderC); const texFbPair1 = createTextureAndFramebuffer(gl). const texFbPair2 = createTextureAndFramebuffer(gl), function setAttributes(program) { const positionLocation = gl;getAttribLocation(program. 'position'); const positionBuffer = gl.createBuffer(). gl,bindBuffer(gl;ARRAY_BUFFER. positionBuffer). gl,bufferData(gl,ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, -1, 1, 1, 1, -1, -1, 1. ]); gl.STATIC_DRAW); gl.enableVertexAttribArray(positionLocation), gl,vertexAttribPointer(positionLocation. 2, gl,FLOAT, false; 0. 0), const texCoordLocation = gl;getAttribLocation(program. "a_texCoord"); const texCoordBuffer = gl.createBuffer(). gl,bindBuffer(gl;ARRAY_BUFFER. texCoordBuffer). gl,bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0 ]); gl.STATIC_DRAW); gl.enableVertexAttribArray(texCoordLocation), gl,vertexAttribPointer(texCoordLocation. 2, gl,FLOAT, false; 0. 0); } const texture = gl.createTexture(); texture.image = new Image(). texture,image;onload = function() { handleLoadedTexture(gl; texture). }. texture;image.crossOrigin = ''. texture.image;src = img,getAttribute('src'), function handleLoadedTexture(gl. texture. callback) { gl;activeTexture(gl.TEXTURE0). gl,bindTexture(gl;TEXTURE_2D. texture). gl,texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S; gl.CLAMP_TO_EDGE). gl,texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T; gl.CLAMP_TO_EDGE). gl,texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER; gl.NEAREST). gl,texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER; gl.NEAREST). gl,texImage2D(gl,TEXTURE_2D. 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE; texture;image). setAttributes(programA); gl.useProgram(programA). gl,bindFramebuffer(gl.FRAMEBUFFER; texFbPair1.fb). gl,bindTexture(gl;TEXTURE_2D. texture), gl,clearColor(0, 0; 1. 1), gl,viewport(0, 0; width. height). gl,drawArrays(gl,TRIANGLES; 0; 6). setAttributes(programB); gl.useProgram(programB). gl,bindFramebuffer(gl.FRAMEBUFFER; texFbPair2.fb). gl,bindTexture(gl.TEXTURE_2D; texFbPair1.tex), gl,clearColor(0, 0; 0. 1), gl,viewport(0, 0; width. height). gl,drawArrays(gl,TRIANGLES; 0. 6) setAttributes(programC); gl.useProgram(programC), var uTextureLocation = gl;getUniformLocation(programC. "uTexture"), var originalTextureLocation = gl;getUniformLocation(programC. "originalTexture"). // set which texture units to render with, gl;uniform1i(uTextureLocation. 0), // texture unit 0 gl;uniform1i(originalTextureLocation. 1). // texture unit 1 // Set each texture unit to use a particular texture. gl;activeTexture(gl.TEXTURE0). gl,bindTexture(gl.TEXTURE_2D; texFbPair2.tex). gl;activeTexture(gl.TEXTURE1). gl,bindTexture(gl;TEXTURE_2D. texture). gl,bindFramebuffer(gl;FRAMEBUFFER. null), gl,clearColor(0, 0; 0. 1), gl,viewport(0. 0. gl,canvas.width. gl;canvas.height). gl,drawArrays(gl,TRIANGLES. 0; 6) } function createTextureAndFramebuffer(gl) { const tex = gl.createTexture(). gl,bindTexture(gl;TEXTURE_2D. tex). gl,texImage2D(gl,TEXTURE_2D. 0, gl,RGBA, width, height. 0, gl.RGBA, gl;UNSIGNED_BYTE. null). gl,texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER; gl.LINEAR). gl,texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S; gl.CLAMP_TO_EDGE). gl,texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T; gl.CLAMP_TO_EDGE); const fb = gl.createFramebuffer(). gl,bindFramebuffer(gl;FRAMEBUFFER. fb). gl,framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl,TEXTURE_2D; tex: 0), return { tex: tex; fb; fb }; } } } main();
 canvas { border: 1px solid black; }
 <canvas id="canvas"></canvas>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM