Search code examples
androidopengl-eslibgdxframebufferblending

Libgdx: Framebuffer for "Fog of War"-Effect


I am writing a RTS Game for Android and I want the "Fog of War" effect on the player's units. This effect means that only a "circle" around each unit shows the background map while on places where no player unit is located, the screen should be black. I don't want to use shaders.

I have a first version of it working. What I am doing is to render the map to the default framebuffer, then I have a second Framebuffer (similar to light technics) which is completely black. Where the units of the players are, I then batch-draw a texture which is completely transparent and has a white circle with blurred edges in its middle. Finally I draw the second (light) FrameBuffer's colorTexture over the first one using Gdx.gl.glBlendFunc(GL20.GL_DST_COLOR, GL20.GL_ZERO);

The visual effect now is that indeed the whole map is black and a circle around my units is visible - but a lot of white color is added. The reason is pretty clear as I drew the light textures for the units like this:

lightBuffer.begin();
        Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE);

        Gdx.gl.glEnable(GL20.GL_BLEND);

        Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        batch.setProjectionMatrix(camera.combined);
        batch.begin();

        batch.setColor(1f, 1f, 1f, 1f);
        for (RTSTroopAction i : unitList) {
                batch.draw(lightSprite, i.getX() + (i.getWidth() / 2) - 230, i.getY() + (i.getHeight() / 2) - 230, 460, 460); //, 0, 0, lightSprite.getWidth(), lightSprite.getHeight(), false, true);

        }

        batch.end();
        lightBuffer.end();

However, I don't want the "white stuff" on the original texture, I just want the original background shine through. How can I achieve that ?

I think it's playing around with the blendFuncs, but I was not able to figure out which values to use yet.


Solution

  • Thanks to Tenfour04 pointing into the right direction, I was able to find the solution. First of all, the problem is not directly within batch.end();. The problem is, that indeed a sprite batch maintains its own blendFunc Settings. These get applied when flush(); is called. (end() calls it also ). However the batch is also calling flush when it draws a TextureRegion that is bound to a different texture than the one used in the previous draw() call.

    So in my original code: whatever blendFunc I had set was always overridden when I called batch.draw(lightBuffer,...). The solution is to use the spritebatch's blendFunc and not the Gdx.gl.blendFunc.

    The total working code finally looks like this:

    lightBuffer.begin();
            Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
            Gdx.gl.glEnable(GL20.GL_BLEND);          
    // start rendering to the lightBuffer
    // set the ambient color values, this is the "global" light of your scene
            Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1f);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    // start rendering the lights 
            batch.setProjectionMatrix(camera.combined);
            batch.begin();
    // set the color of your light (red,green,blue,alpha values)
            batch.setColor(1f, 1f, 1f, 1f);
            for (RTSTroopAction i : unitList) {
                if (i.getOwnerId() == game.getCallback().getPlayerId()) {
                    batch.draw(lightSprite, i.getX() + (i.getWidth() / 2) - 230, i.getY() + (i.getHeight() / 2) - 230, 460, 460); //, 0, 0, lightSprite.getWidth(), lightSprite.getHeight(), false, true);
                }
            }
            batch.end();
            lightBuffer.end();
    

    // now we render the lightBuffer to the default "frame buffer" // with the right blending !

            Gdx.gl.glEnable(GL20.GL_BLEND);
            Gdx.gl.glBlendFunc(GL20.GL_ZERO, GL20.GL_SRC_COLOR);
            batch.setProjectionMatrix(getStage().getCamera().combined);
            batch.enableBlending();
            batch.setBlendFunction(GL20.GL_ZERO, GL20.GL_SRC_COLOR);
            batch.begin();
    
            Gdx.gl.glEnable(GL20.GL_BLEND);
            Gdx.gl.glBlendFunc(GL20.GL_ZERO, GL20.GL_SRC_COLOR);
            batch.draw(lightBufferRegion,0, 0, getStage().getWidth(), getStage().getHeight());
            batch.end();
            batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);