一、Off-Screen Rendering
一些画面效果经常需要离屏渲染,离屏渲染需要创建一个独立的FBO来绘制,在绘制处理完成后再把绘制内容提取出来绘制到游戏显示的FBO里。
这个过程因为涉及到FBO的切换,所以需要跟OpenGL底层打交道。虽说用gdx-vfx库里的VfxFrameBuffer应该是可以自动处理OpenGL的嵌套FrameBuffer的问题的,但gdx-vfx的FrameBuffer限制了只能用TextureAttach的,如果想要使用MSAA之类的特性就不支持了,那只好自己写一个。
主要处理FBO绑定、Viewport和裁剪开关的备份和恢复,需要给裁减开关是因为通常在布局UI中会有一些布局裁剪会设置裁剪区间,如果不临时禁用裁剪的话在离屏FBO上也会被裁剪。
public class GLStateMaintainer {
private final IntBuffer viewport = BufferUtils.newIntBuffer(16);
private final IntBuffer previousFboId = BufferUtils.newIntBuffer(1);
private int vpx, vpy, vpWidth, vpHeight, previousFbo;
private boolean scissorEnabled;
private void backup() {
Gdx.gl.glGetIntegerv(GL20.GL_VIEWPORT, viewport);
vpx = viewport.get(0);
vpy = viewport.get(1);
vpWidth = viewport.get(2);
vpHeight = viewport.get(3);
Gdx.gl.glGetIntegerv(GL20.GL_FRAMEBUFFER_BINDING, previousFboId);
previousFbo = previousFboId.get(0);
scissorEnabled = Gdx.gl.glIsEnabled(GL20.GL_SCISSOR_TEST);
if (scissorEnabled) {
Gdx.gl.glDisable(GL20.GL_SCISSOR_TEST);
}
}
public void execute(Runnable runnable) {
backup();
runnable.run();
restore();
}
private void restore() {
Gdx.gl.glBindFramebuffer(GL20.GL_FRAMEBUFFER, previousFbo);
Gdx.gl.glViewport(vpx, vpy, vpWidth, vpHeight);
if (scissorEnabled) {
Gdx.gl.glEnable(GL20.GL_SCISSOR_TEST);
}
}
}
实际使用的时候大概是这样的:
public void draw(Batch batch, float x, float y, float width, float height {
batch.end();
glStateMaintainer.execute(() -> {
offScreenFbo.begin();
Gdx.gl.glViewport(0, 0, (int) width, (int) height);
Gdx.gl.glClearColor(0, 0, 0, 0);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT | GL20.GL_STENCIL_BUFFER_BIT);
// 使用batch绘制内容,建议使用独立的batch避免维护投影矩阵,
// offScreenBatch.begin();
// offScreenBatch.drawSomething();
// offScreenBatch.end();
offScreenFbo.end();
});
batch.begin();
}
二、GLSL 1.30
libGDX默认使用的GLSL语法是v1.20(Desktop和GWT),在安卓和iOS上是v1.00,我不打算做移动端,所以可以升级一下版本,能方便不少。
在shader文件头加上:
#version 130
就可以使用GLSL v1.30(ES 3.00)的新接口和语法了,例如:
- 用
ivec2 textureSize(gsampler2D sampler, int lod)获取sample2D的大小,可以省去每次需要通过外部传递uniform的形式传递渲染区域的大小。 - 有统一的texture接口。
- 可以用in/out而不是attribute/varying了,可以更好的实现多pass shader。
要在程序启动参数中启用GL30。
-
Deskop项目里要设置setOpenGLEmulation:
Lwjgl3ApplicationConfiguration configuration = new Lwjgl3ApplicationConfiguration(); configuration.setOpenGLEmulation(Lwjgl3ApplicationConfiguration.GLEmulation.GL30, 3, 2); -
GWT项目则是简单一个:
GwtApplicationConfiguration cfg = new GwtApplicationConfiguration(false); cfg.useGL30 = true;
三、MSAA
抗锯齿要启用GL30,上面已经有启用方式了。
但GWT项目里不能用,虽然也可以开启GL30,但multi-sample不能设置大于1,所以在GWT release里要禁用MSAA。
要独立创建带multi-sample的FrameBuffer,只要是矢量图绘制到这个上就会自带抗锯齿了。
FrameBuffer msaaBuffer = new FrameBuffer.FrameBufferBuilder(width, height, 8)
.addColorRenderBuffer(GL30.GL_RGBA)
.addBasicStencilDepthPackedRenderBuffer()
.build());
但是由于没有TextureAttachement,所以msaaBuffer不能直接获取Texture,实际使用通常需要resolve到其他的FBO去。比如resolve到VfxManager上:
private final VfxManager vfxManager;
private FrameBuffer msaaBuffer;
private void init() {
this.msaaBuffer = new GLFrameBuffer.FrameBufferBuilder(width, height, samples)
.addColorRenderBuffer(GL30.GL_RGBA)
.addBasicStencilDepthPackedRenderBuffer()
.build();
}
public void draw(Batch batch, float x, float y, float width, float height) {
msaaBuffer.begin();
// batch.drawSomething();
msaaBuffer.end();
vfxManager.cleanUpBuffers();
msaaBuffer.transfer(vfxManager.getResultBuffer().getFbo());
vfxManager.applyEffects();
vfxManager.renderToScreen();
}


Leave a Reply