Java/Advanced Graphics/OpenGL
Bloom OpenGL
<source lang="java">
/*
* Copyright (c) 2007, Romain Guy * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of the TimingFramework project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.BorderLayout; import java.awt.image.BufferedImage; import java.io.IOException; import javax.imageio.ImageIO; import javax.media.opengl.GL; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLCapabilities; import javax.media.opengl.GLEventListener; import javax.media.opengl.GLJPanel; import javax.media.opengl.glu.GLU; import javax.swing.JFrame; import javax.swing.SwingUtilities; import javax.swing.JPanel; import javax.swing.JLabel; import javax.swing.JSlider; import javax.swing.event.ChangeListener; import javax.swing.event.ChangeEvent; import com.sun.opengl.util.BufferUtil; import com.sun.opengl.util.texture.Texture; import com.sun.opengl.util.texture.TextureIO; /**
* THIS DEMO REQUIRES THE JOGL LIBRARY TO COMPILE AND EXECUTE ! * * JOGL can be found at http://jogl.dev.java.net for your OS. * * /!\ The rendering happens in FBOs so that you can get the result back into * a Java 2D image without displaying it on screen through a GLJPanel. This * implementation does not offer the conversion from FBO to a BufferedImage * but you can do it by reading the texture data from frameBufferTexture2. * * @author Romain Guy <romain.guy@mac.ru> */
public class BloomOpenGL extends GLJPanel implements GLEventListener {
private int frameBufferObject1 = -1; private int frameBufferTexture1 = -1; private int frameBufferObject2 = -1; private int frameBufferTexture2 = -1; private Texture texture; private BufferedImage image; private GLU glu = new GLU(); private String blurShaderSource = "const int MAX_KERNEL_SIZE = 25;" + "uniform sampler2D baseImage;" + "uniform vec2 offsets[MAX_KERNEL_SIZE];" + "uniform float kernelVals[MAX_KERNEL_SIZE];" + "" + "void main(void) {" + " int i;" + " vec4 sum = vec4(0.0);" + "" + " for (i = 0; i < MAX_KERNEL_SIZE; i++) {" + " vec4 tmp = texture2D(baseImage," + " gl_TexCoord[0].st + offsets[i]);" + " sum += tmp * kernelVals[i];" + " }" + "" + " gl_FragColor = sum;" + "}"; private int blurShader; private String brightPassShaderSource = "uniform sampler2D baseImage;" + "uniform float brightPassThreshold;" + "" + "void main(void) {" + " vec3 luminanceVector = vec3(0.2125, 0.7154, 0.0721);" + " vec4 sample = texture2D(baseImage, gl_TexCoord[0].st);" + "" + " float luminance = dot(luminanceVector, sample.rgb);" + " luminance = max(0.0, luminance - brightPassThreshold);" + " sample.rgb *= sign(luminance);" + " sample.a = 1.0;" + "" + " gl_FragColor = sample;" + "}"; private int brightPassShader; private float threshold = 0.3f; public BloomOpenGL() { super(new GLCapabilities()); addGLEventListener(this); try { image = ImageIO.read(getClass().getResource("screen.png")); } catch (IOException e) { e.printStackTrace(); } } public void init(GLAutoDrawable glAutoDrawable) { GL gl = glAutoDrawable.getGL(); if (texture == null) { texture = TextureIO.newTexture(image, false); } // create the blur shader blurShader = createFragmentProgram(gl, new String[] { blurShaderSource }); gl.glUseProgramObjectARB(blurShader); int loc = gl.glGetUniformLocationARB(blurShader, "baseImage"); gl.glUniform1iARB(loc, 0); gl.glUseProgramObjectARB(0); // create the bright-pass shader brightPassShader = createFragmentProgram(gl, new String[] { brightPassShaderSource }); gl.glUseProgramObjectARB(brightPassShader); loc = gl.glGetUniformLocationARB(brightPassShader, "baseImage"); gl.glUniform1iARB(loc, 0); gl.glUseProgramObjectARB(0); // create the FBOs if (gl.isExtensionAvailable("GL_EXT_framebuffer_object")) { int[] fboId = new int[1]; int[] texId = new int[1]; createFrameBufferObject(gl, fboId, texId, image.getWidth(), image.getHeight()); frameBufferObject1 = fboId[0]; frameBufferTexture1 = texId[0]; createFrameBufferObject(gl, fboId, texId, image.getWidth(), image.getHeight()); frameBufferObject2 = fboId[0]; frameBufferTexture2 = texId[0]; } } private static void createFrameBufferObject(GL gl, int[] frameBuffer, int[] colorBuffer, int width, int height) { gl.glGenFramebuffersEXT(1, frameBuffer, 0); gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, frameBuffer[0]); gl.glGenTextures(1, colorBuffer, 0); gl.glBindTexture(GL.GL_TEXTURE_2D, colorBuffer[0]); gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, width, height, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, BufferUtil.newByteBuffer(width * height * 4)); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); gl.glFramebufferTexture2DEXT(GL.GL_FRAMEBUFFER_EXT, GL.GL_COLOR_ATTACHMENT0_EXT, GL.GL_TEXTURE_2D, colorBuffer[0], 0); gl.glBindTexture(GL.GL_TEXTURE_2D, 0); int status = gl.glCheckFramebufferStatusEXT(GL.GL_FRAMEBUFFER_EXT); if (status == GL.GL_FRAMEBUFFER_COMPLETE_EXT) { gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, 0); } else { throw new IllegalStateException("Frame Buffer Oject not created."); } } private static void viewOrtho(GL gl, int width, int height) { gl.glMatrixMode(GL.GL_PROJECTION); gl.glPushMatrix(); gl.glLoadIdentity(); gl.glOrtho(0, width, height, 0, -1, 1); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPushMatrix(); gl.glLoadIdentity(); } private static void renderTexturedQuad(GL gl, float width, float height, boolean flip) { gl.glBegin(GL.GL_QUADS); gl.glTexCoord2f(0.0f, flip ? 1.0f : 0.0f); gl.glVertex2f(0.0f, 0.0f); gl.glTexCoord2f(1.0f, flip ? 1.0f : 0.0f); gl.glVertex2f(width, 0.0f); gl.glTexCoord2f(1.0f, flip ? 0.0f : 1.0f); gl.glVertex2f(width, height); gl.glTexCoord2f(0.0f, flip ? 0.0f : 1.0f); gl.glVertex2f(0.0f, height); gl.glEnd(); } private static int createFragmentProgram(GL gl, String[] fragmentShaderSource) { int fragmentShader, fragmentProgram; int[] success = new int[1]; // create the shader object and compile the shader source code fragmentShader = gl.glCreateShaderObjectARB(GL.GL_FRAGMENT_SHADER_ARB); gl.glShaderSourceARB(fragmentShader, 1, fragmentShaderSource, null); gl.glCompileShaderARB(fragmentShader); gl.glGetObjectParameterivARB(fragmentShader, GL.GL_OBJECT_COMPILE_STATUS_ARB, success, 0); // print the compiler messages, if necessary int[] infoLogLength = new int[1]; int[] length = new int[1]; gl.glGetObjectParameterivARB(fragmentShader, GL.GL_OBJECT_INFO_LOG_LENGTH_ARB, infoLogLength, 0); if (infoLogLength[0] > 1) { byte[] b = new byte[1024]; gl.glGetInfoLogARB(fragmentShader, 1024, length, 0, b, 0); System.out.println("Fragment compile phase = " + new String(b, 0, length[0])); } if (success[0] == 0) { gl.glDeleteObjectARB(fragmentShader); return -1; } // create the program object and attach it to the shader fragmentProgram = gl.glCreateProgramObjectARB(); gl.glAttachObjectARB(fragmentProgram, fragmentShader); // it is now safe to delete the shader object gl.glDeleteObjectARB(fragmentShader); // link the program gl.glLinkProgramARB(fragmentProgram); gl.glGetObjectParameterivARB(fragmentProgram, GL.GL_OBJECT_LINK_STATUS_ARB, success, 0); gl.glGetObjectParameterivARB(fragmentShader, GL.GL_OBJECT_INFO_LOG_LENGTH_ARB, infoLogLength, 0); if (infoLogLength[0] > 1) { byte[] b = new byte[1024]; gl.glGetInfoLogARB(fragmentShader, 1024, length, 0, b, 0); System.out.println("Fragment link phase = " + new String(b, 0, length[0])); } if (success[0] == 0) { gl.glDeleteObjectARB(fragmentProgram); return -1; } return fragmentProgram; } private static void enableBlurFragmentProgram(GL gl, int program, float textureWidth, float textureHeight) { gl.glUseProgramObjectARB(program); int kernelWidth = 5; int kernelHeight = 5; float xoff = 1.0f / textureWidth; float yoff = 1.0f / textureHeight; float[] offsets = new float[kernelWidth * kernelHeight * 2]; int offsetIndex = 0; for (int i = -kernelHeight / 2; i < kernelHeight / 2 + 1; i++) { for (int j = -kernelWidth / 2; j < kernelWidth / 2 + 1; j++) { offsets[offsetIndex++] = j * xoff; offsets[offsetIndex++] = i * yoff; } } int loc = gl.glGetUniformLocationARB(program, "offsets"); gl.glUniform2fv(loc, offsets.length, offsets, 0); float[] values = createGaussianBlurFilter(2); loc = gl.glGetUniformLocationARB(program, "kernelVals"); gl.glUniform1fvARB(loc, values.length, values, 0); } private static float[] createGaussianBlurFilter(int radius) { if (radius < 1) { throw new IllegalArgumentException("Radius must be >= 1"); } int size = radius * 2 + 1; float[] data = new float[size * size]; float sigma = radius / 3.0f; float twoSigmaSquare = 2.0f * sigma * sigma; float sigmaRoot = (float) Math.sqrt(twoSigmaSquare * Math.PI); float total = 0.0f; int index = 0; for (int y = -radius; y <= radius; y++) { for (int x = -radius; x <= radius; x++) { float distance = x * x + y * y; data[index] = (float) Math.exp(-distance / twoSigmaSquare) / sigmaRoot; total += data[index]; index++; } } for (int i = 0; i < data.length; i++) { data[i] /= total; } return data; } private static void enableBrightPassFragmentProgram(GL gl, int program, float threshold) { gl.glUseProgramObjectARB(program); int loc = gl.glGetUniformLocationARB(program, "brightPassThreshold"); gl.glUniform1fARB(loc, threshold); } private static void disableFragmentProgram(GL gl) { gl.glUseProgramObjectARB(0); } public void display(GLAutoDrawable glAutoDrawable) { GL gl = glAutoDrawable.getGL(); gl.glLoadIdentity(); gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); viewOrtho(gl, image.getWidth(), image.getHeight()); gl.glEnable(GL.GL_TEXTURE_2D); int width = image.getWidth(); int height = image.getHeight(); // Source Image/bright pass on FBO1 renderBrightPass(gl, width, height); // Source image on FBO2 renderImage(gl, width, height); // On screen renderTextureOnScreen(gl, width, height); //render5x5(gl, width, height); render11x11(gl, width, height); render21x21(gl, width, height); render41x41(gl, width, height); gl.glDisable(GL.GL_TEXTURE_2D); gl.glFlush(); } private void render41x41(GL gl, int width, int height) { // FBO1/blur on FBO2 renderBlur(gl, width / 8.0f, height / 8.0f); // Add on screen gl.glPushMatrix(); gl.glTranslatef(0.0f, -height * 7.0f, 0.0f); renderAddTextureOnScreen(gl, width * 8.0f, height * 8.0f); gl.glPopMatrix(); } private void render21x21(GL gl, int width, int height) { // FBO1/blur on FBO2 renderBlur(gl, width / 4.0f, height / 4.0f); // Add on screen gl.glPushMatrix(); gl.glTranslatef(0.0f, -height * 3.0f, 0.0f); renderAddTextureOnScreen(gl, width * 4.0f, height * 4.0f); gl.glPopMatrix(); } private void render11x11(GL gl, int width, int height) { // FBO1/blur on FBO2 renderBlur(gl, width / 2.0f, height / 2.0f); // Add on screen gl.glPushMatrix(); gl.glTranslatef(0.0f, -height, 0.0f); renderAddTextureOnScreen(gl, width * 2.0f, height * 2.0f); gl.glPopMatrix(); } private void render5x5(GL gl, int width, int height) { // FBO1/blur on FBO2 renderBlur(gl, width, height); // Add on screen renderAddTextureOnScreen(gl, width, height); } private void renderAddTextureOnScreen(GL gl, float width, float height) { gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE); renderTextureOnScreen(gl, width, height); gl.glDisable(GL.GL_BLEND); } private void renderTextureOnScreen(GL gl, float width, float height) { // Draw the texture on a quad gl.glBindTexture(GL.GL_TEXTURE_2D, frameBufferTexture2); renderTexturedQuad(gl, width, height, false); gl.glBindTexture(GL.GL_TEXTURE_2D, 0); } private void renderBrightPass(GL gl, float width, float height) { // Draw into the FBO gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, frameBufferObject1); gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); enableBrightPassFragmentProgram(gl, brightPassShader, threshold); gl.glBindTexture(GL.GL_TEXTURE_2D, texture.getTextureObject()); renderTexturedQuad(gl, width, height, texture.getMustFlipVertically()); gl.glBindTexture(GL.GL_TEXTURE_2D, 0); disableFragmentProgram(gl); gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, 0); } private void renderImage(GL gl, float width, float height) { // Draw into the FBO gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, frameBufferObject2); gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); gl.glBindTexture(GL.GL_TEXTURE_2D, texture.getTextureObject()); renderTexturedQuad(gl, width, height, texture.getMustFlipVertically()); gl.glBindTexture(GL.GL_TEXTURE_2D, 0); gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, 0); } private void renderBlur(GL gl, float width, float height) { // Draw into the FBO gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, frameBufferObject2); gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); enableBlurFragmentProgram(gl, blurShader, width, height); gl.glBindTexture(GL.GL_TEXTURE_2D, frameBufferTexture1); renderTexturedQuad(gl, width, height, texture.getMustFlipVertically()); gl.glBindTexture(GL.GL_TEXTURE_2D, 0); disableFragmentProgram(gl); gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, 0); } @Override public Dimension getPreferredSize() { return new Dimension(image.getWidth(), image.getHeight()); } public void reshape(GLAutoDrawable glAutoDrawable, int x, int y, int width, int height) { GL gl = glAutoDrawable.getGL(); gl.glViewport(0, 0, width, height); gl.glMatrixMode(GL.GL_PROJECTION); gl.glLoadIdentity(); glu.gluPerspective(50, (float) width / height, 5, 2000); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glLoadIdentity(); } public void displayChanged(GLAutoDrawable glAutoDrawable, boolean modeChanged, boolean deviceChanged) { } public float getThreshold() { return threshold; } public void setThreshold(float threshold) { this.threshold = threshold; repaint(); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { final BloomOpenGL bloom; final JSlider slider; JFrame f = new JFrame("Bloom OpenGL"); f.add(bloom = new BloomOpenGL()); JPanel controls = new JPanel(new FlowLayout(FlowLayout.LEADING)); controls.add(new JLabel("Bloom: 0.0")); controls.add(slider = new JSlider(0, 100, 30)); slider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { JSlider slider = (JSlider) e.getSource(); float threshold = slider.getValue() / 100.0f; bloom.setThreshold(threshold); } }); controls.add(new JLabel("1.0")); f.add(controls, BorderLayout.SOUTH); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.pack(); f.setLocationRelativeTo(null); f.setResizable(false); f.setVisible(true); } }); }
}
</source>