LibGDX / OpenGL 模板缓冲区屏蔽不工作

LibGDX / OpenGL Stencil Buffer masking not working

我正在使用 LibGDX 渲染 2d fog-of-war 类型的功能。这涉及在整个地图上绘制一个黑色矩形,其中有透明孔,您可以在其中看到下面的地图。我正在尝试使用 OpenGl 模板缓冲区来创建圆形蒙版,但我似乎无法正确理解逻辑。

下面的代码正确地绘制了深色矩形(雾),但圆形蒙版没有被模印。即整个地图都是黑暗的。

 batch.end();

    Gdx.gl.glClear(GL_STENCIL_BUFFER_BIT);
    Gdx.gl.glEnable(GL20.GL_STENCIL_TEST);
    Gdx.gl.glColorMask(false, false, false, false);
    //always write the clipped holes to the stencil
    Gdx.gl.glStencilFunc(GL20.GL_ALWAYS, 0x1, 0xffffffff);
    Gdx.gl.glStencilMask(0xFF);
    Gdx.gl.glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

    shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
    shapeRenderer.setProjectionMatrix(gameUi.camera.combined);
   
     //test circle
    shapeRenderer.circle(0, 0, 1000, 100);

    shapeRenderer.end();


    Gdx.gl.glEnable(GL20.GL_BLEND);
    Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
    Gdx.gl.glColorMask(true, true, true, true);
    Gdx.gl.glEnable(GL20.GL_STENCIL_TEST);
    //only draw the fog of war where stencil is 0
    Gdx.gl.glStencilFunc(GL_EQUAL, 0x0, 0xffffffff);
    Gdx.gl.glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);


    //draw fog of war
    shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
    shapeRenderer.setProjectionMatrix(gameUi.camera.combined);
    shapeRenderer.setColor(radarFog);
    int dimension = gameUi.mapConfig.getDimension();
    shapeRenderer.rect(-dimension,-dimension,dimension*3,dimension*3);
    shapeRenderer.end();

    Gdx.gl.glDisable(GL20.GL_STENCIL_TEST);

    batch.begin();

实现 fog-of-war 效果的一种方法是保持 Framebuffer 写入透明像素,然后绘制该缓冲区位于您的游戏视图之上:

步骤大致是:

  1. 画游戏

  2. 更新 fog-of-war 缓冲区

  3. 在游戏顶部绘制迷雾-war

    // Draw the game
    batch.setProjectionMatrix(camera.combined);
    batch.begin();
        batch.draw(map, 0, 0);
    batch.end();
    
    // Draw the fog of war to a previously initialized FrameBuffer without first clearing the buffer
    fogOfWarBuffer.begin(); 
        shapeRenderer.setProjectionMatrix(camera.combined);
        shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
            shapeRenderer.setColor(1, 1, 1, 0);
            shapeRenderer.circle(position.x, position.y, 64, 16);
        shapeRenderer.end();
    fogOfWarBuffer.end();
    
    // Draw the FrameBuffer as a texture
    batch.begin();
        batch.draw(fogOfWarRegion, 0, 0, camera.viewportWidth, camera.viewportHeight);
    batch.end();
    

我知道这在技术上无法回答您关于模板缓冲区的问题,但这是实现 fog-of-war.

的简单方法

上面动画的完整源代码;

import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;

public class FogOfWarGame extends Game {
    OrthographicCamera camera;
    ShapeRenderer shapeRenderer;
    SpriteBatch batch;
    Texture map;
    FrameBuffer fogOfWarBuffer;
    TextureRegion fogOfWarRegion;
    Vector2 position;
    Vector2 direction = new Vector2(1.0f, 0.0f);

    @Override
    public void create() {
        position = new Vector2(Gdx.graphics.getWidth()/2.0f, Gdx.graphics.getHeight()/2.0f);
        camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
        camera.position.set(camera.viewportWidth / 2.0f, camera.viewportHeight / 2.0f, 0.0f);
        camera.update();

        map = new Texture("zelda.png");

        fogOfWarBuffer = new FrameBuffer(Pixmap.Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
        fogOfWarBuffer.begin();
        {
            Gdx.gl.glClearColor(1.0f, 0.5f, 0.8f, 1.0f);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        }
        fogOfWarBuffer.end();

        fogOfWarRegion = new TextureRegion(fogOfWarBuffer.getColorBufferTexture());
        fogOfWarRegion.flip(false, true);

        shapeRenderer = new ShapeRenderer();
        batch = new SpriteBatch();
    }

    @Override
    public void render() {
        position.add(direction);
        if (MathUtils.random() > 0.9f)
            direction.rotateDeg(MathUtils.random(-45, 45));

        // Draw the game
        batch.setProjectionMatrix(camera.combined);
        batch.begin();
            batch.draw(map, 0, 0);
        batch.end();

        // Draw the fog of war to a previously initialized FrameBuffer without first clearing the buffer
        fogOfWarBuffer.begin();
            shapeRenderer.setProjectionMatrix(camera.combined);
            shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
                shapeRenderer.setColor(1, 1, 1, 0);
                shapeRenderer.circle(position.x, position.y, 64, 16);
            shapeRenderer.end();
        fogOfWarBuffer.end();

        // Draw the FrameBuffer as a texture
        batch.begin();
            batch.draw(fogOfWarRegion, 0, 0, camera.viewportWidth, camera.viewportHeight);
        batch.end();
    }
}

我终于能够让它与模板一起工作。我使用的模板操作存在一些问题,但下面的代码对我有用。

        //enable stencil and depth testing
        gl.glEnable(GL_STENCIL_TEST);
        gl.glEnable(GL_DEPTH_TEST);
        //enable blending because our fog has alpha
        Gdx.gl.glEnable(GL20.GL_BLEND);
        Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
        //stencil op which writes to stencil when test passes
        gl.glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
        //clear stencil buffer
        gl.glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        //always pass the stencil test and mask to accept all values
        gl.glStencilFunc(GL_ALWAYS, 1, 0xFF);
        gl.glStencilMask(0xFF);

        //draw our cut out circles
        drawCircles();

        //change stencil function to only match where stencil was written
        gl.glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
        //change mask so nothing is written to stencil
        gl.glStencilMask(0x00);


        drawFog();

        //disable features
        gl.glDisable(GL_STENCIL_TEST);
        gl.glDisable(GL_DEPTH_TEST);
        gl.glDisable(GL_BLEND);