Rendering skybox without post-processing effects such as bloom


#1

Hello,

I am having some problems with rendering skybox without post-processing effects. I was able to get this setup to work by using two framebuffers and by sharing a single stencil buffer between the framebuffers. The issue with this method is that most of the graphics cards does not seem to support this kind of stencil buffer sharing between the two framebuffers. The more commonly supported technique seems to be by using GL_DEPTH24_STENCIL8 when constructing the framebuffer, but there is no way to share the stencil buffer between the framebuffers when this packed format is being used? I have tried both texture framebuffers and render buffer objects.

Here is the way I got this to work with shared stencil buffer:

-first I create two framebuffers (let's call them normalfb and bloomfb) and share stencil component between them. After this the code moves to rendering loop:

-clear color, depth and stencil

-draw objects to normalfb

-apply the following settings: 
glStencilFunc(GL20.GL_ALWAYS, 1, 0xFF)
glStencilMask(0xFF)
glDepthFunc(GL20.GL_LEQUAL)
glDepthMask(false)

-render skybox/cubemap

-set:
glDepthFunc(GL20.GL_LESS)
glDepthMask(true)
glStencilMask(0x00)

-start to the bloomfb

-clear depth and color buffers

-set: 
glStencilFunc(GL20.GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00)

-draw normalfb to bloomfb

-clear stencil buffer

-do rest of the bloom rendering on bloomfb

-finally combine normalfb and bloomfb and render the combination to screen.

Here is the matching code for the pseudocode above, but I assume the pseudocode is more easy to follow. The language is Kotlin.


    fun render(delta : Float, drawables: Array<Drawable>, drawablesBlendSkyBox: Array<Drawable>, cam: Camera) {
        Gdx.gl20.glEnable(GL20.GL_DEPTH_TEST)
        Gdx.gl20.glDepthFunc(GL20.GL_LESS)

        Gdx.gl20.glEnable(GL20.GL_STENCIL_TEST);
        Gdx.gl20.glStencilOp(GL20.GL_KEEP, GL20.GL_KEEP, GL20.GL_REPLACE);

        Gdx.gl20.glClearColor(0f, 0f, 0f, 1f)
        Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT)
        Gdx.gl20.glClear(GL20.GL_DEPTH_BUFFER_BIT)
        
        normalScene.renderOnThis {
            normalScene.pb.setUniform("darkAmount", 1.0f)
            Gdx.gl20.glClearColor(0f, 0f, 0f, 0f)
            Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT or GL20.GL_DEPTH_BUFFER_BIT)
            Gdx.gl20.glStencilMask(0xFF);
            Gdx.gl20.glClear(GL20.GL_STENCIL_BUFFER_BIT)

            Gdx.gl20.glStencilMask(0x00);
            // render scene objects
            drawables.forEach { it.render(delta, cam) }

            Gdx.gl20.glStencilMask(0xFF);
            Gdx.gl20.glClear(GL20.GL_STENCIL_BUFFER_BIT)

            if (cfg.enableSkybox) {

                Gdx.gl20.glStencilFunc(GL20.GL_ALWAYS, 1, 0xFF);
                Gdx.gl20.glStencilMask(0xFF);

                // render cubemap
                Gdx.gl20.glDepthFunc(GL20.GL_LEQUAL)
                Gdx.gl20.glDepthMask(false)
                cubemap.render(delta, cam)
                Gdx.gl20.glDepthMask(true)
                Gdx.gl20.glDepthFunc(GL20.GL_LESS)
                Gdx.gl20.glStencilMask(0x00);
            }

            drawablesBlendSkyBox.forEach { it.render(delta, cam) }
        }

        Gdx.gl20.glStencilMask(0x00);

        if (cfg.enableBloom) {

            hdr.renderOnThis {
                Gdx.gl20.glClear(GL20.GL_COLOR_BUFFER_BIT or GL20.GL_DEPTH_BUFFER_BIT)


                // set stencil to pass everything except 0xFF
                Gdx.gl20.glStencilFunc(GL20.GL_NOTEQUAL, 1, 0xFF);
                Gdx.gl20.glStencilMask(0x00); // don't make more marks to stencil buffer
                normalScene.render(delta)

                Gdx.gl20.glStencilMask(0xFF);
                Gdx.gl20.glClear(GL20.GL_STENCIL_BUFFER_BIT)
                Gdx.gl20.glStencilMask(0x00);
            }

            extractBright.renderOnThis {
                hdr.render(delta)
            }

            vertBlur.renderOnThis {
                extractBright.render(delta)
            }

            // some messy bloom configs...
            val div = MathUtils.clamp(cfg.bloomDiv, 4.0f, 20f)
            //val kernel = arrayOf(0.382925f, 0.24173f,	0.060598f)
            val kernel = arrayOf(0.382925f,	0.24173f,	0.060598f,	0.005977f,	0.000229f)
            //val kernel = arrayOf(0.382925f, 0.32173f,	0.100598f)
            for (i in 0..kernel.size-1) {
                vertBlur.pb.setUniform("w"+i.toString(), kernel[i])
            }
            vertBlur.pb.setUniform("horizontal", 0)
            vertBlur.pb.setUniform("targetHeight", cfg.virtualHeight / div)

            horiBlur.renderOnThis {
                vertBlur.render(delta)
            }

            for (i in 0..kernel.size-1) {
                horiBlur.pb.setUniform("w"+i.toString(), kernel[i])
            }
            horiBlur.pb.setUniform("horizontal", 1)
            horiBlur.pb.setUniform("targetHeight", cfg.virtualWidth / div)

            horiBlur.renderOnThis {
                horiBlur.render(delta)
            }

            viewport.apply()

            // combine framebuffer textures "original scene" and "extractBright"
            // and render to screen
            combineBloom.texture[0] = normalScene.pb.texture[0]!!
            combineBloom.texture[1] = lastPass.pb.texture[0]!!
            combineBloom.setUniform("bloomIntensity", cfg.bloomIntensity)

            if (cfg.screenFadeDarkAmount < 1.0f) {
                lastPass.renderOnThis {
                    lastPass.pb.setUniform("darkAmount", cfg.screenFadeDarkAmount)
                    lastPass.pb.setUniform("brightAmount", cfg.screenFadeBrightAmount)
                    combineBloom.render(delta, cam)
                }
                viewport.apply()
                lastPass.render(delta)
            }
            else {
                viewport.apply()
                combineBloom.render(delta, cam)
            }
        }
        else {
                lastPass.renderOnThis {
                    lastPass.pb.setUniform("darkAmount", cfg.screenFadeDarkAmount)
                    normalScene.render(delta)
                }
                viewport.apply()
                lastPass.render(delta)
        }


        sb.projectionMatrix = viewport.camera.combined
    }

Now, is there a way to do this same thing when both normalfb and bloomfb are using the packed format GL_DEPTH24_STENCIL8? At this point I am willing to sacrifice some performance for compability if there is some way to fix this, like:

  1. copy stencil buffer from normalfb to bloomfb
  2. use third framebuffer to draw the skybox on it alone and combine the three framebuffers together before finally drawing to the screen

Here is my vendor, version and renderer information. I am using OpenGLES2 through the LWJGL / libGDX.

vendor: Intel Open Source Technology Center
renderer: Mesa DRI Intel(R) HD Graphics 620 (Kaby Lake GT2) 
version: 3.0 Mesa 18.3.3

#2

Is there some reason you can’t just use the same stencil/depth image in both framebuffers?


#3

Your words are packed with wisdom. I actually don’t need the depth information with skybox so using the same depth and stencil on both framebuffers does the trick! Thank you very much for pointing this out! Works nicely now on my both machines.