Java tutorial
/* * Copyright LWJGL. All rights reserved. * License terms: http://lwjgl.org/license.php */ package org.lwjgl.demo.opengl.fbo; import org.lwjgl.BufferUtils; import org.lwjgl.demo.opengl.raytracing.Scene; import org.lwjgl.demo.opengl.util.DemoUtils; import org.lwjgl.glfw.*; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GLCapabilities; import org.lwjgl.opengl.GLUtil; import org.lwjgl.system.Callback; import org.joml.Matrix4f; import org.joml.Vector3f; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import static java.lang.Math.*; import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL15.*; import static org.lwjgl.opengl.GL20.*; import static org.lwjgl.opengl.EXTFramebufferObject.*; import static org.lwjgl.system.MemoryUtil.*; /** * Showcases simple reconstruction of the world position from the depth buffer. * <p> * It uses a depth attachment texture to render depth-only to the FBO. * Afterwards, the view-space or world-space coordinates are reconstructed * via the depth values from the depth texture. * <p> * In order to do this, first the inverse of either the view-projection matrix * is computed (for world-space reconstruction) or the inverse of the projection * matrix (for view-space reconstruction). This matrix is uploaded to a shader. * The fragment shader reads the depth values from the depth buffer and * transforms those values by the computed matrix. * * @author Kai Burjack */ public class ReadDepthBufferDemo { /** * The scene as (min, max) axis-aligned boxes. */ private static Vector3f[] boxes = Scene.boxes; private long window; private int width = 1024; private int height = 768; private boolean resetFramebuffer = true; private int depthTexture; private int fullScreenQuadVbo; private int fullScreenQuadProgram; private int depthOnlyProgram; private int fbo; private int vboScene; private int viewProjMatrixUniform; private int inverseMatrixUniform; private Matrix4f camera = new Matrix4f(); private Matrix4f invCamera = new Matrix4f(); private float mouseDownX; private float mouseX; private boolean mouseDown; private boolean reconstructViewSpace; private float currRotationAboutY = 0.0f; private float rotationAboutY = 0.0f; private Vector3f tmpVector = new Vector3f(); private Vector3f cameraLookAt = new Vector3f(0.0f, 0.5f, 0.0f); private Vector3f cameraUp = new Vector3f(0.0f, 1.0f, 0.0f); private FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(16); GLFWErrorCallback errCallback; GLFWKeyCallback keyCallback; GLFWCursorPosCallback cpCallback; GLFWFramebufferSizeCallback fbCallback; GLFWMouseButtonCallback mbCallback; Callback debugProc; private void init() throws IOException { glfwSetErrorCallback(errCallback = new GLFWErrorCallback() { private GLFWErrorCallback delegate = GLFWErrorCallback.createPrint(System.err); @Override public void invoke(int error, long description) { if (error == GLFW_VERSION_UNAVAILABLE) System.err.println("This demo requires OpenGL 2.0 or higher."); delegate.invoke(error, description); } @Override public void free() { delegate.free(); } }); if (!glfwInit()) throw new IllegalStateException("Unable to initialize GLFW"); glfwDefaultWindowHints(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); window = glfwCreateWindow(width, height, "Sample depth buffer", NULL, NULL); if (window == NULL) { throw new AssertionError("Failed to create the GLFW window"); } System.out.println("Press key 'V' to toggle between view-space and world-space reconstruction."); glfwSetKeyCallback(window, keyCallback = new GLFWKeyCallback() { @Override public void invoke(long window, int key, int scancode, int action, int mods) { if (action != GLFW_RELEASE) return; if (key == GLFW_KEY_ESCAPE) { glfwSetWindowShouldClose(window, true); } else if (key == GLFW_KEY_V) { reconstructViewSpace = !reconstructViewSpace; } } }); glfwSetCursorPosCallback(window, cpCallback = new GLFWCursorPosCallback() { @Override public void invoke(long window, double x, double y) { ReadDepthBufferDemo.this.mouseX = (float) x; } }); glfwSetFramebufferSizeCallback(window, fbCallback = new GLFWFramebufferSizeCallback() { @Override public void invoke(long window, int width, int height) { if (width > 0 && height > 0 && (ReadDepthBufferDemo.this.width != width || ReadDepthBufferDemo.this.height != height)) { ReadDepthBufferDemo.this.width = width; ReadDepthBufferDemo.this.height = height; ReadDepthBufferDemo.this.resetFramebuffer = true; } } }); glfwSetMouseButtonCallback(window, mbCallback = new GLFWMouseButtonCallback() { @Override public void invoke(long window, int button, int action, int mods) { if (action == GLFW_PRESS) { ReadDepthBufferDemo.this.mouseDownX = ReadDepthBufferDemo.this.mouseX; ReadDepthBufferDemo.this.mouseDown = true; } else if (action == GLFW_RELEASE) { ReadDepthBufferDemo.this.mouseDown = false; ReadDepthBufferDemo.this.rotationAboutY = ReadDepthBufferDemo.this.currRotationAboutY; } } }); GLFWVidMode vidmode = glfwGetVideoMode(glfwGetPrimaryMonitor()); glfwSetWindowPos(window, (vidmode.width() - width) / 2, (vidmode.height() - height) / 2); glfwMakeContextCurrent(window); glfwSwapInterval(0); glfwShowWindow(window); IntBuffer framebufferSize = BufferUtils.createIntBuffer(2); nglfwGetFramebufferSize(window, memAddress(framebufferSize), memAddress(framebufferSize) + 4); width = framebufferSize.get(0); height = framebufferSize.get(1); GLCapabilities caps = GL.createCapabilities(); if (!caps.GL_EXT_framebuffer_object) { throw new AssertionError("This demo requires the EXT_framebuffer_object extension"); } debugProc = GLUtil.setupDebugMessageCallback(); /* Create all needed GL resources */ createDepthTexture(); createFramebufferObject(); createFullScreenVbo(); createSceneVbo(); createDepthOnlyProgram(); createFullScreenQuadProgram(); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); } private void createFullScreenVbo() { int vbo = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, vbo); ByteBuffer bb = BufferUtils.createByteBuffer(4 * 2 * 6); FloatBuffer fv = bb.asFloatBuffer(); fv.put(-1.0f).put(-1.0f); fv.put(1.0f).put(-1.0f); fv.put(1.0f).put(1.0f); fv.put(1.0f).put(1.0f); fv.put(-1.0f).put(1.0f); fv.put(-1.0f).put(-1.0f); glBufferData(GL_ARRAY_BUFFER, bb, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); fullScreenQuadVbo = vbo; } private void createSceneVbo() { int vbo = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, vbo); ByteBuffer bb = BufferUtils.createByteBuffer(boxes.length / 2 * 4 * (3 + 3) * 6 * 6); FloatBuffer fv = bb.asFloatBuffer(); for (int i = 0; i < boxes.length; i += 2) { DemoUtils.triangulateBox(boxes[i], boxes[i + 1], fv); } glBufferData(GL_ARRAY_BUFFER, bb, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); this.vboScene = vbo; } private void createFramebufferObject() { this.fbo = glGenFramebuffersEXT(); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); glDrawBuffer(GL_NONE); // we are not rendering to color buffers! glReadBuffer(GL_NONE); // we are also not reading from color buffers! glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depthTexture, 0); int fboStatus = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); if (fboStatus != GL_FRAMEBUFFER_COMPLETE_EXT) { throw new AssertionError("Could not create FBO: " + fboStatus); } glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } private void createFullScreenQuadProgram() throws IOException { int program = glCreateProgram(); int vshader = DemoUtils.createShader("org/lwjgl/demo/opengl/fbo/quadDepth.vs", GL_VERTEX_SHADER); int fshader = DemoUtils.createShader("org/lwjgl/demo/opengl/fbo/quadDepth.fs", GL_FRAGMENT_SHADER); glAttachShader(program, vshader); glAttachShader(program, fshader); glBindAttribLocation(program, 0, "vertex"); glLinkProgram(program); int linked = glGetProgrami(program, GL_LINK_STATUS); String programLog = glGetProgramInfoLog(program); if (programLog.trim().length() > 0) { System.err.println(programLog); } if (linked == 0) { throw new AssertionError("Could not link program"); } this.fullScreenQuadProgram = program; glUseProgram(fullScreenQuadProgram); int texUniform = glGetUniformLocation(fullScreenQuadProgram, "tex"); inverseMatrixUniform = glGetUniformLocation(fullScreenQuadProgram, "inverseMatrix"); glUniform1i(texUniform, 0); glUseProgram(0); } private void createDepthOnlyProgram() throws IOException { int program = glCreateProgram(); int vshader = DemoUtils.createShader("org/lwjgl/demo/opengl/fbo/rasterDepth.vs", GL_VERTEX_SHADER); int fshader = DemoUtils.createShader("org/lwjgl/demo/opengl/fbo/rasterDepth.fs", GL_FRAGMENT_SHADER); glAttachShader(program, vshader); glAttachShader(program, fshader); glBindAttribLocation(program, 0, "vertexPosition"); glLinkProgram(program); int linked = glGetProgrami(program, GL_LINK_STATUS); String programLog = glGetProgramInfoLog(program); if (programLog.trim().length() > 0) { System.err.println(programLog); } if (linked == 0) { throw new AssertionError("Could not link program"); } this.depthOnlyProgram = program; glUseProgram(depthOnlyProgram); viewProjMatrixUniform = glGetUniformLocation(depthOnlyProgram, "viewProjMatrix"); glUseProgram(0); } private void createDepthTexture() { this.depthTexture = glGenTextures(); glBindTexture(GL_TEXTURE_2D, depthTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, (ByteBuffer) null); glBindTexture(GL_TEXTURE_2D, 0); } private void resizeFramebufferTexture() { glDeleteTextures(depthTexture); glDeleteFramebuffersEXT(fbo); createDepthTexture(); createFramebufferObject(); } private void update() { if (mouseDown) { /* * If mouse is down, compute the camera rotation based on mouse * cursor location. */ currRotationAboutY = rotationAboutY + (mouseX - mouseDownX) * 0.01f; } else { currRotationAboutY = rotationAboutY; } /* Rotate camera about Y axis. */ tmpVector.set((float) sin(-currRotationAboutY) * 3.0f, 2.0f, (float) cos(-currRotationAboutY) * 3.0f); camera.setPerspective((float) Math.toRadians(60.0f), (float) width / height, 0.01f, 100.0f); if (reconstructViewSpace) { camera.invertPerspective(invCamera); } camera.lookAt(tmpVector, cameraLookAt, cameraUp); if (!reconstructViewSpace) { camera.invert(invCamera); } if (resetFramebuffer) { resizeFramebufferTexture(); resetFramebuffer = false; } } private void renderDepthOnly() { glEnable(GL_DEPTH_TEST); glUseProgram(depthOnlyProgram); /* Update matrices in shader */ glUniformMatrix4fv(viewProjMatrixUniform, false, camera.get(matrixBuffer)); /* Rasterize the boxes into the FBO */ glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); glClear(GL_DEPTH_BUFFER_BIT); glBindBuffer(GL_ARRAY_BUFFER, vboScene); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, false, 4 * (3 + 3), 0L); glDrawArrays(GL_TRIANGLES, 0, 6 * 6 * boxes.length / 2); glDisableVertexAttribArray(0); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); glUseProgram(0); } private void present() { glDisable(GL_DEPTH_TEST); glUseProgram(fullScreenQuadProgram); /* Set the inverse(proj * view) matrix in the shader */ glUniformMatrix4fv(inverseMatrixUniform, false, invCamera.get(matrixBuffer)); glBindBuffer(GL_ARRAY_BUFFER, fullScreenQuadVbo); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, false, 0, 0L); glBindTexture(GL_TEXTURE_2D, depthTexture); glDrawArrays(GL_TRIANGLES, 0, 6); glBindTexture(GL_TEXTURE_2D, 0); glDisableVertexAttribArray(0); glUseProgram(0); } private void loop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); glViewport(0, 0, width, height); update(); renderDepthOnly(); present(); glfwSwapBuffers(window); } } private void run() { try { init(); loop(); if (debugProc != null) debugProc.free(); errCallback.free(); keyCallback.free(); cpCallback.free(); fbCallback.free(); mbCallback.free(); glfwDestroyWindow(window); } catch (Throwable t) { t.printStackTrace(); } finally { glfwTerminate(); } } public static void main(String[] args) { new ReadDepthBufferDemo().run(); } }