这个系列教程 是https://github.com/mattdesl/lwjgl-basics/wiki的系列翻译。
2d和3d的编程的一个基本特性就是可以渲染多个sprite成为一个纹理( render-to-texture)。例如,我们有一个由两个sprite组成的滑动UI,我们使它的不透明度渐变。当以50%的不透明度渲染它们,重合的部分会出现一些我们不想要的混合现象:
左边的图形是100%的不透明度,中间图像的问题是每个精灵都是50%的不透明度重合部分我们能看到下面的sprite。而右边是我们想要的结果,先把所有sprite处理为一个texture( render-to-texture),然后再以50%的透明度渲染这个texture。
另一种利用 render-to-texture的作用是处理特效。我们先把所有的sprite处理为一个为屏幕大小的texture,然后在用shader去处理它,渲染到屏幕。
Note
在我们继续之前,我们必须指出,利用FBOs技术,特别是在每一个帧都用到的时候。可能导致性能的下降,因为它会进行多次转换状态,批量刷新, 缓存清理, 和 着色器的更新。所以在使用之前我们仔细考虑。
Frame Buffer Objects(FBO)
在OpenGL中我们为了render-to-texture,我们必须建立一个FBO。我们将利用Framebuffer类 使过程更简单些。
首先我们创建一个frame buffer 对象:
try {
fbo = new FrameBuffer(width, height, Texture.NEAREST);
} catch (LWJGLException e) {
... if the FBO could not be created ...
}
为了最大的兼容性,我们应该把长和宽设置成2的幂,因为我们会把FrameBuffer转换为Texture。这样可以避免一些我们之前教程里说的硬件限制。
你可以利用Framebuffer.isSupported()去查看硬件是否支持帧缓存。如果返回是false,创建Framebuffer的时候会报错。只有特别老的版本才会出现这种情况,而且多数这时候都不支持shader。这里给一个统计大约93%的驱动支持GL_EXT_framebuffer_object
。当用户不支持的时候你最好建议他们去更新显卡或者驱动。
下面是一段利用FBO的伪代码:
//make the FBO the current buffer
fbo.begin()
//... clear the FBO color with transparent black ...
glClearColor(0f, 0f, 0f, 0f); //transparent black
glClear(GL_COLOR_BUFFER_BIT); //clear the color buffer
//since the FBO may not be the same size as the display,
//we need to give the SpriteBatch our new screen dimensions
batch.resize(fbo.getWidth(), fbo.getHeight());
//render some sprites
batch.begin();
//draw our track and thumb button
batch.draw(track, ...);
batch.draw(slider, ...);
batch.end(); //flushes data to GL
//now we can unbind the FBO, returning rendering back to the default back buffer (the Display)
fbo.end();
//reset the batch back to the Display width/height
batch.resize(Display.getWidth(), Display.getHeight());
//now we are rendering to the back buffer (Display) again
batch.begin();
//draw our offscreen FBO texture to the screen with the given alpha
batch.setColor(1f, 1f, 1f, alpha);
batch.draw(fbo, 0, 0);
batch.end();
如果现在你用下面的sprite sheet去测试:
当50%不透明度的时候会如下图:
混合的苦恼
不透明的背景有些难看,但是只要我们以去掉它,我们在alpha通道就会丢掉一些信息:
这是因为我们进行了两次混合,当我们把sprite放入FBO时我们和背景进行了一次混合,当我们把sprite渲染到屏幕的时候我们又进行了一些混合,第二次混合使sprite比实际看起来更加透明了。
一种解决方案是使用FBO之前调用glDisable(GL_BLEND),或者使用一个不和背景混合的函数。但不幸的是,我们在一个sprite之上渲染另一个半透明的sprite就会用到混合。另一种方法是按下面的流程去做:
- 清理 FBO 为 black (0, 0, 0, 0).
- 设置 RGB 混合混合函数为 GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA and
设置 Alpha 混合函数为GLONE, GL_ONE. - 以100%的不透明度渲染sprites到FBO.
- 结束批处理,解除FBO的绑定.
- 使"pre-multipled alpha" blending, GL_ONE,
GL_ONE_MINUS_SRC_ALPHA. - 用batch.setColor(alpha, alpha, alpha, alpha) to 调整不透明度.
- 渲染FBO.
结果如下图:
全屏FBOs 和后期处理:
像我们之前提到的,一些老的驱动不知道支持非2次幂的长和宽。当我们处理全屏FBO时,这是非常头疼的。因为很少有真正的屏幕的的长宽是2的次幂。
解决方法是利用TextureRegion 去渲染2的次幂的Texture的一部分。由于GL和TextureRegion 的坐标系不同,我们要做一下转换,代码如下:
TextureRegion fboRegion; //the region of our POT frame buffer
FrameBuffer fbo; //our POT frame buffer with a color texture attached
...
if (Texture.isNPOTSupported()) {
fbo = new FrameBuffer(width, height);
fboRegion = new TextureRegion(fbo.getTexture());
} else {
int texWidth = Texture.toPowerOfTwo(width);
int texHeight = Texture.toPowerOfTwo(height);
fbo = new FrameBuffer(texWidth, texHeight);
fboRegion = new TextureRegion(fbo.getTexture(), 0, texHeight-height, width, height);
}
fboRegion.flip(false, true);
...
后期处理的伪代码,如下:
//make offscreen texture active
fbo.begin();
//standard shader
batch.setShader(DEFAULT_SHADER);
//if FBO size == Display size, we can omit this
batch.resize(fbo.getWidth(), fbo.getHeight());
batch.begin();
... render all sprites here ...
batch.end();
//unbind FBO
fbo.end();
//now apply post-processing
batch.setShader(POST_PROCESS_SCENE);
//resize to display since we are no longer rendering to a FBO texture
batch.resize(Display.getWidth(), Display.getHeight());
//draw screen, will be affected by shader
batch.begin();
batch.draw(fboRegion, 0, 0);
batch.end();