package demos.renderToTexture; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import javax.media.opengl.DebugGL; import javax.media.opengl.GL; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLCanvas; import javax.media.opengl.GLEventListener; import javax.media.opengl.GLException; import javax.swing.JFrame; import com.sun.opengl.utils.GLUT; public class RenderToTextureFBO implements GLEventListener { private static GLUT glut = new GLUT(); public static void main(String[] args) { RenderToTextureFBO renderer = new RenderToTextureFBO(); GLCanvas canvas = new GLCanvas(); canvas.addGLEventListener(renderer); JFrame frame = new JFrame("Render To Texture Demo"); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { System.exit(0); } }); frame.getContentPane().add(canvas); canvas.setSize(300, 300); frame.pack(); frame.setVisible(true); } private static float[] lightPosition = new float[] { 1, 0, 10, 1 }; private FramebufferObjectRenderer framebufferRenderer; private boolean isFramebufferRendererCreated = false; public void init(GLAutoDrawable drawable) { drawable.setGL(new DebugGL(drawable.getGL())); GL gl = drawable.getGL(); gl.glEnable(GL.GL_DEPTH_TEST); gl.glEnable(GL.GL_NORMALIZE); gl.glEnable(GL.GL_LIGHTING); gl.glClearColor(0.5f, 0.5f, 0.5f, 0); // Place the camera gl.glMatrixMode(GL.GL_PROJECTION); gl.glLoadIdentity(); gl.glFrustum(-2.0 / 10, 2.0 / 10, -2.0 / 10, 2.0 / 10, 1, 11); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glLoadIdentity(); gl.glTranslated(0, 0, -10); // Configure the light gl.glEnable(GL.GL_LIGHT0); gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, lightPosition, 0); if (!isFramebufferRendererCreated) { framebufferRenderer = new FramebufferObjectRenderer(256); framebufferRenderer.init(drawable); isFramebufferRendererCreated = true; } } public void display(GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); framebufferRenderer.renderToFrameBuffer(drawable); // Draw the teapot casting a shadow on a repeated picture of the // offscreen colour buffer. // We'll use multi-texture unit 1 for the shadowing... framebufferRenderer.prepareForShadowedRendering(drawable, GL.GL_TEXTURE1); // ... and multi-texture unit 0 for the picture framebufferRenderer.prepareForColouredRendering(drawable, GL.GL_TEXTURE0); drawSquare(drawable); framebufferRenderer.stopColouredRendering(drawable); drawFloatingTeapot(drawable); framebufferRenderer.stopShadowedRendering(drawable, GL.GL_TEXTURE1); } private static void drawFloatingTeapot(GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glPushAttrib(GL.GL_TRANSFORM_BIT); { gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPushMatrix(); { gl.glTranslated(0.5, 0, 3); glut.glutSolidTeapot(0.25, true); } gl.glPopMatrix(); } gl.glPopAttrib(); } private static void drawSquare(GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glBegin(GL.GL_QUADS); { gl.glNormal3d(0, 0, 1); gl.glMultiTexCoord2d(GL.GL_TEXTURE0, 0, 0); gl.glVertex3d(-1, -1, 0); gl.glMultiTexCoord2d(GL.GL_TEXTURE0, 2, 0); gl.glVertex3d(1, -1, 0); gl.glMultiTexCoord2d(GL.GL_TEXTURE0, 2, 2); gl.glVertex3d(1, 1, 0); gl.glMultiTexCoord2d(GL.GL_TEXTURE0, 0, 2); gl.glVertex3d(-1, 1, 0); } gl.glEnd(); } public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { // Nothing to do } public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { // Nothing to do } private static class FramebufferObjectRenderer { private int textureDimension; private int frameBufferID; private int colourTextureID; private int depthTextureID; public FramebufferObjectRenderer(int textureDimension) { super(); this.textureDimension = textureDimension; } public void init(GLAutoDrawable drawable) { GL gl = drawable.getGL(); // Allocate the framebuffer object int[] result = new int[1]; gl.glGenFramebuffersEXT(1, result, 0); frameBufferID = result[0]; gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, frameBufferID); // Allocate the colour texture gl.glGenTextures(1, result, 0); colourTextureID = result[0]; gl.glBindTexture(GL.GL_TEXTURE_2D, colourTextureID); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE); gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA8, textureDimension, textureDimension, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, null); // Allocate the depth texture gl.glGenTextures(1, result, 0); depthTextureID = result[0]; gl.glBindTexture(GL.GL_TEXTURE_2D, depthTextureID); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE); gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE); gl.glGetIntegerv(GL.GL_DEPTH_BITS, result, 0); int depthFormat; switch (result[0]) { case 16: depthFormat = GL.GL_DEPTH_COMPONENT16; break; case 24: depthFormat = GL.GL_DEPTH_COMPONENT24; break; case 32: depthFormat = GL.GL_DEPTH_COMPONENT32; break; default: throw new GLException("Unexpected number of depth bits: " + result[0]); } gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, depthFormat, textureDimension, textureDimension, 0, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT, null); // Attach the textures to the framebuffer gl.glFramebufferTexture2DEXT(GL.GL_FRAMEBUFFER_EXT, GL.GL_COLOR_ATTACHMENT0_EXT, GL.GL_TEXTURE_2D, colourTextureID, 0); gl.glFramebufferTexture2DEXT(GL.GL_FRAMEBUFFER_EXT, GL.GL_DEPTH_ATTACHMENT_EXT, GL.GL_TEXTURE_2D, depthTextureID, 0); gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, 0); } private int counter = 0; public void renderToFrameBuffer(GLAutoDrawable drawable) { GL gl = drawable.getGL(); gl.glPushAttrib(GL.GL_TRANSFORM_BIT | GL.GL_ENABLE_BIT | GL.GL_COLOR_BUFFER_BIT); gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, frameBufferID); gl.glEnable(GL.GL_DEPTH_TEST); gl.glEnable(GL.GL_NORMALIZE); gl.glDisable(GL.GL_LIGHTING); gl.glClearColor(0, 0, 1, 0); // Place the offscreen camera at the light position gl.glMatrixMode(GL.GL_PROJECTION); gl.glPushMatrix(); gl.glLoadIdentity(); applyLightFrustum(gl); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPushMatrix(); gl.glLoadIdentity(); applyLightTransformation(gl); gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); // Draw the teapot as a red silhouette if (counter++ % 2 == 0) { gl.glColor3d(1, 0, 0); } else { gl.glColor3d(0, 1, 0); } drawFloatingTeapot(drawable); // Don't draw the square (because we don't want it to cast a shadow) // Unbind the framebuffer gl.glBindFramebufferEXT(GL.GL_FRAMEBUFFER_EXT, 0); gl.glMatrixMode(GL.GL_PROJECTION); gl.glPopMatrix(); gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPopMatrix(); gl.glPopAttrib(); } private void applyLightFrustum(GL gl) { // Set up the viewing frustum so that the light only sees the // volume between itself and the unit X-Y square at the origin // (because that is the only volume in which we will be casting // shadows). gl.glFrustum(-2.0 / 10, 0, -1.0 / 10, 1.0 / 10, 1, 10); } private void applyLightTransformation(GL gl) { gl.glTranslated(-lightPosition[0], -lightPosition[1], -lightPosition[2]); } public void prepareForShadowedRendering(GLAutoDrawable targetDrawable, int textureUnitID) { GL gl = targetDrawable.getGL(); // The state modified by this preparation will be restored by a call // to finishShadowedRendering() gl.glPushAttrib(GL.GL_TEXTURE_BIT); gl.glActiveTexture(textureUnitID); gl.glBindTexture(GL.GL_TEXTURE_2D, depthTextureID); gl.glPushAttrib(GL.GL_TEXTURE_BIT); // Generate texture coordinates - an object-linear texture // generation that sets the texture coordinates to be the // object-space vertex coordinates (we will use the texture matrix // to transform from object-space to light-space) gl.glEnable(GL.GL_TEXTURE_GEN_S); gl.glEnable(GL.GL_TEXTURE_GEN_T); gl.glEnable(GL.GL_TEXTURE_GEN_R); gl.glEnable(GL.GL_TEXTURE_GEN_Q); gl.glTexGeni(GL.GL_S, GL.GL_TEXTURE_GEN_MODE, GL.GL_OBJECT_LINEAR); gl.glTexGeni(GL.GL_T, GL.GL_TEXTURE_GEN_MODE, GL.GL_OBJECT_LINEAR); gl.glTexGeni(GL.GL_R, GL.GL_TEXTURE_GEN_MODE, GL.GL_OBJECT_LINEAR); gl.glTexGeni(GL.GL_Q, GL.GL_TEXTURE_GEN_MODE, GL.GL_OBJECT_LINEAR); gl.glPushAttrib(GL.GL_TRANSFORM_BIT); { gl.glMatrixMode(GL.GL_MODELVIEW); gl.glPushMatrix(); { gl.glLoadIdentity(); float[] objectPlane = new float[] { 1, 0, 0, 0 }; gl.glTexGenfv(GL.GL_S, GL.GL_OBJECT_PLANE, objectPlane, 0); objectPlane[0] = 0; objectPlane[1] = 1; gl.glTexGenfv(GL.GL_T, GL.GL_OBJECT_PLANE, objectPlane, 0); objectPlane[1] = 0; objectPlane[2] = 1; gl.glTexGenfv(GL.GL_R, GL.GL_OBJECT_PLANE, objectPlane, 0); objectPlane[2] = 0; objectPlane[3] = 1; gl.glTexGenfv(GL.GL_Q, GL.GL_OBJECT_PLANE, objectPlane, 0); } gl.glPopMatrix(); // Modify texture matrix - the texture matrix will transform // from object-space coordinates to light-space (i.e. the same // transformation that was applied when rendering the shadow // texture) gl.glMatrixMode(GL.GL_TEXTURE); // The texture matrix will be restored by // stopShadowedRendering() gl.glPushMatrix(); gl.glLoadIdentity(); // Go from the unit square to the upper quadrant (because // texture coords run from 0 to 1) gl.glScalef(0.5f, 0.5f, 0.5f); gl.glTranslatef(1, 1, 1); // Apply the transformation to light-space applyLightFrustum(gl); applyLightTransformation(gl); } gl.glPopAttrib(); // Set the texture up to be used as a depth comparison int textureTarget = GL.GL_TEXTURE_2D; gl.glEnable(textureTarget); gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP_TO_EDGE); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP_TO_EDGE); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_COMPARE_MODE, GL.GL_COMPARE_R_TO_TEXTURE); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_COMPARE_FUNC, GL.GL_LEQUAL); gl.glTexParameteri(textureTarget, GL.GL_DEPTH_TEXTURE_MODE, GL.GL_LUMINANCE); } public void stopShadowedRendering(GLAutoDrawable targetDrawable, int textureUnitID) { GL gl = targetDrawable.getGL(); gl.glActiveTexture(textureUnitID); gl.glPushAttrib(GL.GL_TRANSFORM_BIT); { gl.glMatrixMode(GL.GL_TEXTURE); gl.glPopMatrix(); } gl.glPopAttrib(); // Restore the texture attributes for the textureUnith texture gl.glPopAttrib(); // Unbind the depth texture gl.glBindTexture(GL.GL_TEXTURE_2D, 0); // Restore the active texture gl.glPopAttrib(); } public void prepareForColouredRendering(GLAutoDrawable targetDrawable, int textureUnitID) { GL gl = targetDrawable.getGL(); // The state modified by this preparation will be restored by a call // to finishShadowedRendering() gl.glPushAttrib(GL.GL_TEXTURE_BIT); gl.glActiveTexture(textureUnitID); gl.glBindTexture(GL.GL_TEXTURE_2D, colourTextureID); // Set the texture up to be used for painting a surface int textureTarget = GL.GL_TEXTURE_2D; gl.glEnable(textureTarget); gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_MODULATE); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT); gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT); } public void stopColouredRendering(GLAutoDrawable targetDrawable) { GL gl = targetDrawable.getGL(); gl.glBindTexture(GL.GL_TEXTURE_2D, 0); // Restore the active texture gl.glPopAttrib(); } } }