package net.minestom.demo.largeframebuffers; import org.joml.Matrix4f; import org.lwjgl.BufferUtils; import org.lwjgl.opengl.GLUtil; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.ByteBuffer; import java.util.stream.Collectors; import static org.lwjgl.opengl.GL20.*; public final class OpenGLRendering { private static int vbo; private static int indexBuffer; private static final int VERTEX_SIZE = 5*4; // position + tex // array of vertices (order: X,Y,Z, Tex U, Tex V) private static float[] vertices = { // front face -1f, -1f, -1f, 0, 0, 1f, -1f, -1f, 1, 0, 1f, 1f, -1f, 1, 1, -1f, 1f, -1f, 0, 1, // back face -1f, -1f, 1f, 0, 1, 1f, -1f, 1f, 1, 1, 1f, 1f, 1f, 1, 0, -1f, 1f, 1f, 0, 0, }; private static int[] indices = { // south face 0,1,2, 2,3,0, // north face 4,5,6, 6,7,4, // west face 0,4,7, 7,3,0, // east face 1,5,6, 6,2,1, // top face 3, 2, 6, 6, 7, 3 }; private static int renderShader; private static Matrix4f projectionMatrix; private static Matrix4f viewMatrix; private static Matrix4f modelMatrix; private static int projectionUniform; private static int viewUniform; private static int modelUniform; private static int boxTexture; static void init() { GLUtil.setupDebugMessageCallback(); boxTexture = loadTexture("box"); vbo = glGenBuffers(); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW); indexBuffer = glGenBuffers(); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW); // prepare matrices and shader renderShader = glCreateProgram(); projectionMatrix = new Matrix4f().setPerspective((float) (Math.PI/4f), 1f, 0.001f, 100f); viewMatrix = new Matrix4f().setLookAt(5f, 5f, 5f, 0, 0, 0, 0, -1, 0); modelMatrix = new Matrix4f().identity(); int vertexShader = createShader("/shaders/vertex.glsl", GL_VERTEX_SHADER); int fragmentShader = createShader("/shaders/fragment.glsl", GL_FRAGMENT_SHADER); glAttachShader(renderShader, vertexShader); glAttachShader(renderShader, fragmentShader); glLinkProgram(renderShader); if(glGetProgrami(renderShader, GL_LINK_STATUS) == 0) { System.err.println("Link error: "+glGetProgramInfoLog(renderShader)); } projectionUniform = glGetUniformLocation(renderShader, "projection"); viewUniform = glGetUniformLocation(renderShader, "view"); modelUniform = glGetUniformLocation(renderShader, "model"); int boxUniform = glGetUniformLocation(renderShader, "box"); glUseProgram(renderShader); { uploadMatrix(projectionUniform, projectionMatrix); uploadMatrix(viewUniform, viewMatrix); glUniform1i(boxUniform, 0); // texture unit 0 } glUseProgram(0); } private static int loadTexture(String filename) { int tex = glGenTextures(); glBindTexture(GL_TEXTURE_2D, tex); BufferedImage image; try { image = ImageIO.read(OpenGLRendering.class.getResourceAsStream("/textures/"+filename+".png")); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("Missing image "+filename, e); } ByteBuffer pixels = BufferUtils.createByteBuffer(image.getWidth()*image.getHeight()*4); for (int y = 0; y < image.getHeight(); y++) { for (int x = 0; x < image.getWidth(); x++) { int rgb = image.getRGB(x, y); int alpha = (rgb >> 24) & 0xFF; int red = (rgb >> 16) & 0xFF; int green = (rgb >> 8) & 0xFF; int blue = rgb & 0xFF; pixels.put((byte) red); pixels.put((byte) green); pixels.put((byte) blue); pixels.put((byte) alpha); } } pixels.flip(); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.getWidth(), image.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // closest neighbor required here, as pixels can have very different rgb values, and interpolation will break palette lookup glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); return tex; } private static void uploadMatrix(int uniform, Matrix4f matrix) { float[] values = new float[4*4]; matrix.get(values); glUniformMatrix4fv(uniform, false, values); } private static int createShader(String filename, int type) { int shader = glCreateShader(type); try(BufferedReader reader = new BufferedReader(new InputStreamReader(OpenGLRendering.class.getResourceAsStream(filename)))) { String source = reader.lines().collect(Collectors.joining("\n")); glShaderSource(shader, source); glCompileShader(shader); } catch (IOException e) { e.printStackTrace(); } return shader; } private static long lastTime = System.currentTimeMillis(); private static int frame = 0; static void render() { if(frame % 100 == 0) { long time = System.currentTimeMillis(); long dt = time-lastTime; System.out.println(">> Render time for 100 frames: "+dt); System.out.println(">> Average time per frame: "+(dt/100.0)); System.out.println(">> Average FPS: "+(1000.0/(dt/100.0))); lastTime = time; } frame++; glClearColor(0f, 0f, 0f, 1f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); modelMatrix.rotateY((float) (Math.PI/60f)); glUseProgram(renderShader); { glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, boxTexture); uploadMatrix(modelUniform, modelMatrix); glBindBuffer(GL_ARRAY_BUFFER, vbo); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, false, VERTEX_SIZE, 0); // position glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, false, VERTEX_SIZE, 3*4); // color glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); glDrawElements(GL_TRIANGLES, indices.length, GL_UNSIGNED_INT, 0); } glUseProgram(0); } }