libgdx - 画刷线条绘制的混合函数?(加法/非加法混合)

3
我正在为两点之间的每一步在帧缓冲区上绘制精灵,但我在混合方面遇到了问题:
我目前使用的是:
(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);
然后,在我的着色器中,我执行:
if( color.a == 0 )
  color.rgb = 0;

然而,如果你看红色和绿色的星星,你会看到它们之间的“加性混合”,只有当alpha值在(0,1)之间时才会发生。
没有加性混合,将出现这种3D效果:

enter image description here


是否有一种方法可以让相似颜色进行加性混合,但是避免颜色不匹配的情况? 它应该看起来像这样:

enter image description here


我本来想手动构建一个着色器,将画笔和帧缓存传递进去,但我对投影理解不够,无法为两个纹理获取正确的纹理坐标。 - JoeOfTex
1个回答

2
您可以使用预乘alpha来完成此操作。
首先,您需要正确设置纹理。将其绘制成黑白相间的形式,如下所示:
然后,将纹理的亮度复制到它的alpha通道中。在GIMP中,您可以右键单击图层,选择添加图层蒙版,然后选择“灰度复制层”。它看起来有点像这样(如Gimp所示):
然后,如果需要,您可以重新着色RGB通道,尽管在代码中将其保留为白色并对其进行着色更加灵活。
在代码中,当您绘制形状时,请使用预乘alpha的混合函数:
batch.setBlendFunction(GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA);

使用默认的SpriteBatch着色器绘制它。在绘制完形状后,切换回常规的alpha混合:

batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);

示例游戏:

这里输入图片描述

public class GameMain extends ApplicationAdapter implements InputProcessor{
    SpriteBatch batch;

    Texture star;
    Color color = new Color();
    Viewport viewport;
    int screenWidth, screenHeight;
    FrameBuffer fbo;
    boolean shouldClearFBO = false;
    boolean fboUpdated = false;
    OrthographicCamera fullScreenCamera;

    boolean touchDown = false;
    Vector2 touchLocation = new Vector2();

    int colorIndex = 0;
    static final int[] COLORS = {0xff0000ff, 0x00ff00ff, 0x0000ffff, 0xffff00ff, 0xff00ffff, 0x00ffffff, 0xffffffff};

    static final float SIZE = 150;

    @Override
    public void create () {
        batch = new SpriteBatch();
        star = new Texture("star.png");
        viewport = new ExtendViewport(800, 480);
        fullScreenCamera = new OrthographicCamera();
        Gdx.input.setInputProcessor(this);
    }

    @Override
    public void resize (int width, int height){
        viewport.update(width, height, true);
        fullScreenCamera.setToOrtho(false, width, height);
        fullScreenCamera.position.set(width/2, height/2, 0);
        fullScreenCamera.update();
        fboUpdated = false;
        screenWidth = width;
        screenHeight = height;
    }

    @Override
    public void render () {
        if (!fboUpdated){
            fboUpdated = true;
            if (fbo!=null)
                fbo.dispose();
            fbo = new FrameBuffer(Pixmap.Format.RGBA8888, screenWidth, screenHeight, false);
            fbo.begin();
            Gdx.gl.glClearColor(0, 0, 0, 1);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
            fbo.end();
        }

        color.set(COLORS[colorIndex]);

        fbo.begin();
        if (shouldClearFBO){
            shouldClearFBO = false;
            Gdx.gl.glClearColor(0, 0, 0, 1);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        }
        if (touchDown){
            batch.setProjectionMatrix(viewport.getCamera().combined);
            batch.setBlendFunction(GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA);
            batch.begin();
            batch.setColor(color);
            batch.draw(star, touchLocation.x - SIZE/2, touchLocation.y - SIZE/2, SIZE, SIZE);
            batch.end();
        }
        fbo.end();

        Gdx.gl.glClearColor(0, 0, 0, 1);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        batch.setColor(Color.WHITE);
        batch.setProjectionMatrix(fullScreenCamera.combined);
        batch.setBlendFunction(GL20.GL_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
        batch.begin();
        batch.draw(fbo.getColorBufferTexture(), 0, screenHeight, screenWidth, -screenHeight);
        batch.end();
    }

    @Override
    public boolean keyDown(int keycode) {
        switch (keycode){
            case Input.Keys.SPACE:
                colorIndex = (++colorIndex) % COLORS.length;
                return true;
            case Input.Keys.X:
                shouldClearFBO = true;
                return true;
        }

        return false;
    }

    @Override
    public boolean keyUp(int keycode) {
        return false;
    }

    @Override
    public boolean keyTyped(char character) {
        return false;
    }

    @Override
    public boolean touchDown(int screenX, int screenY, int pointer, int button) {
        viewport.unproject(touchLocation.set(screenX, screenY));
        if (pointer==1){
                colorIndex = (++colorIndex) % COLORS.length;
                return true;
        }
        if (pointer == 2){
            shouldClearFBO = true;
            return true;
        }
        touchDown = true;
        return true;
    }

    @Override
    public boolean touchUp(int screenX, int screenY, int pointer, int button) {
        touchDown = false;
        return true;
    }

    @Override
    public boolean touchDragged(int screenX, int screenY, int pointer) {
        viewport.unproject(touchLocation.set(screenX, screenY));
        return true;
    }

    @Override
    public boolean mouseMoved(int screenX, int screenY) {
        return false;
    }

    @Override
    public boolean scrolled(int amount) {
        return false;
    }
}

令人印象深刻!当我尝试复制时,即使使用batch.setBlendFunction(GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA),我仍然得到了3D画笔效果。我相信这可能与我使用的图集有关,该图集是使用libgdx提供的TexturePacker创建的。我将继续尝试,并很快在此更新。 - JoeOfTex
TexturePacker有一个premultiplyAlpha选项。因此,我认为您可以按照正常方式创建精灵(不要在源图像中进行预乘),并依靠TexturePacker来完成它。您的源图像可能在RGB通道中是纯白色,并且在alpha通道中看起来像上面的第一张图片。我还没有尝试过这个方法。 - Tenfour04
它运行了! 按照您的指示,通过GIMP处理图像完美地工作。 虽然,我不确定如何在Photoshop上复制此方法。 我知道我的问题,我的纹理需要使RGB值与α值匹配。 目前,它们所有的颜色都为(1,1,1),而alpha是会改变的。 - JoeOfTex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接