RogerLePatissier
RogerLePatissier

Reputation: 157

how to pass stencil from a framebuffer to another

I'm struggling to understand why in the code below, the stencil tests work as expected when i render straight to the screen and doesnt when i attempt to use framebuffers. I have no problem using these framebuffers for multipass postprocessing on textures, and so i suspect i'm doing something wrong with my depth & stencil buffers.

First i initialise, the context:

        gl = canvas.getContext("webgl",{stencil:true});

then

function initFBO()
{

for (var i = 0; i < shaderPrograms.length; i++) 
{

    var texture = createTexture(imgWidth,imgHeight);
    textures.push(texture);

    var fbo = gl.createFramebuffer();
    framebuffers.push(fbo);

    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

    var renderbuffer = gl.createRenderbuffer();

    gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
    gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, imgWidth, imgHeight);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);

}
}


function drawScene() 
{
gl.enable(gl.STENCIL_TEST);
gl.enable(gl.DEPTH_TEST);

if(!useFBO)
{
    computeProgFBO(9,null,imgHeight,imgHeight,textureId,null,null,false);
    computeProgFBO(8,null,imgHeight,imgHeight,textureId,null,null,false);
}else{
    computeProgFBO(9,framebuffers[0],imgHeight,imgHeight,textureId,null,null,false);
    computeProgFBO(8,null,imgHeight,imgHeight,textures[0],null,null,false);
}

// Restore the original matrix
mvPopMatrix();

gl.disable(gl.STENCIL_TEST);
gl.disable(gl.DEPTH_TEST);

}

function computeProgFBO(progId,fboId,w,h,textureId0,textureId1,textureId2,flip)
{

    currentProgram=shaderPrograms[progId];
    gl.useProgram(currentProgram);

    if(progId==9)
    {
        transform(w,h,0.0);
        gl.stencilMask(1);
        gl.stencilFunc(gl.ALWAYS,1,0);
        gl.stencilOp(gl.KEEP,gl.KEEP,gl.REPLACE);
        gl.colorMask(true, true, true, true);
        gl.depthMask(false);
        setMatrixUniformsView();
    }else{
        gl.colorMask(true, true, true, true);
        gl.depthMask(true);
        gl.stencilFunc(gl.EQUAL,0,1);
        gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP);
        setMatrixUniforms();
    }

    setMyUniforms(progId,flip);
    setMyTexture(textureId0, textureId1,textureId2);

    gl.activeTexture(gl.TEXTURE1);
    gl.bindFramebuffer(gl.FRAMEBUFFER, fboId);
//the below is commented out for now as i'm only testing the stencil buffer for now        
//  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textures[progId], 0);




    gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);

    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.bindTexture(gl.TEXTURE_2D , null);
}

I dont think there is any point in showing the different uniforms and texture functions, but please do let me know otherwise. Btw, the progId 9 draws a small green square, and progId 8 a bigger red square on top of it. So, i'm expecting to see a green square surrounded by some red edges as it currently show when i render straight to the screen.

****FURTHER to the 1st answer i have amended the below *****

if(!useFBO)
{
    computeProgFBO(9,null,imgHeight,imgHeight,textureId,null,null,false,null);
    computeProgFBO(8,null,imgHeight,imgHeight,textureId,null,null,false,null);
    computeProgFBO(6,null,imgHeight,imgHeight,textureId,null,null,false,null);
}else{
    computeProgFBO(9,framebuffers[0],imgHeight,imgHeight,textureId,null,null,false,null);
    computeProgFBO(8,framebuffers[1],imgHeight,imgHeight,textures[0],null,null,false,renderbuffers[0]);
    computeProgFBO(6,null,imgHeight,imgHeight,textures[1],null,null,false,null);
}

and i have changed the function arguments to

computeProgFBO(progId,fboId,w,h,textureId0,textureId1,textureId2,flip,render)

and in there after the below comment

//the below is commented out for now as i'm only testing the stencil buffer for now        
//  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textures[progId], 0);

i have added

if(render!=null)
    {
        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, render);
    }

The result is slightly better but not yet exactly what i was expecting, ie: im getting the big red square and instead of having the little green square inside, i get a white square. I feel like this is something trivial but can't really work it out just yet. Rendering straight to the screen still works fine.

***** FURTHER to comments from others i added******

i think i understand the problem and found a workaround for it [not very elegant tho]. Basically, the first pass writes using stencil to textures[0] of framebuffers[0], and the 2nd pass writes to textures[1] of framebuffers[1] using renderbuffers[0] of framebuffers[0], but not textures[0]. Hence i end up with just a small white[instead of green] square surrounded by a red one. Since, i cant blit, i've realised that i can just draw twice in the first pass as per the below:

....
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);

if(progId==9) 
    {
        gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers[1]);
        gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
    }

This works but looks very ugly! Is there any other way?

When it comes to render the final texture to the screen, it seems like i dont have to do much, ie: i only send the color buffer to the screen (and not the stencil & depth buffer) and all the final texture gets displayed as expected.

Also, note that my fragment shaders are only doing the below

shader9: gl_FragColor = vec4(0.0,1.0,0.0,1.0);

shader8: gl_FragColor = vec4(1.0,vec2(0.0),1.0);

shader6: gl_FragColor = texture2D(u_Texture0,vTextureCoord);

Upvotes: 3

Views: 2075

Answers (1)

WacławJasper
WacławJasper

Reputation: 3364

You can not copy the contents of buffers in webgl, but you can share the depth-stencil buffer between many FBOS by calling gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, renderbuffer); on the same renderbuffer.

If you want to render to the default framebuffer with stencil test enabled, you must first render to a FBO with the desired stencil renderbuffer attachment. Then just draw to the default framebuffer using that FBO's color texture.

Upvotes: 2

Related Questions