Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

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).

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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 worng. 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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading