Search code examples
javaopenglshaderlwjglcoordinate-transformation

Unable to render simple vertex array


I am in the process of migrating a 2D game from legacy -> modern OpenGL. For me, this means using shaders and vertex arrays instead of glProjection, glOrtho and glBegin / glEnd.

I've stripped the relevant code down to a very basic example. The camera is at (0, 0, -1) looking at (0, 0, 0), and I am trying to draw a triangle with points:

(0, 0, 0)
      X
      | '
      |   '
      |     '
      X-------X
(0, 1, 0)   (1, 1, 0)

Even with a very simple shader that renders all fragments white, I do not see this triangle, just the flashing background colour.

Code

package client.render.opengl;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;

import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;

import client.res.gfx.ScreenShader;

public class RenderingTest {

    private static final int NUM_VERTICES = 3;

    private static long window;
    private static int shaderProgram;
    private static int vao;
    private static int uniformLoc;
    private static Matrix4f viewProjMatrix = new Matrix4f();
    private static FloatBuffer fb16 = BufferUtils.createFloatBuffer(16);

    public static void main(String[] args) throws IOException {

        init();

        while (!GLFW.glfwWindowShouldClose(window)) {
            GLFW.glfwPollEvents();
            render();
            GLFW.glfwSwapBuffers(window);
        }
    }

    private static void init() throws IOException {

        // Setup an error callback
        GLFW.glfwSetErrorCallback(
                GLFWErrorCallback.createPrint(System.err)
        );

        // Initialise GLFW
        if (!GLFW.glfwInit()) {
            throw new IllegalStateException("Unable to initialize GLFW");
        }

        // Create window
        window = GLFW.glfwCreateWindow(800, 600, "test", 0, 0);
        GLFW.glfwMakeContextCurrent(window);
        GL.createCapabilities();
        GLFW.glfwShowWindow(window);

        // Load shader
        // Assume this code is good for now, it's too long to post here
        ScreenShader.initialise();
        shaderProgram = ScreenShader.shader.getProgram();
        uniformLoc = ScreenShader.shader.getUniformLoc(ScreenShader.UNIFORM_VIEW_PROJ_MATRIX);

        // Define our vertices
        ByteBuffer vertexBuffer = ByteBuffer.allocateDirect(NUM_VERTICES * 2 * Float.BYTES);
        vertexBuffer.putFloat(0).putFloat(0); // Vertex 1
        vertexBuffer.putFloat(0).putFloat(1); // Vertex 2
        vertexBuffer.putFloat(1).putFloat(1); // Vertex 3
        vertexBuffer.flip();

        // Create VAO
        vao = GL30.glGenVertexArrays();
        GL30.glBindVertexArray(vao);

        // Create VBO and fill it with vertex positions
        int vboIdPositions = GL15.glGenBuffers();
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboIdPositions);
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertexBuffer, GL15.GL_STATIC_DRAW);
        GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 0, 0);
        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); // Deselect

        // Unbind the VAO
        GL30.glBindVertexArray(0);
    }

    public static void render() {

        // Render a random background to prove the loop is working
        GL11.glClearColor(
                (float) (Math.random() * 0.2f),
                (float) (Math.random() * 0.2f),
                (float) (Math.random() * 0.2f),
                1.0f);
        GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);

        // Use shader
        GL20.glUseProgram(shaderProgram);

        // Get camera position
        float cameraX = 0;
        float cameraY = 0;

        // Set view and projection
        viewProjMatrix
                .setOrtho(
                        0,
                        800,
                        600,
                        0,
                        0.01f,
                        10
                ).lookAt(
                        cameraX, cameraY, -1,    // Camera position
                        cameraX, cameraY, 0,     // Camera "look" vector
                        0, 1, 0                  // Camera "up" vector
                );

        // Pass this matrix to our shader
        GL20.glUniformMatrix4fv(uniformLoc, false, viewProjMatrix.get(fb16));

        // Bind our vertex array
        GL30.glBindVertexArray(vao);

        // Enable vertex attributes
        GL20.glEnableVertexAttribArray(0);

        // Draw the vertices
        GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, NUM_VERTICES);

        // Disable vertex attributes
        GL20.glDisableVertexAttribArray(0);

        // Unbind vertex array
        GL30.glBindVertexArray(0);

        // Release shader
        GL20.glUseProgram(0);
    }

}

Vertex Shader

#version 330

uniform mat4 view_proj_matrix;

layout(location = 0) in vec2 in_vertex;

void main(void) {
    gl_Position = view_proj_matrix * vec4(in_vertex, 0, 1);
}

Fragment Shader

#version 330

out vec4 frag_colour;

void main(void) {
    frag_colour = vec4(1, 1, 1, 1);
}

I don't know how to progress since I can't even get this toy example to work. Can anyone see what I'm doing wrong, or point me to a working example that uses JOML with LWJGL2 and shaders?


Solution

  • With your view and projection matrix, the triangle has a size of 1 pixel and is out of the screen at the left.


    If your triangle has the coordinates (0, 0, 0), (0, 1, 0), (1, 1, 0), your window has a size of (800, 600) and you set up an orthographic projection linke this:

    viewProjMatrix.setOrtho(0, 800, 600, 0, 0.01f, 10)
    

    the the triangle has a size of 1 pixel on the screen.

    You have to change the orthographic projection:

    float aspect = 800.0f/600.0f;
    viewProjMatrix.setOrtho(-aspect, aspect, 1, -1, 0.01f, 10)
    

    Note, the projection matrix describes the mapping from 3D points of a scene, to 2D points of the viewport. At Orthographic Projection the coordinates in the view space are linearly mapped to clip space coordinates.


    Made it 100x bigger, still no sign of it!

    If you just scale the triangle triangle to (0, 0, 0), (0, 100, 0), (100, 100, 0), you won't see the triangle either, because of the view matrix:

    lookAt(0, 0, -1, 0, 0, 0, 0, 1, 0 );
    

    With this view matrix the x-axis is inverted in a Right-handed coordinate system. The view coordinates system describes the direction and position from which the scene is looked at. The view matrix transforms from the world space to the view (eye) space. In view space, the X-axis points to the left, the Y-axis up and the Z-axis out of the view (Note in a right hand system the Z-Axis is the cross product of the X-Axis and the Y-Axis).

    viewspace

    Your view is defined as follows

    position = (0, 0, -1)
    center   = (0, 0, 0)
    up       = (0, 1, 0)
    

    the y-axis and z-axis are so:

    y-axis = up = (0, 1, 0)
    z-axis = position - center = (0, 0, -1)
    

    the x-axis is the cross product of the y-axis and the z-axis:

    x-axis = cross(y-axis, z-axis) = (-1, 0, 0)
    

    This means, that you look at the back of the triangle. This causes that the triangle is "flipped" out of the viewport. It is drawn at the left out of the screen, so you can't see it:

            (0, 0)
                  X---------------------------x
                / |                           |
              /   |                           |
            X-----X                           |
    (-100, 100)   |         viewport          |
                  |                           |
                  |                           |
                  |                           |
                  x---------------------------x 
                                                (800, 600)
    

    Invert the line of sight to solve the issue:

    lookAt(0, 0, 1, 0, 0, 0, 0, 1, 0 );