Edit

kc3-lang/kc3/libkc3_window/sdl2/gl_camera.c

Branch :

  • libkc3_window/sdl2/gl_camera.c
  • /* kc3
     * Copyright 2022,2023,2024 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 330 core\n"
      "layout (location = 0) in vec3 iPos;\n"
      "layout (location = 1) in vec3 iNormal;\n"
      "layout (location = 2) in vec2 iTexCoord;\n"
      "out vec3 ioNormal;\n"
      "out vec3 ioPos;\n"
      "out vec2 ioTexCoord;\n"
      "uniform mat4 uProjectionMatrix;\n"
      "uniform mat4 uViewMatrix;\n"
      "uniform mat4 uModelMatrix;\n"
      "\n"
      "void main() {\n"
      "  gl_Position = vec4(uProjectionMatrix * uViewMatrix *\n"
      "                     uModelMatrix * vec4(iPos, 1.0));\n"
      "  ioNormal = vec3(uViewMatrix * uModelMatrix * vec4(iNormal, 1.0));\n"
      "  ioPos = (uViewMatrix * uModelMatrix * vec4(iPos, 1.0)).xyz;\n"
      "  ioTexCoord = iTexCoord;\n"
      "}\n";
    
    static const char * g_gl_camera_fragment_shader_src =
      "#version 330 core\n"
      "const float PI = 3.141592653;\n"
      "in vec3 ioNormal;\n"
      "in vec3 ioPos;\n"
      "in vec2 ioTexCoord;\n"
      "out vec4 oColor;\n"
      "uniform bool uEnableTex2D;\n"
      "uniform sampler2D uTex2D;\n"
      "uniform int uLightCount;\n"
      "uniform vec3 uLightPos[16];       // Light position in cam. coords.\n"
      "uniform vec3 uLightColor[16];\n"
      "uniform struct MaterialInfo {\n"
      "  float Rough;     // Roughness\n"
      "  bool Metal;      // Metallic (true) or dielectric (false)\n"
      "  vec4 Color;      // Diffuse color for dielectrics, f0 for metallic\n"
      "} uMaterial;\n"
      "float ggxDistribution (float nDotH) {\n"
      "  float alpha2 = uMaterial.Rough * uMaterial.Rough *\n"
      "                 uMaterial.Rough * uMaterial.Rough;\n"
      "  float d = (nDotH * nDotH) * (alpha2 - 1) + 1;\n"
      "  return alpha2 / (PI * d * d);\n"
      "}\n"
      "float geomSmith( float dotProd ) {\n"
      "  float k = (uMaterial.Rough + 1.0) * (uMaterial.Rough + 1.0) / 8.0;\n"
      "  float denom = dotProd * (1 - k) + k;\n"
      "  return 1.0 / denom;\n"
      "}\n"
      "vec4 schlickFresnel( float lDotH ) {\n"
      "  vec4 f0 = vec4(0.04, 0.04, 0.04, 1.0);\n"
      "  if( uMaterial.Metal ) {\n"
      "    f0 = uMaterial.Color;\n"
      "  }\n"
      "  return f0 + (1 - f0) * pow(1.0 - lDotH, 5);\n"
      "}\n"
      "vec4 microfacetModel (int lightIdx, vec3 pos, vec3 n, vec4 color) {\n"
      "  vec4 diffuseBrdf = vec4(0.0, 0.0, 0.0, 1.0);  // Metallic\n"
      "  if (! uMaterial.Metal) {\n"
      "    diffuseBrdf = color;\n"
      "  }\n"
      "  vec3 l = vec3(0.0);\n"
      "  vec3 lightI = uLightColor[lightIdx];\n"
      "  l = uLightPos[lightIdx] - pos;\n"
      "  float dist = length(l);\n"
      "  l = normalize(l);\n"
      "  lightI /= (dist * dist);\n"
      "  vec3 v = normalize(-pos);\n"
      "  vec3 h = normalize(v + l);\n"
      "  float nDotH = dot(n, h);\n"
      "  float lDotH = dot(l, h);\n"
      "  float nDotL = max(dot(n, l), 0.0);\n"
      "  float nDotV = dot(n, v);\n"
      "  vec4 specBrdf = 0.25 * ggxDistribution(nDotH) *\n"
      "    schlickFresnel(lDotH) * geomSmith(nDotL) * geomSmith(nDotV);\n"
      "  return (diffuseBrdf + PI * specBrdf) * vec4(lightI, 1.0) * nDotL;\n"
      "}\n"
      "void main() {\n"
      "  vec4 texColor = texture(uTex2D, ioTexCoord);\n"
      "  if (uEnableTex2D) {\n"
      "    oColor = texColor * uMaterial.Color;\n"
      "  }\n"
      "  else\n"
      "    oColor = uMaterial.Color;\n"
      "  vec4 sum = vec4(0);\n"
      "  vec3 n = normalize(ioNormal);\n"
      "  for (int i = 0; i < uLightCount; i++) {\n"
      "    sum += microfacetModel(i, ioPos, n, oColor);\n"
      "  }\n"
      "  sum = pow(sum, vec4(1.0 / 2.2));\n"
      "  oColor += sum;\n"
      "}\n";
    
    void gl_camera_bind_texture (s_gl_camera *camera, GLuint texture)
    {
      assert(camera);
      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_material (s_gl_camera *camera, const s_gl_material *material)
    {
      assert(camera);
      assert(material);
      assert(glGetError() == GL_NO_ERROR);
      glUniform1f(camera->gl_material_rough_loc, material->roughness);
      assert(glGetError() == GL_NO_ERROR);
      glUniform1i(camera->gl_material_metal_loc, material->metal);
      assert(glGetError() == GL_NO_ERROR);
      glUniform4fv(camera->gl_material_color_loc, 1, &material->color.r);
      assert(glGetError() == GL_NO_ERROR);
    }
    
    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 = 10.0f;
      camera->clip_z_near = 0.1f;
      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->light_count = 1;
      camera->light_pos[0] = (s_vec3) {-100, 0, 0};
      camera->light_color[0] = (s_rgb) {1, 1, 1};
      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: shader compilation failed: ");
        err_puts(info_log);
      }
      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: shader compilation failed: ");
        err_puts(info_log);
      }
      camera->gl_shader_program = glCreateProgram();
      glAttachShader(camera->gl_shader_program, vertex_shader);
      glAttachShader(camera->gl_shader_program, fragment_shader);
      glLinkProgram(camera->gl_shader_program);
      glDeleteShader(vertex_shader);
      glDeleteShader(fragment_shader);
      camera->gl_projection_matrix_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uProjectionMatrix");
      camera->gl_view_matrix_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uViewMatrix");
      camera->gl_model_matrix_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uModelMatrix");
      camera->gl_enable_tex2d_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uEnableTex2D");
      camera->gl_tex2d_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uTex2D");
      camera->gl_light_pos_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uLightPos");
      camera->gl_light_color_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uLightColor");
      camera->gl_material_rough_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uMaterial.Rough");
      camera->gl_material_metal_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uMaterial.Metal");
      camera->gl_material_color_loc =
        glGetUniformLocation(camera->gl_shader_program,
                             "uMaterial.Color");
      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)
    {
      s_mat4 matrix;
      const s_gl_material material = {0.1, false, {1, 1, 1, 1}};
      assert(camera);
      assert(glGetError() == GL_NO_ERROR);
      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 });
      mat4_init_identity(&camera->model_matrix);
      mat4_init_copy(&matrix, &camera->view_matrix);
      for (int i = 0; i < camera->light_count; i++)
        mat4_mult_vec3(&matrix, camera->light_pos + i,
                       camera->light_pos_cam + i);
      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);
      glUniform1i(camera->gl_enable_tex2d_loc, 0);
      assert(glGetError() == GL_NO_ERROR);
      glUniform1i(camera->gl_tex2d_loc, 0);
      assert(glGetError() == GL_NO_ERROR);
      glUniform3fv(camera->gl_light_pos_loc, camera->light_count,
                   &camera->light_pos_cam[0].x);
      assert(glGetError() == GL_NO_ERROR);
      glUniform3fv(camera->gl_light_color_loc, camera->light_count,
                   &camera->light_color[0].r);
      assert(glGetError() == GL_NO_ERROR);
      gl_camera_material(camera, &material);
      assert(glGetError() == GL_NO_ERROR);
      glDisable(GL_CULL_FACE);
      assert(glGetError() == GL_NO_ERROR);
      glDepthRange(camera->clip_z_near, camera->clip_z_far);
      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;
    }