Edit

kc3-lang/kc3/gl/gl_camera.c

Branch :

  • gl/gl_camera.c
  • /* kc3
     * Copyright from 2022 to 2025 kmx.io <contact@kmx.io>
     *
     * Permission is hereby granted to use this software granted the above
     * copyright notice and this permission paragraph are included in all
     * copies and substantial portions of this software.
     *
     * THIS SOFTWARE IS PROVIDED "AS-IS" WITHOUT ANY GUARANTEE OF
     * PURPOSE AND PERFORMANCE. IN NO EVENT WHATSOEVER SHALL THE
     * AUTHOR BE CONSIDERED LIABLE FOR THE USE AND PERFORMANCE OF
     * THIS SOFTWARE.
     */
    #include <math.h>
    #include "../libkc3/kc3.h"
    #include "gl_camera.h"
    #include "mat4.h"
    
    static const char * g_gl_camera_vertex_shader_src =
      "#version 300 es\n"
      "precision highp float;\n"
      "in vec3 iPos;\n"
      "in vec3 iNormal;\n"
      "in vec2 iTexCoord;\n"
      "out vec3 ioFragNormal;\n"
      "out vec3 ioPos;\n"
      "out vec2 ioTexCoord;\n"
      "out mat4 ioViewMat;\n"
      "uniform mat4 uProjectionMatrix;\n"
      "uniform mat4 uViewMatrix;\n"
      "uniform mat4 uModelMatrix;\n"
      "void main() {\n"
      "  ioViewMat = uViewMatrix;\n"
      "  gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(iPos, 1.0);\n"
      "  ioFragNormal = vec3(mat3(uViewMatrix * uModelMatrix) * iNormal);\n"
      "  ioPos = vec3(uViewMatrix * uModelMatrix * vec4(iPos, 1.0));\n"
      "  ioTexCoord = iTexCoord;\n"
      "}\n";
    
    static const char * g_gl_camera_fragment_shader_src =
      "#version 300 es\n"
      "precision highp float;\n"
      "struct Light {\n"
      "  vec4 position;\n"
      "  vec4 intensity;\n"
      "};\n"
      "in vec3 ioFragNormal;\n"
      "in vec3 ioPos;\n"
      "in vec2 ioTexCoord;\n"
      "in mat4 ioViewMat;\n"
      "out vec4 oFragColor;\n"
      "uniform vec4 uColor;\n"
      "uniform bool uEnableTex2D;\n"
      "uniform sampler2D uTex2D;\n"
      "uniform Light uLight[128];\n"
      "uniform int uLightCount;\n"
      "void main() {\n"
      "  vec3 lightColor = vec3(0, 0, 0);\n"
      "  vec3 normal = normalize(ioFragNormal);\n"
      "  vec4 texColor = texture(uTex2D, ioTexCoord);\n"
      "  for (int i = 0; i < uLightCount; i++) {\n"
      "    vec3 lightDir;\n"
      "    if (uLight[i].position.w == 0.0)\n"
      "      lightDir = normalize(vec3(ioViewMat * vec4(uLight[i].position.xyz, 1)));\n"  // directional
      "    else\n"
      "      lightDir = normalize(vec3(ioViewMat * vec4(uLight[i].position.xyz, 1)) - ioPos);\n"  // point
      "    float diffuse = max(dot(normal, lightDir), 0.0);\n"
      "    vec3 viewDir = normalize(-ioPos);\n"
      "    vec3 halfDir = normalize(lightDir + viewDir);\n"
      "    float specular = 0.0;\n"
      "    if (diffuse > 0.0) {\n"
      "      specular = pow(max(dot(normal, halfDir), 0.0), 16.0);\n"
      "    }\n"
      "    lightColor += (diffuse + specular) * uLight[i].intensity.rgb;\n"
      "  }\n"
      "  oFragColor = uColor;\n"
      "  if (uLightCount > 0) {\n"
      "    oFragColor.r *= lightColor.r;\n"
      "    oFragColor.g *= lightColor.g;\n"
      "    oFragColor.b *= lightColor.b;\n"
      "  }\n"
      "  if (uEnableTex2D)\n"
      "    oFragColor *= texColor;\n"
      "}\n";
    
    void gl_camera_bind_texture (s_gl_camera *camera, GLuint texture)
    {
      assert(camera);
      assert(glGetError() == GL_NO_ERROR);
      glUseProgram(camera->gl_shader_program);
      assert(glGetError() == GL_NO_ERROR);
      if (! texture) {
        glActiveTexture(GL_TEXTURE0);
        assert(glGetError() == GL_NO_ERROR);
        glBindTexture(GL_TEXTURE_2D, 0);
        assert(glGetError() == GL_NO_ERROR);
        glUniform1i(camera->gl_enable_tex2d_loc, 0);
        assert(glGetError() == GL_NO_ERROR);
        glUniform1i(camera->gl_tex2d_loc, 0);
        assert(glGetError() == GL_NO_ERROR);
        return;
      }
      glActiveTexture(GL_TEXTURE0);
      assert(glGetError() == GL_NO_ERROR);
      glBindTexture(GL_TEXTURE_2D, texture);
      assert(glGetError() == GL_NO_ERROR);
      glUniform1i(camera->gl_enable_tex2d_loc, 1);
      assert(glGetError() == GL_NO_ERROR);
      glUniform1i(camera->gl_tex2d_loc, 0);
      assert(glGetError() == GL_NO_ERROR);
    }
    
    void gl_camera_clean (s_gl_camera *camera)
    {
      assert(camera);
      glDeleteProgram(camera->gl_shader_program);
    }
    
    void gl_camera_delete (s_gl_camera *camera)
    {
      gl_camera_clean(camera);
      free(camera);
    }
    
    s_gl_camera * gl_camera_init (s_gl_camera *camera, uw w, uw h)
    {
      GLuint fragment_shader;
      GLint success;
      GLuint vertex_shader;
      assert(camera);
      gl_camera_set_aspect_ratio(camera, w, h);
      camera->clip_z_far = 30.0f;
      camera->clip_z_near = 1.0f;
      camera->fov_y = 90.0f;
      camera->position.x = 0.0f;
      camera->position.y = 0.0f;
      camera->position.z = -10.0f;
      camera->rotation.x = M_PI / 2.0;
      camera->rotation.y = 0.0f;
      camera->rotation.z = 0.0f;
      camera->ambiant_light_color = (s_rgb) {1.0f, 1.0f, 1.0f};
      camera->light_count = 1;
      camera->light_pos[0] = (s_vec4) {-40.0f, 0.0f, 0.0f, 1.0f};
      camera->light_color[0] = (s_rgb) {1.0f, 1.0f, 1.0f};
      vertex_shader = glCreateShader(GL_VERTEX_SHADER);
      glShaderSource(vertex_shader, 1, &g_gl_camera_vertex_shader_src,
                     NULL);
      glCompileShader(vertex_shader);
      glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
      if (! success) {
        char info_log[512];
        glGetShaderInfoLog(vertex_shader, sizeof(info_log), NULL, info_log);
        err_write_1("gl_camera_init: vertex shader compilation failed: ");
        err_puts(info_log);
        return NULL;
      }
      fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
      glShaderSource(fragment_shader, 1, &g_gl_camera_fragment_shader_src,
                     NULL);
      glCompileShader(fragment_shader);
      glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
      if (! success) {
        char info_log[512];
        glGetShaderInfoLog(fragment_shader, sizeof(info_log), NULL,
                           info_log);
        err_write_1("gl_camera_init: fragment shader compilation failed: ");
        err_puts(info_log);
        return NULL;
      }
      camera->gl_shader_program = glCreateProgram();
      glAttachShader(camera->gl_shader_program, vertex_shader);
      glAttachShader(camera->gl_shader_program, fragment_shader);
      glBindAttribLocation(camera->gl_shader_program, 0, "iPos");
      glBindAttribLocation(camera->gl_shader_program, 1, "iNormal");
      glBindAttribLocation(camera->gl_shader_program, 2, "iTexCoord");
      glLinkProgram(camera->gl_shader_program);
      glGetProgramiv(camera->gl_shader_program, GL_LINK_STATUS, &success);
      if (! success) {
        char info_log[512];
        glGetProgramInfoLog(camera->gl_shader_program, sizeof(info_log),
                            NULL, info_log);
        err_write_1("gl_camera_init: program linking failed: ");
        err_puts(info_log);
        return NULL;
      }
      assert(glGetError() == GL_NO_ERROR);
      glDeleteShader(vertex_shader);
      glDeleteShader(fragment_shader);
      assert(glGetError() == GL_NO_ERROR);
      camera->gl_projection_matrix_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uProjectionMatrix");
      assert(glGetError() == GL_NO_ERROR);
      if (camera->gl_projection_matrix_loc == (GLuint)-1)
        err_puts("gl_camera_init: uProjectionMatrix location is -1!");
      camera->gl_view_matrix_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uViewMatrix");
      assert(glGetError() == GL_NO_ERROR);
      if (camera->gl_view_matrix_loc == (GLuint)-1)
        err_puts("gl_camera_init: uViewMatrix location is -1!");
      camera->gl_model_matrix_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uModelMatrix");
      assert(glGetError() == GL_NO_ERROR);
      if (camera->gl_model_matrix_loc == (GLuint)-1)
        err_puts("gl_camera_init: uModelMatrix location is -1!");
      camera->gl_color_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uColor");
      assert(glGetError() == GL_NO_ERROR);
      camera->gl_enable_tex2d_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uEnableTex2D");
      assert(glGetError() == GL_NO_ERROR);
      camera->gl_tex2d_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uTex2D");
      assert(glGetError() == GL_NO_ERROR);
      camera->gl_light_count_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uLightCount");
      assert(glGetError() == GL_NO_ERROR);
      return camera;
    }
    
    s_gl_camera * gl_camera_new (uw w, uw h)
    {
      s_gl_camera *camera;
      camera = calloc(1, sizeof(s_gl_camera));
      if (! camera) {
        err_puts("gl_camera_new: failed to allocate memory");
        return NULL;
      }
      if (! gl_camera_init(camera, w, h)) {
        free(camera);
        return NULL;
      }
      return camera;
    }
    
    void gl_camera_render (s_gl_camera *camera)
    {
      assert(camera);
      assert(glGetError() == GL_NO_ERROR);
      if (camera->gl_shader_program == 0) {
        err_puts("gl_camera_render: shader program is 0!");
        return;
      }
      mat4_init_identity(&camera->projection_matrix);
      mat4_perspective(&camera->projection_matrix, camera->fov_y,
                       camera->aspect_ratio, camera->clip_z_near,
                       camera->clip_z_far);
      mat4_init_identity(&camera->view_matrix);
      mat4_translate(&camera->view_matrix, camera->position.x,
                     camera->position.y, camera->position.z);
      mat4_rotate_axis(&camera->view_matrix, camera->rotation.x,
                       &(s_vec3) { 1.0f, 0.0f, 0.0f });
      mat4_rotate_axis(&camera->view_matrix, camera->rotation.y,
                       &(s_vec3) { 0.0f, 1.0f, 0.0f });
      mat4_rotate_axis(&camera->view_matrix, camera->rotation.z,
                       &(s_vec3) { 0.0f, 0.0f, 1.0f });
      glUseProgram(camera->gl_shader_program);
      assert(glGetError() == GL_NO_ERROR);
      glUniformMatrix4fv(camera->gl_projection_matrix_loc, 1, GL_FALSE,
                         &camera->projection_matrix.xx);
      assert(glGetError() == GL_NO_ERROR);
      glUniformMatrix4fv(camera->gl_view_matrix_loc, 1, GL_FALSE,
                         &camera->view_matrix.xx);
      assert(glGetError() == GL_NO_ERROR);
      glUniformMatrix4fv(camera->gl_model_matrix_loc, 1, GL_FALSE,
                         &camera->model_matrix.xx);
      assert(glGetError() == GL_NO_ERROR);
      glUniform4f(camera->gl_color_loc, 1.0f, 1.0f, 1.0f, 1.0f);
      assert(glGetError() == GL_NO_ERROR);
      glUniform1i(camera->gl_enable_tex2d_loc, 0);
      assert(glGetError() == GL_NO_ERROR);
      glUniform1i(camera->gl_tex2d_loc, 0);
      assert(glGetError() == GL_NO_ERROR);
      glUniform1i(camera->gl_light_count_loc, camera->light_count);
      assert(glGetError() == GL_NO_ERROR);
      for (int i = 0; i < camera->light_count; i++) {
        char uniform_name[64];
        GLint loc;
        snprintf(uniform_name, sizeof(uniform_name),
                 "uLight[%d].position", i);
        loc = glGetUniformLocation(camera->gl_shader_program,
                                   uniform_name);
        if (loc != -1)
          glUniform4fv(loc, 1, &camera->light_pos[i].x);
        assert(glGetError() == GL_NO_ERROR);
        snprintf(uniform_name, sizeof(uniform_name),
                 "uLight[%d].intensity", i);
        loc = glGetUniformLocation(camera->gl_shader_program,
                                   uniform_name);
        if (loc != -1)
          glUniform4f(loc, camera->light_color[i].r,
                      camera->light_color[i].g,
                      camera->light_color[i].b, 1.0f);
        assert(glGetError() == GL_NO_ERROR);
      }
      glDisable(GL_CULL_FACE);
      assert(glGetError() == GL_NO_ERROR);
      glDepthRangef(0.0f, 1.0f);
      assert(glGetError() == GL_NO_ERROR);
    }
    
    void gl_camera_render_end (s_gl_camera *camera)
    {
      assert(camera);
      (void) camera;
      glUseProgram(0);
    }
    
    s_gl_camera * gl_camera_set_aspect_ratio (s_gl_camera *camera, uw w,
                                              uw h)
    {
      assert(camera);
      camera->aspect_ratio = (f64) (w ? w : 1) / (h ? h : 1);
      return camera;
    }