Search code examples
copenglglslglfw

Vertex shader: Multiplying by projection matrix give me blank screen


I'm trying to do some 3D graphics using OpenGL, with cglm for math.
I was following a guide that lead me to multiplying my vertex position by 3 matrices: model, view and projection.
Since the tutorial wasn't in the same language as me, I couldn't follow it one-by-one, so I did the projection matrix in the way I could find, with cglm

Multiplying the vertex by those matrices gave me a black screen (object not rendered), and I noticed that it just happened because of the projection matrix.

I checked my matrix values and they matched with one projection matrix I've asked ChatGPT, but didn't work, so I done one by myself instead of using the cglm one, and it worked (with some distortion, but worked).

I want to know why the cglm one failed, because I'd rather using theirs.

Here is a important code snippet (C):

  mat4 model, view, proj;
  glm_mat4_identity(model);
  glm_mat4_identity(proj);

  glm_rotate(model, PI4, (vec3) { 1, 0, 0 });


  // glm_perspective(cam.fov, (f32) cam.width / cam.height, cam.near, cam.far, proj);
  proj[0][0] = PI4;
  proj[1][1] = PI4;
  proj[2][2] = (cam.far + cam.near) / (cam.near - cam.far);
  proj[2][3] = -1;
  proj[3][2] = 2 * cam.far * cam.near / (cam.near - cam.far);

  glUniformMatrix4fv(UNI(shader_program, "MODEL"), 1, GL_FALSE, (const f32*) { model[0] });
  glUniformMatrix4fv(UNI(shader_program, "PROJ"),  1, GL_FALSE, (const f32*) { proj[0] });

  while (!glfwWindowShouldClose(canvas.window)) {
    glm_mat4_identity(view);
    glm_translate(view, (vec3) { -cam.pos[0], -cam.pos[1], -cam.pos[2] });
    glUniformMatrix4fv(UNI(shader_program, "VIEW"),  1, GL_FALSE, (const f32*) { view[0] });

    canvas_clear((RGBA) { 0, 0, 0, 1 });
    glDrawArrays(GL_TRIANGLES, 0, 3);

    glfwSwapBuffers(canvas.window); 
    glfwPollEvents();    
    handle_inputs(canvas.window);    
  }

The full code (C):

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <cglm/cglm.h>
#include <stdio.h>
#include "shader.h"
#include "misc.h"

#define ASSERT(x, str)    if (!(x)) {printf(str); exit(1);}
#define UNI(shader, name) (glGetUniformLocation(shader, name))
#define PI  3.14159
#define PI2 PI / 2
#define PI4 PI / 4
#define TAU PI * 2

#define VERTEX_SHADER   "shd/vertex.glsl"
#define FRAGMENT_SHADER "shd/fragment.glsl"
#define TEXTURE_0 "img/bills.ppm"
#define TEXTURE_1 "img/ilu.ppm"
#define WIDTH  800
#define HEIGHT 800
#define MOVEMENT_SPEED 0.01

typedef uint8_t  u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef int8_t   i8;
typedef int16_t  i16;
typedef int32_t  i32;
typedef float    f32;
typedef double   f64;

// --- Type

typedef struct { f32 R, G, B, A; } RGBA;

typedef struct {
  i16 width, height;
  f32 fov, near, far;

  f32 pos[3];
  f32 ang[2];
} Camera;

typedef struct {
  GLFWwindow* window;

  i16 width, height;
} Canvas;

u32 canvas_create_texture(GLenum unit, char path[], u32 shader, char uniform_name[], i32 uniform_value);
void canvas_config_texture(GLenum wrap_s, GLenum wrap_t, GLenum min_filter, GLenum mag_filter);
void key_callback(GLFWwindow* window, i32 key, i32 scancode, i32 action, i32 mods);
void handle_inputs(GLFWwindow* window);
void canvas_init(Canvas* canvas);
void canvas_clear(RGBA color);
u32 canvas_create_VBO();
u32 canvas_create_VAO();
u32 canvas_create_EBO();

// --- Setup

u8 wireframe_mode;

Canvas canvas = { NULL, WIDTH, HEIGHT };
Camera cam = { WIDTH, HEIGHT, PI4, 0.1, 100, { 0, 0, 0 }, { 0, 0 } };

f32 vertices[] = {
  +0.00, +0.45, 0,  0.5, 0.0,
  -0.50, -0.50, 0,  1.0, 1.0,
  +0.50, -0.50, 0,  0.0, 1.0,
};

// --- Main

i8 main() {
  canvas_init(&canvas);
  glfwSetKeyCallback(canvas.window, key_callback);

  u32 VBO = canvas_create_VBO();
  u32 VAO = canvas_create_VAO();

  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(f32), (void*) 0);
  glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(f32), (void*) (sizeof(f32) * 3));
  glEnableVertexAttribArray(0);
  glEnableVertexAttribArray(1);

  glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

  u32 shader_program = shader_create_program(VERTEX_SHADER, FRAGMENT_SHADER);

  canvas_config_texture(GL_MIRRORED_REPEAT, GL_MIRRORED_REPEAT, GL_NEAREST, GL_NEAREST);
  canvas_create_texture(GL_TEXTURE0, TEXTURE_0, shader_program, "TEXTURE_0", 0);
  canvas_create_texture(GL_TEXTURE1, TEXTURE_1, shader_program, "TEXTURE_1", 1);

  mat4 model, view, proj;
  glm_mat4_identity(model);
  glm_mat4_identity(proj);

  glm_rotate(model, PI4, (vec3) { 1, 0, 0 });

  // glm_perspective(cam.fov, (f32) cam.width / cam.height, cam.near, cam.far, proj);
  proj[0][0] = PI4;
  proj[1][1] = PI4;
  proj[2][2] = (cam.far + cam.near) / (cam.near - cam.far);
  proj[2][3] = -1;
  proj[3][2] = 2 * cam.far * cam.near / (cam.near - cam.far);

  glUniformMatrix4fv(UNI(shader_program, "MODEL"), 1, GL_FALSE, (const f32*) { model[0] });
  glUniformMatrix4fv(UNI(shader_program, "PROJ"),  1, GL_FALSE, (const f32*) { proj[0] });

  while (!glfwWindowShouldClose(canvas.window)) {
    glm_mat4_identity(view);
    glm_translate(view, (vec3) { -cam.pos[0], -cam.pos[1], -cam.pos[2] });
    glUniformMatrix4fv(UNI(shader_program, "VIEW"),  1, GL_FALSE, (const f32*) { view[0] });

    canvas_clear((RGBA) { 0, 0, 0, 1 });
    glDrawArrays(GL_TRIANGLES, 0, 3);

    glfwSwapBuffers(canvas.window); 
    glfwPollEvents();    
    handle_inputs(canvas.window);    
  }

  glfwTerminate();
  return 0;
}

// --- Function

void canvas_init(Canvas* canvas) {
  glfwInit();
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

  canvas->window = glfwCreateWindow(canvas->width, canvas->height, "Coordinate System", NULL, NULL);
  ASSERT(canvas->window, "Failed creating a window");
  glfwMakeContextCurrent(canvas->window);

  ASSERT(gladLoadGLLoader((GLADloadproc) glfwGetProcAddress), "Failed loading glad");
  glViewport(0, 0, WIDTH, HEIGHT);
}

void canvas_clear(RGBA color) {
  glClearColor(color.R, color.G, color.B, color.A);
  glClear(GL_COLOR_BUFFER_BIT);
}

void canvas_config_texture(GLenum wrap_s, GLenum wrap_t, GLenum min_filter, GLenum mag_filter) {
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
}

u32 canvas_create_texture(GLenum unit, char path[], u32 shader, char uniform_name[], i32 uniform_value) {
  u32 texture_ID;
  glGenTextures(1, &texture_ID);
  glActiveTexture(unit);
  glBindTexture(GL_TEXTURE_2D, texture_ID);

  u16 width, height;
  get_image_size(path, &width, &height);
  f32* image_buffer = malloc(sizeof(f32) * width * height * 3);
  load_image(path, image_buffer, width * height);

  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_FLOAT, image_buffer);
  glGenerateMipmap(GL_TEXTURE_2D);
  glUniform1i(UNI(shader, uniform_name), uniform_value);

  free(image_buffer);
  return texture_ID;
}

void key_callback(GLFWwindow* window, i32 key, i32 scancode, i32 action, i32 mods) {
  if (action != GLFW_PRESS) return;

  switch (key) {
    case GLFW_KEY_ESCAPE:
      glfwSetWindowShouldClose(canvas.window, 1); 
      break;

    case GLFW_KEY_M:
      if (wireframe_mode) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      else glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      wireframe_mode = 1 - wireframe_mode;
  }
}

void handle_inputs(GLFWwindow* window) {
  if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
    cam.pos[2] += MOVEMENT_SPEED;
  if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
    cam.pos[2] -= MOVEMENT_SPEED;
  if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
    cam.pos[0] -= MOVEMENT_SPEED;
  if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
    cam.pos[0] += MOVEMENT_SPEED;
  if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS)
    cam.pos[1] += MOVEMENT_SPEED;
  if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS)
    cam.pos[1] -= MOVEMENT_SPEED;
}

u32 canvas_create_VBO() {
  u32 VBO;
  glGenBuffers(1, &VBO);
  glBindBuffer(GL_ARRAY_BUFFER, VBO);
  return VBO;
}

u32 canvas_create_VAO() {
  u32 VAO;
  glGenVertexArrays(1, &VAO);
  glBindVertexArray(VAO);
  return VAO;
}

u32 canvas_create_EBO() {
  u32 EBO;
  glGenBuffers(1, &EBO);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
  return EBO;
}

My vertex shader (GLSL):

# version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTex;
uniform mat4 MODEL;
uniform mat4 VIEW;
uniform mat4 PROJ;
out vec2 _tex;

void main() {
  _tex = aTex;

  gl_Position = PROJ * VIEW * MODEL * vec4(aPos, 1);
}

I also printed the matrices:

Mine
0.785398 0.000000 0.000000 0.000000 
0.000000 0.785398 0.000000 0.000000 
0.000000 0.000000 -1.002002 -1.000000 
0.000000 0.000000 -0.200200 1.000000 

CGLM
2.414216 0.000000 0.000000 0.000000 
0.000000 2.414216 0.000000 0.000000 
0.000000 0.000000 -1.002002 -1.000000 
0.000000 0.000000 -0.200200 0.000000 

I also noticed now, that mine was wrong, because it wasn't supposed to have that last value as 1, it was happening because I was doing an identity matrix before, when I removed that 1, it stopped working


Solution

  • The calculation for the perspective projection matrix is wrong. The symmetrically perspective projection matrix is:

    column 0:    1/(ta*a)  0      0              0
    column 1:    0         1/ta   0              0
    column 2:    0         0     -(f+n)/(f-n)   -1
    column 3:    0         0     -2*f*n/(f-n)    0
    

    where a = w / h and ta = tan(fov_y / 2)

    For your code that means:

    float aspect = (float)window_width / (float)wondow_height; 
    proj[0][0] = 1 / (tan(PI4 / 2) * aspect);
    proj[1][1] = 1 / tan(PI4 / 2);
    proj[2][2] = (cam.far + cam.near) / (cam.near - cam.far);
    proj[2][3] = -1;
    proj[3][2] = 2 * cam.far * cam.near / (cam.near - cam.far);
    proj[3][3] = 0;
    

    Note, glm_mat4_identity initializes a matrix with the identity matrix. So proj[3][3] needs to be set 0.