Java tutorial
/* * Copyright LWJGL. All rights reserved. * License terms: http://lwjgl.org/license.php */ package org.lwjgl.demo.opengl.textures; import org.lwjgl.BufferUtils; import org.lwjgl.PointerBuffer; import org.lwjgl.glfw.*; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL14; 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 org.lwjgl.opengl.ARBShaderObjects.*; import static org.lwjgl.opengl.ARBVertexShader.*; import static org.lwjgl.opengl.ARBFragmentShader.*; import static org.lwjgl.opengl.ARBSeamlessCubeMap.*; import static org.lwjgl.opengl.ARBTextureCubeMap.*; import static org.lwjgl.opengl.EXTTextureFilterAnisotropic.*; import static org.lwjgl.demo.opengl.util.DemoUtils.*; import static org.lwjgl.glfw.GLFW.*; import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.stb.STBImage.*; import static org.lwjgl.system.MemoryUtil.*; /** * Like {@link FullscreenCubemapDemo} but renders the black hole using a * regular polygon that encompasses only the circular area of influence of the * black hole. * * @author Kai Burjack */ public class BillboardCubemapDemo { long window; int width = 1024; int height = 768; int backgroundProgram; int background_invViewProjUniform; int background_cameraPositionUniform; int blackholeProgram; int blackhole_viewProjUniform; int blackhole_cameraPositionUniform; int blackhole_blackholePositionUniform; int blackhole_blackholeSizeUniform; int blackhole_debugUniform; Vector3f blackholePosition = new Vector3f(1.0f, 2.0f, 0.0f); float blackholeSize = 5.0f; boolean debug; ByteBuffer quadVertices; ByteBuffer circleVertices; /** * The number of vertices of the regular polygon. * Must be at least 3. */ int circleRefinement = 5; Vector3f tmp = new Vector3f(); Matrix4f projMatrix = new Matrix4f(); Matrix4f viewMatrix = new Matrix4f(); Matrix4f viewProjMatrix = new Matrix4f(); Matrix4f invViewProjMatrix = new Matrix4f(); FloatBuffer matrixBuffer = BufferUtils.createFloatBuffer(16); GLCapabilities caps; GLFWErrorCallback errCallback; GLFWKeyCallback keyCallback; GLFWFramebufferSizeCallback fbCallback; Callback debugProc; boolean isCrappyIntel; void init() throws IOException { glfwSetErrorCallback(errCallback = new GLFWErrorCallback() { 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 1.1 or higher."); delegate.invoke(error, description); } @Override public void free() { delegate.free(); } }); if (!glfwInit()) throw new IllegalStateException("Unable to initialize GLFW"); glfwDefaultWindowHints(); glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); window = glfwCreateWindow(width, height, "Cubemap texture sampling with projected quad", NULL, NULL); if (window == NULL) { throw new AssertionError("Failed to create the GLFW window"); } glfwSetFramebufferSizeCallback(window, fbCallback = new GLFWFramebufferSizeCallback() { @Override public void invoke(long window, int width, int height) { if (width > 0 && height > 0 && (BillboardCubemapDemo.this.width != width || BillboardCubemapDemo.this.height != height)) { BillboardCubemapDemo.this.width = width; BillboardCubemapDemo.this.height = height; } } }); System.out.println("Press 'D' to debug the blackhole rendering."); System.out.println("Press 'arrow up' to increase the polygon refinement."); System.out.println("Press 'arrow down ' to decrease the polygon refinement."); glfwSetKeyCallback(window, keyCallback = new GLFWKeyCallback() { @Override public void invoke(long window, int key, int scancode, int action, int mods) { if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) { glfwSetWindowShouldClose(window, true); } else if (key == GLFW_KEY_D && action == GLFW_RELEASE) { debug = !debug; } else if (key == GLFW_KEY_UP && (action == GLFW_PRESS || action == GLFW_REPEAT)) { circleRefinement++; createBillboardPolygon(); System.out.println("Circle refinement: " + circleRefinement); } else if (key == GLFW_KEY_DOWN && (action == GLFW_PRESS || action == GLFW_REPEAT)) { circleRefinement = Math.max(circleRefinement - 1, 3); createBillboardPolygon(); System.out.println("Circle refinement: " + circleRefinement); } } }); 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); caps = GL.createCapabilities(); String vendor = glGetString(GL_VENDOR); isCrappyIntel = vendor != null && vendor.toLowerCase().contains("intel"); if (!caps.GL_ARB_shader_objects) { throw new AssertionError("This demo requires the ARB_shader_objects extension."); } if (!caps.GL_ARB_vertex_shader) { throw new AssertionError("This demo requires the ARB_vertex_shader extension."); } if (!caps.GL_ARB_fragment_shader) { throw new AssertionError("This demo requires the ARB_fragment_shader extension."); } if (!caps.GL_ARB_texture_cube_map && !caps.OpenGL13) { throw new AssertionError("This demo requires the ARB_texture_cube_map extension or OpenGL 1.3."); } debugProc = GLUtil.setupDebugMessageCallback(); /* Create all needed GL resources */ createTexture(); createFullScreenQuad(); createBillboardPolygon(); createBackgroundProgram(); createBlackholeProgram(); /* Activate vertex array */ glEnableClientState(GL_VERTEX_ARRAY); } void createFullScreenQuad() { quadVertices = BufferUtils.createByteBuffer(4 * 2 * 6); FloatBuffer fv = quadVertices.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); } /** * This will construct a regular polygon with {@link #circleRefinement} vertices which will circumscribe the unit circle. * <p> * The draw mode will be {@link GL11#GL_TRIANGLE_FAN}. */ void createBillboardPolygon() { // Compute the scale factor to make the regular polygon circumscribe the unit circle/sphere. // Reference: http://www.mathopenref.com/polygonradius.html float scale = 1.0f / (float) Math.cos(Math.PI / circleRefinement); circleVertices = BufferUtils.createByteBuffer(4 * 2 * (circleRefinement + 2)); FloatBuffer fv = circleVertices.asFloatBuffer(); fv.put(0.0f).put(0.0f); for (int i = 0; i < circleRefinement + 1; i++) { float angle = (float) (2.0 * Math.PI * (i % circleRefinement) / circleRefinement); float x = (float) Math.cos(angle) * scale; float y = (float) Math.sin(angle) * scale; fv.put(x).put(y); } } static int createShader(String resource, int type) throws IOException { int shader = glCreateShaderObjectARB(type); ByteBuffer source = ioResourceToByteBuffer(resource, 1024); PointerBuffer strings = BufferUtils.createPointerBuffer(1); IntBuffer lengths = BufferUtils.createIntBuffer(1); strings.put(0, source); lengths.put(0, source.remaining()); glShaderSourceARB(shader, strings, lengths); glCompileShaderARB(shader); int compiled = glGetObjectParameteriARB(shader, GL_OBJECT_COMPILE_STATUS_ARB); String shaderLog = glGetInfoLogARB(shader); if (shaderLog.trim().length() > 0) { System.err.println(shaderLog); } if (compiled == 0) { throw new AssertionError("Could not compile shader"); } return shader; } void createBackgroundProgram() throws IOException { int program = glCreateProgramObjectARB(); int vshader = createShader("org/lwjgl/demo/opengl/textures/cubemapBack.vs", GL_VERTEX_SHADER_ARB); int fshader = createShader("org/lwjgl/demo/opengl/textures/cubemapBack.fs", GL_FRAGMENT_SHADER_ARB); glAttachObjectARB(program, vshader); glAttachObjectARB(program, fshader); glLinkProgramARB(program); int linked = glGetObjectParameteriARB(program, GL_OBJECT_LINK_STATUS_ARB); String programLog = glGetInfoLogARB(program); if (programLog.trim().length() > 0) { System.err.println(programLog); } if (linked == 0) { throw new AssertionError("Could not link program"); } glUseProgramObjectARB(program); int texLocation = glGetUniformLocationARB(program, "tex"); glUniform1iARB(texLocation, 0); background_invViewProjUniform = glGetUniformLocationARB(program, "invViewProj"); background_cameraPositionUniform = glGetUniformLocationARB(program, "cameraPosition"); glUseProgramObjectARB(0); this.backgroundProgram = program; } void createBlackholeProgram() throws IOException { int program = glCreateProgramObjectARB(); int vshader = createShader("org/lwjgl/demo/opengl/textures/cubemapBH.vs", GL_VERTEX_SHADER_ARB); int fshader = createShader("org/lwjgl/demo/opengl/textures/cubemapBH.fs", GL_FRAGMENT_SHADER_ARB); glAttachObjectARB(program, vshader); glAttachObjectARB(program, fshader); glLinkProgramARB(program); int linked = glGetObjectParameteriARB(program, GL_OBJECT_LINK_STATUS_ARB); String programLog = glGetInfoLogARB(program); if (programLog.trim().length() > 0) { System.err.println(programLog); } if (linked == 0) { throw new AssertionError("Could not link program"); } glUseProgramObjectARB(program); int texLocation = glGetUniformLocationARB(program, "tex"); glUniform1iARB(texLocation, 0); blackhole_viewProjUniform = glGetUniformLocationARB(program, "viewProj"); blackhole_cameraPositionUniform = glGetUniformLocationARB(program, "cameraPosition"); blackhole_blackholePositionUniform = glGetUniformLocationARB(program, "blackholePosition"); blackhole_blackholeSizeUniform = glGetUniformLocationARB(program, "blackholeSize"); blackhole_debugUniform = glGetUniformLocationARB(program, "debug"); glUseProgramObjectARB(0); this.blackholeProgram = program; } void createTexture() throws IOException { int tex = glGenTextures(); glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, tex); glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR); ByteBuffer imageBuffer; IntBuffer w = BufferUtils.createIntBuffer(1); IntBuffer h = BufferUtils.createIntBuffer(1); IntBuffer comp = BufferUtils.createIntBuffer(1); String[] names = { "right", "left", "top", "bottom", "front", "back" }; ByteBuffer image; if (caps.GL_EXT_texture_filter_anisotropic) { float maxAnisotropy = glGetFloat(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT); System.out.println("EXT_texture_filter_anisotropic available: Will use " + (int) maxAnisotropy + "x anisotropic filtering."); glTexParameterf(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy); } else { System.err .println("EXT_texture_filter_anisotropic unavailable: Distorted light might look too blurry."); } if (caps.OpenGL14) { System.out .println("OpenGL 1.4 available: Will use automatic mipmap generation via GL_GENERATE_MIPMAP."); glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL14.GL_GENERATE_MIPMAP, GL_TRUE); } else { System.err.println( "OpenGL 1.4 unavailable: Aliasing effects might be visible (texture looks too crisp)."); glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } for (int i = 0; i < 6; i++) { imageBuffer = ioResourceToByteBuffer("org/lwjgl/demo/space_" + names[i] + (i + 1) + ".jpg", 8 * 1024); if (!stbi_info_from_memory(imageBuffer, w, h, comp)) throw new IOException("Failed to read image information: " + stbi_failure_reason()); image = stbi_load_from_memory(imageBuffer, w, h, comp, 0); if (image == null) throw new IOException("Failed to load image: " + stbi_failure_reason()); glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + i, 0, GL_RGB8, w.get(0), h.get(0), 0, GL_RGB, GL_UNSIGNED_BYTE, image); stbi_image_free(image); } if (caps.OpenGL32 || caps.GL_ARB_seamless_cube_map) { System.out.println("ARB_seamless_cube_map available: Will use seamless cubemap sampling."); glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); } else { System.err.println("ARB_seamless_cube_map unavailable: Cubemap might have seams."); } } float rot = 0.0f; long lastTime = System.nanoTime(); void update() { projMatrix.setPerspective((float) Math.toRadians(60.0f), (float) width / height, 0.01f, 100.0f); viewMatrix.setLookAt(0.0f, 5.0f, 10.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f).rotateY(rot) .rotateX(rot * 0.23f).rotateZ(rot * -0.562f).originAffine(tmp); viewProjMatrix.set(projMatrix).mulPerspectiveAffine(viewMatrix).invert(invViewProjMatrix); /* Update the background shader */ glUseProgramObjectARB(backgroundProgram); glUniformMatrix4fvARB(background_invViewProjUniform, false, invViewProjMatrix.get(matrixBuffer)); glUniform3fARB(background_cameraPositionUniform, tmp.x, tmp.y, tmp.z); /* Update the black hole shader */ glUseProgramObjectARB(blackholeProgram); glUniformMatrix4fvARB(blackhole_viewProjUniform, false, viewProjMatrix.get(matrixBuffer)); glUniform3fARB(blackhole_cameraPositionUniform, tmp.x, tmp.y, tmp.z); glUniform3fARB(blackhole_blackholePositionUniform, blackholePosition.x, blackholePosition.y, blackholePosition.z); glUniform1fARB(blackhole_blackholeSizeUniform, blackholeSize); glUniform1fARB(blackhole_debugUniform, debug ? 1.0f : 0.0f); long thisTime = System.nanoTime(); float diff = (thisTime - lastTime) / 1E9f; lastTime = thisTime; rot += diff * 0.1f; } void render() { /* * Workaround an apparent bug in Intel's drivers: * Even though we don't need to clear the color buffer because every frame we render to the entire viewport, * we have to clear it, because otherwise if the window is being minimized and restored there is nothing rendered * anymore (just pure black). */ if (isCrappyIntel) glClear(GL_COLOR_BUFFER_BIT); /* Draw the cubemap background */ glUseProgramObjectARB(backgroundProgram); glVertexPointer(2, GL_FLOAT, 0, quadVertices); glDrawArrays(GL_TRIANGLES, 0, 6); /* Draw a single black hole */ glUseProgramObjectARB(blackholeProgram); glVertexPointer(2, GL_FLOAT, 0, circleVertices); glDrawArrays(GL_TRIANGLE_FAN, 0, circleRefinement + 2); } void loop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); glViewport(0, 0, width, height); update(); render(); glfwSwapBuffers(window); } } void run() { try { init(); loop(); if (debugProc != null) { debugProc.free(); } errCallback.free(); keyCallback.free(); fbCallback.free(); glfwDestroyWindow(window); } catch (Throwable t) { t.printStackTrace(); } finally { glfwTerminate(); } } public static void main(String[] args) { new BillboardCubemapDemo().run(); } }