【问题标题】:LibGdx Is there a way to apply shader to a section of the sprite batch for a water effect?LibGdx 有没有办法将着色器应用于精灵批次的一部分以获得水效果?
【发布时间】:2015-07-30 02:32:10
【问题描述】:

所以我对一个矩形图像应用了水效果,这是我的水,以在其上应用正弦波函数。仅适用于此TextureRegion

Water.java

public  void updateshaders(){

    float dt = Gdx.graphics.getDeltaTime();

    if(waterShader != null){

        time += dt ;
        float angle = time * (2 * MathUtils.PI);
        if (angle > (2 * MathUtils.PI))
            angle -= (2 * MathUtils.PI);

        Gdx.gl20.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
        Gdx.gl20.glEnable(GL20.GL_BLEND);
        waterShader.setUniformMatrix("u_projTrans",  gs.cam.combined);

        waterShader.begin();
        waterShader.setUniformf("timedelta", -angle);
        waterShader.end();
    }
}
@Override
public void draw(SpriteBatch g) {
    g.end();
    updateshaders();
    g.setProjectionMatrix(gs.cam.combined);
    g.setShader(waterShader);
    g.begin();
    g.draw(gs.gsm.rm.water_top, x, y + height - gs.gsm.rm.water_top.getRegionHeight(), width, gs.gsm.rm.water_top.getRegionHeight());
    g.draw(gs.gsm.rm.water, x, y, width, height - gs.gsm.rm.water_top.getRegionHeight());
    g.end();
    g.setShader(null);
    g.begin();
}

我想将此效果添加到红色矩形中的所有内容。我正在考虑刷新SpriteBatch 并切出该区域的图像,应用失真然后在原始图像上重新绘制它,然后完成我的渲染线程的其余部分。

更新: 所以我的解决方案奏效了……有点。它非常慢。它看起来是正确的,但会使游戏无法玩且速度慢。 (在 gif 中可能有点难以注意到,但在游戏中看起来不错。)

新代码:

Water.java

public void draw(SpriteBatch g) {
    g.end();
    updateshaders();
    //Distortion
    coords = gs.cam.project(new Vector3(x,y,0));
    if(scr != null){scr.getTexture().dispose();}
    scr = ScreenUtils.getFrameBufferTexture(
            (int)coords.x,(int)coords.y,
            (int)(width * scaleX),(int)((height - gs.gsm.rm.water_top.getRegionHeight() / 4) * scaleY));

    if(scr != null){
        g.setShader(waterShader2);
        g.begin();
        g.draw(scr,
            x,y,
            width,height- gs.gsm.rm.water_top.getRegionHeight() / 4);
        g.end();
    }
    //SURFACE WAVES
    g.setShader(waterShader);
    g.begin();
    g.draw(gs.gsm.rm.water_top, x, y + height - gs.gsm.rm.water_top.getRegionHeight(), width, gs.gsm.rm.water_top.getRegionHeight());
    g.end();
    //BACK TO NORMAL
    g.setShader(null);
    g.begin();
    g.draw(gs.gsm.rm.water, x, y, width, height - gs.gsm.rm.water_top.getRegionHeight());
}

更新: 我尝试了 Tenfour04 的解决方案,它奏效了……有点。虽然存在失真效果,并且游戏以全 FPS 运行,但失真会使背景显示出来。这是因为着色器被应用于纹理,而不仅仅是在我抓取的区域范围内:

    scr = new TextureRegion(((PlayGameState) gs).frameBuffer.getColorBufferTexture(),
            (int)coords.x, (int)coords.y,
            (int)(width * scaleX),(int)((height - gs.gsm.rm.water_top.getRegionHeight() / 4) * scaleY));

【问题讨论】:

    标签: java opengl libgdx


    【解决方案1】:

    在每一帧上,您都丢弃并重新创建像素图和纹理,然后将屏幕捕获到该纹理。这是一个非常缓慢的操作,一遍又一遍地重复。

    相反,您可以将游戏直接渲染到持久的离屏帧缓冲区。

    private FrameBuffer frameBuffer;
    private final Matrix4 idt = new Matrix4();
    
    public void render() {
        if (frameBuffer == null){
            //Normally this would go in the resize method, but that causes issues on iOS 
            //because resize isn't always called on the GL thread in iOS. So lazy load here.
            try {
                frameBuffer = new FrameBuffer(Format.RGBA8888, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
            } catch (GdxRuntimeException e){ 
                frameBuffer = new FrameBuffer(Format.RGB565, Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), false);
                //RGBA8888 not supported on all devices. You might instead want to turn off 
                //the water effect if it's not supported, because 565 is kinda ugly.
            }
        }
    
        frameBuffer.begin();
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        //draw everything that is behind the water layer here
        frameBuffer.end();
    
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
        //Draw the frame buffer texture to the screen:
        spriteBatch.setProjectionMatrix(idt);
        spriteBatch.begin();
        spriteBatch.draw(frameBuffer.getColorBufferTexture(), -1, 1, 2, -2); //IIRC, you need to vertically flip it. If I remembered wrong, then do -1, -1, 2, 2
        spriteBatch.end();
    
        water.draw(spriteBatch, frameBuffer.getColorBufferTexture());
    
        //draw stuff that is in front of the water here
    }
    

    然后修改你的水类如下。如果我没记错的话,屏幕纹理是颠倒的,所以你可能需要翻转一些数学。我没有在下面检查您的数学,以了解您是否已经考虑到这一点。

    public void draw(SpriteBatch g, Texture scr) {
        updateshaders();
        //Distortion
        coords = gs.cam.project(new Vector3(x,y,0));
    
        if(scr != null){
            g.setShader(waterShader2);
            g.begin();
            g.draw(scr,
                x,y,
                width,height- gs.gsm.rm.water_top.getRegionHeight() / 4);
            g.end();
        }
        //SURFACE WAVES
        g.setShader(waterShader);
        g.begin();
        g.draw(gs.gsm.rm.water_top, x, y + height - gs.gsm.rm.water_top.getRegionHeight(), width, gs.gsm.rm.water_top.getRegionHeight());
        g.end();
        //BACK TO NORMAL
        g.setShader(null);
        g.begin();
        g.draw(gs.gsm.rm.water, x, y, width, height - gs.gsm.rm.water_top.getRegionHeight());
    }
    

    在此过程中未优化的一件事是,您最终将所有像素绘制在水层后面至少两次。一次将其绘制在帧缓冲区上,然后第二次将帧缓冲区绘制到屏幕上。这可以稍后通过仅在被水覆盖的视图区域中绘制水后的东西来优化(如果您受填充率限制),然后将帧缓冲区纹理绘制到屏幕上的步骤替换为绘制背景的东西正常。这只有在您成功构建 RGBA8888 帧缓冲区时才会看起来不错,并非所有 Android 都支持。

    【讨论】:

    • 唯一的事情是我必须在水被绘制之前将投影矩阵改回相机,而且我必须采取帧缓冲纹理的区域来实现我的目标
    • 您的解决方案非常适合性能部分,但我如何确保着色器仅应用于红色框中的“TextureRegion”?
    • 整个纹理被绘制到屏幕上,然后你的水类重新绘制需要效果的区域。我假设您已经使用从 ScreenUtils 获得的屏幕大小的纹理来处理该部分。由于所有变量,不确定您的水课程是如何工作的。
    • ScreenUtils 获得了我从屏幕上剪下的纹理,但这个解决方案获得了整个屏幕的纹理,应用着色器,然后绘制区域。如果有办法,我想将我正在创建的纹理区域制作为纹理。
    • 也许我的措辞不清楚。当纹理是整个屏幕时,着色器中的正弦波将像素从区域外部拉到区域中。我不希望这种情况发生..
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-06
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多