import java.awt.image.BufferedImage; import java.io.File; import java.nio.*; import org.lwjgl.LWJGLException; import org.lwjgl.opengl.*; import org.lwjgl.util.glu.*; import org.lwjgl.input.Keyboard; /** * GLART_10_pbuffer.java * * Render into a Pbuffer or "pixel buffer" (a virtual screen), and save * the Pbuffer contents to a file. Pbuffers function just like the opengl * Display, but are not drawn to the screen. They can be larger than the * screen, up to the memory limits of the graphics card. * * Pbuffers can be used to create images that are larger than the screen, * and can be used to generate textures dynamically (render into the pbuffer * then copy to a texture). * * This demmo creates a Pbuffer, draws a simple image into it and saves * the image to a PNG file. * * Hit F1 to save the image to screen_capture.png and pbuffer_capture.png. * * The functions: * * makePbuffer() - create a Pbuffer object * selectPbuffer() - send render commands to the Pbuffer * selectDisplay() - send render commands to the Display * screenShot() - save the Pbuffer pixels to a PNG * * init() - creates the Pbuffer * render() - draws into the Pbuffer */ public class GLART_10_pbuffer { private boolean done = false; private final String windowTitle = "Pbuffer Demo"; private DisplayMode displayMode; private float rotation = 0f; private float rotation2 = 0f; private float rotationAmount = .08f; // Pbuffer is an LWJGL class that wraps a "pixel buffer", a virtual // screen that can be rendered to just like the Display. Pbuffer pb; // Set the Pbuffer size relative to the Display. Most OpenGL rendering // will scale to fit the Pbuffer dimensions. float pbufferScale = 2f; // flag: when true, we'll capture the current screen from the Pbuffer boolean bufferCapture = false; /** * Main function just creates and runs the application. */ public static void main(String args[]) { GLART_10_pbuffer app = new GLART_10_pbuffer(); app.run(); } /** * Initialize the app, then sit in a render loop until done==true. */ public void run() { try { init(); while (!done) { mainloop(); render(); Display.update(); } cleanup(); } catch (Exception e) { e.printStackTrace(); System.exit(0); } } /** * Initialize the environment * @throws Exception */ private void init() throws Exception { initDisplay(); initGL(displayMode.getWidth(), displayMode.getHeight()); // Random value for rotation increment: .05 - 1.0 rotationAmount = (float) (.05 + (Math.random()*.05)); // Create a pixel buffer object. May be larger than screen pb = makePbuffer( (int) (displayMode.getWidth()*pbufferScale), (int) (displayMode.getHeight()*pbufferScale) ); // "Select" the pbuffer, so rendering commands will go to the pbuffer pb = selectPbuffer(pb); // Initialize the OpenGL context FOR THE PBUFFER (which we "selected" above) initGL(displayMode.getWidth(), displayMode.getHeight()); // Select the Display, so rendering commands will now go the screen selectDisplay(); } /** * Create an OpenGL display, in this case a fullscreen window. * @throws Exception */ private void initDisplay() throws Exception { // get all possible display resolutions DisplayMode d[] = Display.getAvailableDisplayModes(); // find a resolution we like for (int i = 0; i < d.length; i++) { if (d[i].getWidth() == 800 //1024 && d[i].getHeight() == 600 //768 && d[i].getBitsPerPixel() == 32) { displayMode = d[i]; break; } } // set the display to the resolution we picked Display.setDisplayMode(displayMode); Display.setTitle(windowTitle); // if true, set to full screen, no chrome Display.setFullscreen(false); Display.setVSyncEnabled(true); // create the window Display.create(); } /** * Initialize OpenGL * */ private void initGL(int displayWidth, int displayHeight) { // Select the Projection Matrix (controls perspective) GL11.glMatrixMode(GL11.GL_PROJECTION); GL11.glLoadIdentity(); // Reset The Projection Matrix // Define perspective GLU.gluPerspective( 45.0f, // Field Of View (float)displayWidth / (float)displayHeight, // aspect ratio 0.1f, // near Z clipping plane 100.0f); // far Z clipping plane // Select The Modelview Matrix (controls model orientation) GL11.glMatrixMode(GL11.GL_MODELVIEW); // No need for depth, composition is flat GL11.glDisable(GL11.GL_DEPTH_TEST); // set the background color GL11.glClearColor(.1f, .1f, .12f, 1); GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); // To create transparencies (alpha blending) GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); } /** * Handle keyboard input. Just check for escape key or user * clicking to close the window. */ private void mainloop() { if(Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) { // Escape is pressed done = true; } if(Display.isCloseRequested()) { // Window is closed done = true; } // handle key down and up events handleKeyPressEvents(); } /** * Key event functions (keyDown() and keyUp() are are called by mainloop()). * * We set screen capture flags here, but do the screen capturing * in render(), not here. This is because the input thread does * not have access to the OpenGL display. Only the render() thread * does, so any code involving opengl has to be invoked by render(). * * @param keycode */ public void keyDown(int keycode) { // set flag to save screen (see render()) if (keycode == Keyboard.KEY_F1) { bufferCapture = true; } } public void keyUp(int keycode) { } /** * Detect changes in key state, ie. a key is pressed or released, and * call keydown() and keyup() functions. These are non-repeating events * (keydown will be called only when the key is first pressed). */ public void handleKeyPressEvents() { while ( Keyboard.next() ) { if (Keyboard.getEventKeyState()) { keyDown(Keyboard.getEventKey()); } else { keyUp(Keyboard.getEventKey()); } } } /** * Render the scene. */ private void render() { rotation += .08f; rotation2 += rotationAmount; renderFrame(); // Save framebuffer and Pbuffer pixels to PNG image // OpenGL scales most rendering operations to fit the current // screen proportions, except for glLineWidth() and glPointSize(), // which are absolute sizes and do not scale. Before drawing // to the Pbuffer I scale up the lineWidth. if (bufferCapture) { // save screen screenShot(displayMode.getWidth(), displayMode.getHeight(), "screen_capture.png"); // select Pbuffer pb = selectPbuffer(pb); // adjust the line width to match pbuffer scale GL11.glLineWidth(pbufferScale); // draw the scene into pbuffer renderFrame(); // save the pbuffer to PNG screenShot(pb.getWidth(), pb.getHeight(), "pbuffer_capture.png"); // select Display selectDisplay(); // reset the line width to normal scale GL11.glLineWidth(1); // lower the flag bufferCapture = false; } } /** * Render the scene. */ private void renderFrame() { GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); // Reset the Modelview matrix GL11.glMatrixMode(GL11.GL_MODELVIEW); GL11.glLoadIdentity(); // be sure we're in modelview mode (frameDraw switches to Projection matrix) GL11.glMatrixMode(GL11.GL_MODELVIEW); // Place the 'eye' GLU.gluLookAt( 0f, 0f, 5f, // eye position 0f, 0f, 0f, // target to look at 0f, 1f, 0f); // which way is up // rotate scene GL11.glRotatef(rotation*3.3f, 0,0,1); GL11.glTranslatef(.5f,0,0); // draw a red quad GL11.glColor4f(1,0f,0f,.7f); GL11.glPushMatrix(); { GL11.glRotatef(rotation*.7f, 0,1,1); GL11.glTranslatef(0,.5f,0); GL11.glRotatef(rotation2, 0,0,1); drawQuad(); } GL11.glPopMatrix(); // rotate more GL11.glRotatef(rotation2*4.7f, 0,0,1); GL11.glTranslatef(1,0,0); // draw a green quad GL11.glColor4f(0f,1,0f,.7f); GL11.glPushMatrix(); { GL11.glRotatef(rotation2*2f, 0,0,1); GL11.glTranslatef(-.5f,-.5f,0); GL11.glRotatef(rotation2, 0,0,1); drawQuad(); } GL11.glPopMatrix(); // draw a blue quad GL11.glColor4f(0f,0f,1,.7f); GL11.glPushMatrix(); { GL11.glRotatef(rotation2, 1,0,1); GL11.glTranslatef(.5f,-.5f,0); GL11.glRotatef(rotation2, 0,0,1); drawQuadLine(); } GL11.glPopMatrix(); // draw lines GL11.glPushMatrix(); { GL11.glRotatef(rotation2*2.1f, 1,0,1); GL11.glColor4f(0f,.5f,1,.7f); drawQuadLine(); GL11.glRotatef(2f, 1,0,1); GL11.glColor4f(0f,.6f,1,.7f); drawQuadLine(); GL11.glRotatef(3f, 1,0,1); GL11.glColor4f(0f,.8f,1,.7f); drawQuadLine(); } GL11.glPopMatrix(); } /** * draw a 1x1 square */ public void drawQuad() { GL11.glBegin(GL11.GL_QUADS); { GL11.glTexCoord2f(0, 0); GL11.glVertex3f(-1.0f,-1.0f, 0.0f); // Bottom Left GL11.glTexCoord2f(1, 0); GL11.glVertex3f( 1.0f,-1.0f, 0.0f); // Bottom Right GL11.glTexCoord2f(1, 1); GL11.glVertex3f( 1.0f, 1.0f, 0.0f); // Top Right GL11.glTexCoord2f(0, 1); GL11.glVertex3f(-1.0f, 1.0f, 0.0f); // Top left } GL11.glEnd(); } /** * draw a 1x1 square */ public void drawQuadLine() { GL11.glBegin(GL11.GL_LINE_STRIP); { GL11.glTexCoord2f(0, 0); GL11.glVertex3f(-1.0f,-1.0f, 0.0f); // Bottom Left GL11.glTexCoord2f(1, 0); GL11.glVertex3f( 1.0f,-1.0f, 0.0f); // Bottom Right GL11.glTexCoord2f(1, 1); GL11.glVertex3f( 1.0f, 1.0f, 0.0f); // Top Right GL11.glTexCoord2f(0, 1); GL11.glVertex3f(-1.0f, 1.0f, 0.0f); // Top left GL11.glTexCoord2f(0, 0); GL11.glVertex3f(-1.0f,-1.0f, 0.0f); // Bottom Left } GL11.glEnd(); } /** * Cleanup all the resources. * */ private void cleanup() { Display.destroy(); if (pb != null) { pb.destroy(); } } //======================================================================== // PBuffer functions // // Pbuffers are offscreen buffers that can be rendered into just like // the regular framebuffer. A pbuffer can be larger than the screen, // which allows for the creation of higher resolution images. // //======================================================================== /** * Create a Pbuffer for use as an offscreen buffer, with the given * width and height. Use selectPbuffer() to make the pbuffer the * context for all subsequent opengl commands. Use selectDisplay() to * make the Display the context for opengl commands. *
* @param width * @param height * @return Pbuffer * @see selectPbuffer(), selectDisplay() */ public Pbuffer makePbuffer(final int width, final int height) { Pbuffer pbuffer = null; try { pbuffer = new Pbuffer(width, height, new PixelFormat(24, //bits per pixel 8, //alpha 24, //depth 0, //stencil 0), //samples null, null); } catch (LWJGLException e) { System.out.println("GLApp.makePbuffer(): exception " + e); } return pbuffer; } /** * Make the pbuffer the current context for opengl commands. All following * gl functions will operate on this buffer instead of the display. *
* NOTE: the Pbuffer may be recreated if it was lost since last used. It's * a good idea to use: *
* pbuff = selectPbuffer(pbuff);
*
* to hold onto the new Pbuffer reference if Pbuffer was recreated.
*
* @param pb pbuffer to make current
* @return Pbuffer
* @see selectDisplay(), makePbuffer()
*/
public Pbuffer selectPbuffer(Pbuffer pb) {
if (pb != null) {
try {
// re-create the buffer if necessary
if (pb.isBufferLost()) {
int w = pb.getWidth();
int h = pb.getHeight();
System.out.println("GLApp.selectPbuffer(): Buffer contents lost - recreating the pbuffer");
pb.destroy();
pb = makePbuffer(w, h);
}
// select the pbuffer for rendering
pb.makeCurrent();
}
catch (LWJGLException e) {
System.out.println("GLApp.selectPbuffer(): exception " + e);
}
}
return pb;
}
/**
* Make the Display the current context for OpenGL commands. Subsequent
* gl functions will operate on the Display.
*
* @see selectPbuffer()
*/
public void selectDisplay()
{
try {
Display.makeCurrent();
} catch (LWJGLException e) {
System.out.println("GLApp.selectDisplay(): exception " + e);
}
}
/**
* Copy the pbuffer contents to a texture. (Should this use glCopyTexSubImage2D()?
* Is RGB the fastest format?)
*/
public static void frameSave(Pbuffer pbuff, int textureHandle) {
GL11.glBindTexture(GL11.GL_TEXTURE_2D,textureHandle);
GL11.glCopyTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, 0, 0, pbuff.getWidth(), pbuff.getHeight(), 0);
}
/**
* Save the contents of the current render buffer to a PNG image. If the current
* buffer is the framebuffer then this will work as a screen capture. Can
* also be used with the PBuffer class to copy large images or textures that
* have been rendered into the offscreen pbuffer.
* * WARNING: this function hogs memory! Not optimized. *
*/ public static void screenShot(int width, int height, String saveFilename) { // allocate space for RBG pixels ByteBuffer framebytes = allocBytes(width * height * 3); int[] pixels = new int[width * height]; int bindex; // grab a copy of the current frame contents as RGB (has to be UNSIGNED_BYTE or colors come out too dark) GL11.glReadPixels(0, 0, width, height, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, framebytes); // copy RGB data from ByteBuffer to integer array for (int i = 0; i < pixels.length; i++) { bindex = i * 3; pixels[i] = 0xFF000000 // A | ((framebytes.get(bindex) & 0x000000FF) << 16) // R | ((framebytes.get(bindex+1) & 0x000000FF) << 8) // G | ((framebytes.get(bindex+2) & 0x000000FF) << 0); // B } // free up this memory framebytes = null; // flip the pixels vertically (opengl has 0,0 at lower left, java is upper left) pixels = GLImage.flipPixels(pixels, width, height); try { // Create a BufferedImage with the RGB pixels then save as PNG BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); image.setRGB(0, 0, width, height, pixels, 0, width); javax.imageio.ImageIO.write(image, "png", new File(saveFilename)); } catch (Exception e) { System.out.println("screenShot(): exception " + e); } } public static final int SIZE_BYTE = 1; public static ByteBuffer allocBytes(int howmany) { return ByteBuffer.allocateDirect(howmany * SIZE_BYTE).order(ByteOrder.nativeOrder()); } }