/* * Copyright 2012 Benjamin Glatzel <benjamin.glatzel@me.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.logic.manager; import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; import static org.lwjgl.opengl.GL11.GL_MODELVIEW; import static org.lwjgl.opengl.GL11.GL_PROJECTION; import static org.lwjgl.opengl.GL11.GL_QUADS; import static org.lwjgl.opengl.GL11.glBegin; import static org.lwjgl.opengl.GL11.glCallList; import static org.lwjgl.opengl.GL11.glClear; import static org.lwjgl.opengl.GL11.glColor4f; import static org.lwjgl.opengl.GL11.glEnd; import static org.lwjgl.opengl.GL11.glEndList; import static org.lwjgl.opengl.GL11.glGenLists; import static org.lwjgl.opengl.GL11.glGetTexImage; import static org.lwjgl.opengl.GL11.glLoadIdentity; import static org.lwjgl.opengl.GL11.glMatrixMode; import static org.lwjgl.opengl.GL11.glNewList; import static org.lwjgl.opengl.GL11.glPopMatrix; import static org.lwjgl.opengl.GL11.glPushMatrix; import static org.lwjgl.opengl.GL11.glTexCoord2d; import static org.lwjgl.opengl.GL11.glVertex3i; import static org.lwjgl.opengl.GL11.glViewport; import java.nio.FloatBuffer; import java.util.HashMap; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.ARBHalfFloatPixel; import org.lwjgl.opengl.ARBTextureFloat; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.EXTFramebufferObject; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL14; import org.lwjgl.opengl.GLContext; import org.terasology.game.CoreRegistry; import org.terasology.math.TeraMath; import org.terasology.rendering.shader.ShaderProgram; import org.terasology.rendering.world.WorldRenderer; /** * TODO * * @author Benjamin Glatzel <benjamin.glatzel@me.com> */ public class PostProcessingRenderer { public static final float MAX_EXPOSURE = 4.0f; public static final float MAX_EXPOSURE_NIGHT = 2.0f; public static final float MIN_EXPOSURE = 0.5f; public static final float TARGET_LUMINANCE = 1.0f; public static final float ADJUSTMENT_SPEED = 0.025f; private static PostProcessingRenderer _instance = null; private float _exposure = 16.0f; private float _sceneLuminance = 1.0f; private int _displayListQuad = -1; private long lastExposureUpdate; private boolean _extensionsAvailable = false; public class FBO { public int _fboId = 0; public int _textureId = 0; public int _depthTextureId = 0; public int _depthRboId = 0; public int _width = 0; public int _height = 0; public void bind() { EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, _fboId); } public void unbind() { EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0); } public void bindDepthTexture() { GL11.glBindTexture(GL11.GL_TEXTURE_2D, _depthTextureId); } public void bindTexture() { GL11.glBindTexture(GL11.GL_TEXTURE_2D, _textureId); } public void unbindTexture() { GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); } } private HashMap<String, FBO> _FBOs = new HashMap<String, FBO>(); /** * Returns (and creates – if necessary) the static instance * of this helper class. * * @return The instance */ public static PostProcessingRenderer getInstance() { if (_instance == null) { _instance = new PostProcessingRenderer(); } return _instance; } public PostProcessingRenderer() { _extensionsAvailable = GLContext.getCapabilities().GL_ARB_framebuffer_object; if (_extensionsAvailable) initialize(); } public void initialize() { createOrUpdateFullscreenFbos(); createFBO("sceneReflected", 512, 512, true, true); createFBO("sceneHighPass", 256, 256, false, false); createFBO("sceneBloom0", 256, 256, false, false); createFBO("sceneBloom1", 256, 256, false, false); createFBO("sceneBlur0", 512, 512, false, false); createFBO("sceneBlur1", 512, 512, false, false); createFBO("scene16", 16, 16, false, false); createFBO("scene8", 8, 8, false, false); createFBO("scene4", 4, 4, false, false); createFBO("scene2", 2, 2, false, false); createFBO("scene1", 1, 1, false, false); } public void deleteFBO(String title) { if (_FBOs.containsKey(title)) { FBO fbo = _FBOs.get(title); EXTFramebufferObject.glDeleteFramebuffersEXT(fbo._fboId); EXTFramebufferObject.glDeleteRenderbuffersEXT(fbo._depthRboId); GL11.glDeleteTextures(fbo._depthTextureId); GL11.glDeleteTextures(fbo._textureId); } } public FBO createFBO(String title, int width, int height, boolean hdr, boolean depth) { // Make sure to delete the existing FBO before creating a new one deleteFBO(title); // Create a new FBO object FBO fbo = new FBO(); fbo._width = width; fbo._height = height; // Create the color target texture fbo._textureId = GL11.glGenTextures(); GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo._textureId); GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE); GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE); if (hdr) GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, ARBTextureFloat.GL_RGBA16F_ARB, width, height, 0, GL11.GL_RGBA, ARBHalfFloatPixel.GL_HALF_FLOAT_ARB, (java.nio.ByteBuffer) null); else GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (java.nio.ByteBuffer) null); if (depth) { // Generate the depth texture fbo._depthTextureId = GL11.glGenTextures(); GL11.glBindTexture(GL11.GL_TEXTURE_2D, fbo._depthTextureId); GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE); GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE); GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL14.GL_DEPTH_COMPONENT24, width, height, 0, GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT, (java.nio.ByteBuffer) null); // Create depth render buffer object fbo._depthRboId = EXTFramebufferObject.glGenRenderbuffersEXT(); EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, fbo._depthRboId); EXTFramebufferObject.glRenderbufferStorageEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, GL14.GL_DEPTH_COMPONENT24, width, height); EXTFramebufferObject.glBindRenderbufferEXT(EXTFramebufferObject.GL_RENDERBUFFER_EXT, 0); } GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); // Create the FBO fbo._fboId = EXTFramebufferObject.glGenFramebuffersEXT(); EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, fbo._fboId); EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_COLOR_ATTACHMENT0_EXT, GL11.GL_TEXTURE_2D, fbo._textureId, 0); if (depth) { // Generate the depth render buffer and depth map texture EXTFramebufferObject.glFramebufferRenderbufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT, EXTFramebufferObject.GL_RENDERBUFFER_EXT, fbo._depthRboId); EXTFramebufferObject.glFramebufferTexture2DEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, EXTFramebufferObject.GL_DEPTH_ATTACHMENT_EXT, GL11.GL_TEXTURE_2D, fbo._depthTextureId, 0); } EXTFramebufferObject.glBindFramebufferEXT(EXTFramebufferObject.GL_FRAMEBUFFER_EXT, 0); _FBOs.put(title, fbo); return fbo; } private void updateExposure() { long currentTime = System.currentTimeMillis(); if (currentTime - lastExposureUpdate > 1000) { lastExposureUpdate = currentTime; FloatBuffer pixels = BufferUtils.createFloatBuffer(4); FBO scene = PostProcessingRenderer.getInstance().getFBO("scene1"); scene.bindTexture(); glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_FLOAT, pixels); scene.unbindTexture(); _sceneLuminance = 0.2126f * pixels.get(0) + 0.7152f * pixels.get(1) + 0.0722f * pixels.get(2); } if (_sceneLuminance > 0.0f) // No division by zero _exposure = (float) TeraMath.lerp(_exposure, TARGET_LUMINANCE / _sceneLuminance, ADJUSTMENT_SPEED); float maxExposure = MAX_EXPOSURE; if (CoreRegistry.get(WorldRenderer.class).getSkysphere().getDaylight() == 0.0) maxExposure = MAX_EXPOSURE_NIGHT; if (_exposure > maxExposure) _exposure = maxExposure; if (_exposure < MIN_EXPOSURE) _exposure = MIN_EXPOSURE; } public void beginRenderScene() { if (!_extensionsAvailable) return; getFBO("scene").bind(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } public void beginRenderReflectedScene() { if (!_extensionsAvailable) return; getFBO("sceneReflected").bind(); glViewport(0, 0, 512, 512); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } public void endRenderScene() { if (!_extensionsAvailable) return; getFBO("scene").unbind(); } public void endRenderReflectedScene() { if (!_extensionsAvailable) return; getFBO("sceneReflected").unbind(); glViewport(0, 0, Display.getWidth(), Display.getHeight()); } /** * Renders the final scene to a quad and displays it. The FBO gets automatically rescaled if the size * of the viewport changes. */ public void renderScene() { if (!_extensionsAvailable) return; if (Config.getInstance().isEnablePostProcessingEffects()) { generateDownsampledScene(); updateExposure(); generateTonemappedScene(); for (int i = 0; i < 2; i++) { generateBloom(i); generateBlur(i); } generateHighPass(); renderFinalScene(); } else { PostProcessingRenderer.FBO scene = PostProcessingRenderer.getInstance().getFBO("scene"); ShaderManager.getInstance().enableDefaultTextured(); scene.bindTexture(); renderFullQuad(); } createOrUpdateFullscreenFbos(); } private void renderFinalScene() { ShaderProgram shaderPost = ShaderManager.getInstance().getShaderProgram("post"); shaderPost.enable(); renderFullQuad(); } private void generateTonemappedScene() { ShaderManager.getInstance().enableShader("hdr"); PostProcessingRenderer.getInstance().getFBO("sceneTonemapped").bind(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderFullQuad(); PostProcessingRenderer.getInstance().getFBO("sceneTonemapped").unbind(); } /** * Initially creates the scene FBO and updates it according to the size of the viewport. */ private void createOrUpdateFullscreenFbos() { if (!_FBOs.containsKey("scene")) { createFBO("scene", Display.getWidth(), Display.getHeight(), true, true); createFBO("sceneTonemapped", Display.getWidth(), Display.getHeight(), true, false); } else { FBO scene = getFBO("scene"); if (scene._width != Display.getWidth() || scene._height != Display.getHeight()) { createFBO("scene", Display.getWidth(), Display.getHeight(), true, true); createFBO("sceneTonemapped", Display.getWidth(), Display.getHeight(), true, false); } } } private void generateHighPass() { ShaderManager.getInstance().enableShader("highp"); PostProcessingRenderer.getInstance().getFBO("sceneHighPass").bind(); glViewport(0, 0, 256, 256); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); PostProcessingRenderer.getInstance().getFBO("sceneTonemapped").bindTexture(); renderFullQuad(); PostProcessingRenderer.getInstance().getFBO("sceneHighPass").unbind(); glViewport(0, 0, Display.getWidth(), Display.getHeight()); } private void generateBlur(int id) { ShaderProgram shader = ShaderManager.getInstance().getShaderProgram("blur"); shader.enable(); shader.setFloat("radius", 2.5f); PostProcessingRenderer.getInstance().getFBO("sceneBlur" + id).bind(); glViewport(0, 0, 512, 512); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (id == 0) PostProcessingRenderer.getInstance().getFBO("sceneTonemapped").bindTexture(); else PostProcessingRenderer.getInstance().getFBO("sceneBlur" + (id - 1)).bindTexture(); renderFullQuad(); PostProcessingRenderer.getInstance().getFBO("sceneBlur" + id).unbind(); glViewport(0, 0, Display.getWidth(), Display.getHeight()); } private void generateBloom(int id) { ShaderProgram shader = ShaderManager.getInstance().getShaderProgram("blur"); shader.enable(); shader.setFloat("radius", 16.0f); PostProcessingRenderer.getInstance().getFBO("sceneBloom" + id).bind(); glViewport(0, 0, 256, 256); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (id == 0) PostProcessingRenderer.getInstance().getFBO("sceneHighPass").bindTexture(); else PostProcessingRenderer.getInstance().getFBO("sceneBloom" + (id - 1)).bindTexture(); renderFullQuad(); PostProcessingRenderer.getInstance().getFBO("sceneBloom" + id).unbind(); glViewport(0, 0, Display.getWidth(), Display.getHeight()); } private void generateDownsampledScene() { ShaderProgram shader = ShaderManager.getInstance().getShaderProgram("down"); shader.enable(); for (int i = 4; i >= 0; i--) { int sizePrev = (int) java.lang.Math.pow(2, i + 1); int size = (int) java.lang.Math.pow(2, i); shader.setFloat("size", size); PostProcessingRenderer.getInstance().getFBO("scene" + size).bind(); glViewport(0, 0, size, size); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (i == 4) PostProcessingRenderer.getInstance().getFBO("scene").bindTexture(); else PostProcessingRenderer.getInstance().getFBO("scene" + sizePrev).bindTexture(); renderFullQuad(); PostProcessingRenderer.getInstance().getFBO("scene" + size).unbind(); } glViewport(0, 0, Display.getWidth(), Display.getHeight()); } private void renderFullQuad() { glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); renderQuad(); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); } private void renderQuad() { if (_displayListQuad == -1) { _displayListQuad = glGenLists(1); glNewList(_displayListQuad, GL11.GL_COMPILE); glBegin(GL_QUADS); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glTexCoord2d(0.0, 0.0); glVertex3i(-1, -1, -1); glTexCoord2d(1.0, 0.0); glVertex3i(1, -1, -1); glTexCoord2d(1.0, 1.0); glVertex3i(1, 1, -1); glTexCoord2d(0.0, 1.0); glVertex3i(-1, 1, -1); glEnd(); glEndList(); } glCallList(_displayListQuad); } public float getExposure() { return _exposure; } public FBO getFBO(String title) { return _FBOs.get(title); } public boolean areExtensionsAvailable() { return _extensionsAvailable; } }