/* Uniform Buffer Objects CC BY-SA Edouard.Thiel@univ-amu.fr - 08/02/2025 */ #include #include #include #include #include #include // Pour générer glad.h : https://glad.dav1d.de/ // C/C++, gl 4.5, OpenGL, Core, extensions: add all, local files #include "glad.h" // Pour définir des matrices : module vmath.h du OpenGL Red Book // https://github.com/openglredbook/examples/blob/master/include/vmath.h // avec bugfix: erreur de signe dans Ortho(). // provoque un warning avec -O2, supprimé avec -fno-strict-aliasing // + ajout de calculs dans vmath-et.h #include "vmath-et.h" #include // Pour charger des images avec le module stb_image #include "stb_image.h" //----------------------------- L O C A T I O N S ----------------------------- // Vertex attribute location imposées enum VA_Locations{ VPOS_LOC = 0, VCOL_LOC = 1, VNOR_LOC = 2, VTEX_LOC = 3, LAST_LOC }; const char* get_vertex_attribute_name (VA_Locations loc) { switch (loc) { case VPOS_LOC : return "vPos"; case VCOL_LOC : return "vCol"; case VNOR_LOC : return "vNor"; case VTEX_LOC : return "vTex"; default : return ""; } } //----------------------------------- U B O ----------------------------------- // Type avec alignement GLSL std140 aligné sur 16 octets struct UBO_Uniforms { alignas(16) vmath::mat4 matProj; alignas(16) vmath::mat4 matCam; alignas(16) vmath::vec4 mousePos; alignas(16) GLfloat time; }; const GLint UBO_BINDING_POINT = 0; //------------------------------ T R I A N G L E S ---------------------------- class Triangles { GLuint m_VAO_id, m_VBO_id; public: Triangles() { // Données GLfloat positions[] = { -0.7, -0.5, -0.1, 0.8, -0.2, -0.1, 0.1, 0.9, 0.3, -0.6, 0.7, -0.2, 0.8, 0.8, -0.2, 0.1, -0.9, 0.7 }; GLfloat colors[] = { 1.0, 0.6, 0.6, 1.0, 0.6, 0.6, 1.0, 0.6, 0.6, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 }; // Création d'une structure de données à plat std::vector vertices; for (int i = 0; i < 6; i++) { for (int j = 0; j < 3; j++) vertices.push_back (positions[i*3+j]); for (int j = 0; j < 3; j++) vertices.push_back (colors[i*3+j]); } // Création du VAO glCreateVertexArrays (1, &m_VAO_id); glBindVertexArray (m_VAO_id); // Création du VBO pour les positions et couleurs glGenBuffers (1, &m_VBO_id); glBindBuffer (GL_ARRAY_BUFFER, m_VBO_id); // Copie le buffer dans la mémoire du serveur glBufferData (GL_ARRAY_BUFFER, vertices.size()*sizeof(GLfloat), vertices.data(), GL_STATIC_DRAW); // VAA associant les données à la variable vPos du shader, avec l'offset 0 glVertexAttribPointer (VPOS_LOC, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), reinterpret_cast(0*sizeof(GLfloat))); glEnableVertexAttribArray (VPOS_LOC); // VAA associant les données à la variable vCol du shader, avec l'offset 3 glVertexAttribPointer (VCOL_LOC, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), reinterpret_cast(3*sizeof(GLfloat))); glEnableVertexAttribArray (VCOL_LOC); glBindVertexArray (0); // désactive le VAO courant m_VAO_id } ~Triangles() { glDeleteBuffers (1, &m_VBO_id); glDeleteVertexArrays (1, &m_VAO_id); } void draw() { glBindVertexArray (m_VAO_id); glDrawArrays (GL_TRIANGLES, 0, 6); glBindVertexArray (0); } }; // Triangles //------------------------------ T R I A N T E X ------------------------------ class TrianTex { GLuint m_VAO_id, m_VBO_id; public: TrianTex() { // Données GLfloat positions[] = { -0.7, -0.5, -0.1, 0.8, -0.2, -0.1, 0.1, 0.9, 0.3, -0.6, 0.7, -0.2, 0.8, 0.8, -0.2, 0.1, -0.9, 0.7 }; GLfloat tex_coords[] = { 0.0, 1.0, 1.0, 1.0, 0.5, 0.0, 0.0, 0.0, 1.0, 0.0, 0.5, 1.0 }; // Création d'une structure de données à plat std::vector vertices; for (int i = 0; i < 6; i++) { for (int j = 0; j < 3; j++) vertices.push_back (positions[i*3+j]); for (int j = 0; j < 2; j++) vertices.push_back (tex_coords[i*2+j]); } // Création du VAO glCreateVertexArrays (1, &m_VAO_id); glBindVertexArray (m_VAO_id); // Création du VBO pour les positions et couleurs glGenBuffers (1, &m_VBO_id); glBindBuffer (GL_ARRAY_BUFFER, m_VBO_id); // Copie le buffer dans la mémoire du serveur glBufferData (GL_ARRAY_BUFFER, vertices.size()*sizeof(GLfloat), vertices.data(), GL_STATIC_DRAW); // VAA associant les données à la variable vPos du shader, avec l'offset 0 glVertexAttribPointer (VPOS_LOC, 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), reinterpret_cast(0*sizeof(GLfloat))); glEnableVertexAttribArray (VPOS_LOC); // VAA associant les données à la variable vTex du shader, avec l'offset 3 glVertexAttribPointer (VTEX_LOC, 2, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), reinterpret_cast(3*sizeof(GLfloat))); glEnableVertexAttribArray (VTEX_LOC); glBindVertexArray (0); // désactive le VAO courant m_VAO_id } ~TrianTex() { glDeleteBuffers (1, &m_VBO_id); glDeleteVertexArrays (1, &m_VAO_id); } void draw (GLuint texture_id1, GLuint texture_id2) { glBindVertexArray (m_VAO_id); glBindTexture (GL_TEXTURE_2D, texture_id1); glDrawArrays (GL_TRIANGLES, 0, 3); glBindTexture (GL_TEXTURE_2D, texture_id2); glDrawArrays (GL_TRIANGLES, 3, 3); glBindVertexArray (0); } }; // TrianTex //--------------------------------- K I T E ----------------------------------- class Kite { GLuint m_VAO_id, m_VBO_id; bool m_phong; public: Kite (bool phong) : m_phong {phong} { // Positions GLfloat positions[] = { 0.2, 0.5, 0.2, // 0 A 0.1, -0.6, 0.1, // 1 B -0.8, 0.1, -0.3, // 2 C 0.8, -0.1, -0.1, // 3 D }; // A C B A B D GLint ind_pos[] = { 0, 2, 1, 0, 1, 3 }; // Couleurs GLfloat colors[] = { 1.0, 0.6, 0.6, // triangle 0 0.7, 1.0, 0.5, // triangle 1 }; // A C B A B D int ind_col[] = { 0, 0, 0, 1, 1, 1 }; // Conversion des positions en vec3 auto pos_to_v = [&](int i) { return vmath::vec3( positions[i*3], positions[i*3+1], positions[i*3+2]); }; vmath::vec3 pA = pos_to_v(0), pB = pos_to_v(1), pC = pos_to_v(2), pD = pos_to_v(3); vmath::vec3 vAB = pB-pA, vAC = pC-pA, vAD = pD-pA; // Calcul des normales vmath::vec3 nACB = vmath::cross (vAC, vAB), nABD = vmath::cross (vAB, vAD); // Normalisation vmath::normalize (nACB); vmath::normalize (nABD); std::vector normals; std::vector ind_nor; if (m_phong) { vmath::vec3 nAB = (nACB + nABD) / 2.0; // Stockage des normales normalisées pour lissage de Phong normals = { nACB[0], nACB[1], nACB[2], nABD[0], nABD[1], nABD[2], nAB [0], nAB [1], nAB [2] }; // A C B A B D ind_nor = { 2, 0, 2, 2, 2, 1 }; } else { // Stockage des normales normalisées normals = { nACB[0], nACB[1], nACB[2], nABD[0], nABD[1], nABD[2] }; // A C B A B D ind_nor = { 0, 0, 0, 1, 1, 1 }; } // Création d'une structure de données à plat std::vector vertices; for (int i = 0; i < 6; i++) { // Positions sommets for (int j = 0; j < 3; j++) vertices.push_back (positions[ind_pos[i]*3+j]); // Couleurs sommets for (int j = 0; j < 3; j++) vertices.push_back (colors[ind_col[i]*3+j]); // Normales sommets for (int j = 0; j < 3; j++) vertices.push_back (normals[ind_nor[i]*3+j]); } // Création du VAO glCreateVertexArrays (1, &m_VAO_id); glBindVertexArray (m_VAO_id); // Création du VBO pour les positions et couleurs glGenBuffers (1, &m_VBO_id); glBindBuffer (GL_ARRAY_BUFFER, m_VBO_id); // Copie le buffer dans la mémoire du serveur glBufferData (GL_ARRAY_BUFFER, vertices.size()*sizeof(GLfloat), vertices.data(), GL_STATIC_DRAW); // VAA associant les données à la variable vPos du shader, avec l'offset 0 glVertexAttribPointer (VPOS_LOC, 3, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), reinterpret_cast(0*sizeof(GLfloat))); glEnableVertexAttribArray (VPOS_LOC); // VAA associant les données à la variable vCol du shader, avec l'offset 3 glVertexAttribPointer (VCOL_LOC, 3, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), reinterpret_cast(3*sizeof(GLfloat))); glEnableVertexAttribArray (VCOL_LOC); // VAA associant les données à la variable vNor du shader, avec l'offset 6 glVertexAttribPointer (VNOR_LOC, 3, GL_FLOAT, GL_FALSE, 9*sizeof(GLfloat), reinterpret_cast(6*sizeof(GLfloat))); glEnableVertexAttribArray (VNOR_LOC); glBindVertexArray (0); // désactive le VAO courant m_VAO_id } ~Kite() { glDeleteBuffers (1, &m_VBO_id); glDeleteVertexArrays (1, &m_VAO_id); } void draw() { glBindVertexArray (m_VAO_id); glDrawArrays (GL_TRIANGLES, 0, 6); glBindVertexArray (0); } }; // Kite //------------------------------ W I R E C U B E ---------------------------- class WireCube { bool m_is_white; GLfloat m_radius; GLuint m_VAO_id, m_VBO_id, m_EBO_id; public: WireCube (bool is_white, GLfloat radius) : m_is_white {is_white}, m_radius {radius} { GLfloat r = m_radius; // Positions GLfloat positions[] = { -r, -r, -r, // P0 6 ------- 7 r, -r, -r, // P1 / | / | -r, r, -r, // P2 / | / | r, r, -r, // P3 2 ------- 3 | -r, -r, r, // P4 | 4 --|---- 5 r, -r, r, // P5 | / | / -r, r, r, // P6 | / | / r, r, r, // P7 0 ------- 1 }; // Indices : positions, couleurs GLint indexes[] = { 0, 1, 0, 0, 2, 3, 0, 0, 4, 5, 0, 0, 6, 7, 0, 0, 0, 2, 1, 1, 1, 3, 1, 1, 4, 6, 1, 1, 5, 7, 1, 1, 0, 4, 2, 2, 1, 5, 2, 2, 2, 6, 2, 2, 3, 7, 2, 2 }; // Couleurs GLfloat colors_rgb[] = { 1.0, 0.0, 0.0, // C0 C1 C2 0.0, 1.0, 0.0, // C1 | / 0.0, 0.0, 1.0 // C2 + --C0 }; GLfloat colors_white[] = { 1.0, 1.0, 1.0, // C0 1.0, 1.0, 1.0, // C1 1.0, 1.0, 1.0 // C2 }; GLfloat *colors = (is_white) ? colors_white : colors_rgb; // Création d'une structure de données à plat std::vector vertices; for (int i = 0; i < 12*4; i+=4) { for (int j = 0; j < 3; j++) vertices.push_back (positions[indexes[i+0]*3+j]); for (int j = 0; j < 3; j++) vertices.push_back (colors[indexes[i+2]*3+j]); for (int j = 0; j < 3; j++) vertices.push_back (positions[indexes[i+1]*3+j]); for (int j = 0; j < 3; j++) vertices.push_back (colors[indexes[i+3]*3+j]); } // Création du VAO glCreateVertexArrays (1, &m_VAO_id); glBindVertexArray (m_VAO_id); // Création du VBO pour les positions et couleurs glGenBuffers (1, &m_VBO_id); glBindBuffer (GL_ARRAY_BUFFER, m_VBO_id); // Copie le buffer dans la mémoire du serveur glBufferData (GL_ARRAY_BUFFER, vertices.size()*sizeof(GLfloat), vertices.data(), GL_STATIC_DRAW); // VAA associant les données à la variable vPos du shader, avec l'offset 0 glVertexAttribPointer (VPOS_LOC, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), reinterpret_cast(0*sizeof(GLfloat))); glEnableVertexAttribArray (VPOS_LOC); // VAA associant les données à la variable vCol du shader, avec l'offset 3 glVertexAttribPointer (VCOL_LOC, 3, GL_FLOAT, GL_FALSE, 6*sizeof(GLfloat), reinterpret_cast(3*sizeof(GLfloat))); glEnableVertexAttribArray (VCOL_LOC); glBindVertexArray (0); // désactive le VAO courant m_VAO_id } ~WireCube() { glDeleteBuffers (1, &m_VBO_id); glDeleteVertexArrays (1, &m_VAO_id); } void draw() { glBindVertexArray(m_VAO_id); glDrawArrays (GL_LINES, 0, 24); glBindVertexArray (0); } }; // WireCube //------------------------ S H A D E R P R O G R A M S ---------------------- class ShaderProg { public: enum ShaderType { T_VERTEX, T_FRAGMENT, T_GEOMETRY, T_NUM }; static const char* get_shader_type_name (ShaderType type) { switch (type) { case T_VERTEX : return "vertex"; case T_FRAGMENT : return "fragment"; case T_GEOMETRY : return "geometry"; default : return ""; } } static GLenum get_gl_shader_type (ShaderType type) { switch (type) { case T_VERTEX : return GL_VERTEX_SHADER; case T_FRAGMENT : return GL_FRAGMENT_SHADER; case T_GEOMETRY : return GL_GEOMETRY_SHADER; default : return 0; } } static ShaderType get_shader_type_from_argv (const char* arg) { if (!strcmp (arg, "-vs")) return T_VERTEX; if (!strcmp (arg, "-fs")) return T_FRAGMENT; if (!strcmp (arg, "-gs")) return T_GEOMETRY; return T_NUM; } enum ShaderCateg { C_COLOR, C_TEXTURE, C_DIFFUSE, C_SPECULAR, C_NUM }; static const char* get_shader_categ_name (ShaderCateg categ) { switch (categ) { case C_COLOR : return "color"; case C_TEXTURE : return "texture"; case C_DIFFUSE : return "diffuse"; case C_SPECULAR : return "specular"; default : return ""; } } static ShaderCateg get_shader_categ_from_name (const char* name) { if (!strcmp (name, "color")) return C_COLOR; if (!strcmp (name, "texture")) return C_TEXTURE; if (!strcmp (name, "diffuse")) return C_DIFFUSE; if (!strcmp (name, "specular")) return C_SPECULAR; return C_NUM; } static std::string get_usage_for_shader_categs() { std::string s; for (auto categ = C_COLOR; categ < C_NUM; categ = static_cast((int)categ+1)) { const char* name = get_shader_categ_name (categ); if (!name) continue; if (s.length() > 0) s+= "|"; s += name; } return s; } const char* m_default_shader_texts[C_NUM][T_NUM] = { // C_COLOR : transmet position et couleur { // Vertex shader "#version 330\n" "in vec4 vPos;\n" "in vec4 vCol;\n" "out VertexData {\n" " vec4 color;\n" "} vd_out;\n" "layout (std140) uniform Uniforms {\n" " mat4 matProj;\n" " mat4 matCam;\n" " vec4 mousePos;\n" " float time;\n" "};\n" "uniform mat4 matWorld;\n" "\n" "void main()\n" "{\n" " gl_Position = matProj * matCam * matWorld * vPos;\n" " vd_out.color = vCol;\n" "}\n", // Fragment shader "#version 330\n" "in VertexData {\n" " vec4 color;\n" "} vd_in;\n" "out vec4 fragColor;\n" "\n" "void main()\n" "{\n" " fragColor = vd_in.color;\n" "}\n", // Geometry shader "" }, // C_TEXTURE : texture non éclairée { // Vertex shader "#version 330\n" "in vec4 vPos;\n" "in vec2 vTex;\n" "out VertexData {\n" " vec2 texCoord;\n" "} vd_out;\n" "layout (std140) uniform Uniforms {\n" " mat4 matProj;\n" " mat4 matCam;\n" " vec4 mousePos;\n" " float time;\n" "};\n" "uniform mat4 matWorld;\n" "\n" "void main()\n" "{\n" " gl_Position = matProj * matCam * matWorld * vPos;\n" " vd_out.texCoord = vTex;\n" "}\n", // Fragment shader "#version 330\n" "in VertexData {\n" " vec2 texCoord;\n" "} vd_in;\n" "out vec4 fragColor;\n" "uniform sampler2D uTex;\n" "\n" "void main()\n" "{\n" " fragColor = texture2D (uTex, vd_in.texCoord);\n" "}\n", // Geometry shader "" }, // C_DIFFUSE : normales, lumière ambiante et diffuse { // Vertex shader "#version 330\n" "in vec4 vPos;\n" "in vec4 vCol;\n" "in vec3 vNor;\n" "out VertexData {\n" " vec4 color;\n" " vec3 normal;\n" "} vd_out;\n" "layout (std140) uniform Uniforms {\n" " mat4 matProj;\n" " mat4 matCam;\n" " vec4 mousePos;\n" " float time;\n" "};\n" "uniform mat4 matWorld;\n" "uniform mat3 matNor;\n" "\n" "void main()\n" "{\n" " gl_Position = matProj * matCam * matWorld * vPos;\n" " vd_out.color = vCol;\n" " vd_out.normal = matNor * vNor;\n" "}\n", // Fragment shader "#version 330\n" "in VertexData {\n" " vec4 color;\n" " vec3 normal;\n" "} vd_in;\n" "out vec4 fragColor;\n" "\n" "void main()\n" "{\n" " // Couleur et direction de lumière, normalisée\n" " vec3 lightColor = vec3(0.8);\n" " vec3 lightDir = normalize(vec3(0.0, 0.0, 10.0));\n" "\n" " // Normale du fragment, normalisée\n" " vec3 nor3 = normalize(vd_in.normal);\n" "\n" " // Cosinus de l'angle entre la normale et la lumière\n" " float cosTheta = dot(nor3, lightDir);\n" "\n" " // Lumière diffuse\n" " vec3 diffuse = lightColor * max(cosTheta, 0.0);\n" "\n" " // Lumière ambiante\n" " vec3 ambiant = vec3(0.3);\n" "\n" " // Somme des lumières\n" " vec3 sumLight = diffuse + ambiant;\n" "\n" " // Couleur de l'objet éclairé\n" " vec4 result = vec4(sumLight, 1.0) * vd_in.color;\n" "\n" " fragColor = clamp(result, 0.0, 1.0);\n" "}\n", // Geometry shader "" }, // C_SPECULAR : normales, lumière ambiante, diffuse et spéculaire { // Vertex shader "#version 330\n" "in vec4 vPos;\n" "in vec4 vCol;\n" "in vec3 vNor;\n" "out VertexData {\n" " vec4 color;\n" " vec4 posit;\n" " vec3 normal;\n" "} vd_out;\n" "layout (std140) uniform Uniforms {\n" " mat4 matProj;\n" " mat4 matCam;\n" " vec4 mousePos;\n" " float time;\n" "};\n" "uniform mat4 matWorld;\n" "uniform mat3 matNor;\n" "\n" "void main()\n" "{\n" " gl_Position = matProj * matCam * matWorld * vPos;\n" " vd_out.color = vCol;\n" " vd_out.posit = matWorld * vPos;\n" " vd_out.normal = matNor * vNor;\n" "}\n", // Fragment shader "#version 330\n" "in VertexData {\n" " vec4 color;\n" " vec4 posit;\n" " vec3 normal;\n" "} vd_in;\n" "out vec4 fragColor;\n" "\n" "void main()\n" "{\n" " // Couleur et direction de lumière, normalisée\n" " vec3 lightColor = vec3(0.6);\n" " vec3 lightDir = normalize(vec3(0.0, -1.0, 10.0));\n" "\n" " // Normale du fragment, normalisée\n" " vec3 nor3 = normalize(vd_in.normal);\n" "\n" " // Cosinus de l'angle entre la normale et la lumière\n" " float cosTheta = dot(nor3, lightDir);\n" "\n" " // Lumière diffuse\n" " vec3 diffuse = lightColor * max(cosTheta, 0.0);\n" "\n" " // Lumière ambiante\n" " vec3 ambiant = vec3(0.2);\n" "\n" " // Lumière spéculaire\n" " vec3 eyePos = vec3(0.0, 0.0, 1.0);\n" " vec3 specColor = vec3(1.0, 1.0, 1.0);\n" " float spec_S = 0.2;\n" " float spec_m = 32.0;\n" " vec3 specular = vec3(0);\n" "\n" " if (cosTheta > 0.0) {\n" " vec3 R = reflect (-lightDir, nor3);\n" " vec3 V = eyePos - vec3(vd_in.posit);\n" " float cosGamma = dot(normalize(R), normalize(V));\n" " specular = specColor * spec_S * pow(max(cosGamma, 0.0), spec_m);\n" " }\n" "\n" " // Somme des lumières\n" " vec3 sumLight = diffuse + specular + ambiant;\n" "\n" " // Couleur de l'objet éclairé\n" " vec4 result = vec4(sumLight, 1.0) * vd_in.color;\n" "\n" " fragColor = clamp(result, 0.0, 1.0);\n" "}\n", // Geometry shader "" } }; private: ShaderCateg m_shader_categ; std::string m_shader_str[T_NUM]; std::vector m_shaders; GLuint m_program = -1; public: ShaderProg (ShaderCateg categ, const std::string shader_paths[T_NUM]) : m_shader_categ {categ} { if (categ < C_COLOR || C_NUM <= categ) return; for (auto type = T_VERTEX; type < T_NUM; type = static_cast((int)type+1)) { m_shader_str[type] = m_default_shader_texts[categ][type]; } m_program = glCreateProgram(); bind_attrib_locations(); for (auto type = T_VERTEX; type < T_NUM; type = static_cast((int)type+1)) { const std::string& shader_path = shader_paths[type]; if (shader_path.length() > 0) load_shader_code (type, shader_path); } } ~ShaderProg() { glDeleteProgram (m_program); } const GLuint get_program() { return m_program; } void use_program() { glUseProgram (m_program); } // Appeler après glCreateProgram mais avant glLinkProgram void bind_attrib_locations() { for (auto loc = VPOS_LOC; loc < LAST_LOC; loc = static_cast((int)loc+1)) { const char* name = get_vertex_attribute_name (loc); if (!name) continue; glBindAttribLocation (m_program, loc, name); } } GLint get_uniform (const char* name) { return glGetUniformLocation (m_program, name); } bool compile_program() { const char* name = get_shader_categ_name(m_shader_categ); std::cout << "Begin compilation of " << name << " program...\n"; for (auto type = T_VERTEX; type < T_NUM; type = static_cast((int)type+1)) { std::string& shader_str = m_shader_str[type]; if (shader_str.length() == 0) continue; GLenum gl_shader_type = get_gl_shader_type (type); GLuint shader = glCreateShader (gl_shader_type); m_shaders.push_back (shader); const char* shader_text = shader_str.c_str(); const char* name = get_shader_type_name (type); glShaderSource (shader, 1, &shader_text, NULL); if (compile_shader (shader, name)) glAttachShader (m_program, shader); } bool ok = link_program(); // Marque les shaders pour suppression par glDeleteProgram for (auto shader : m_shaders) { glDeleteShader (shader); } if (ok) { std::cout << "Compilation succeed." << std::endl; } return ok; } bool compile_shader (GLuint shader, const char* name) { std::cout << "Compile " << name << " shader...\n"; glCompileShader (shader); GLint isCompiled = 0; glGetShaderiv (shader, GL_COMPILE_STATUS, &isCompiled); bool ok = isCompiled == GL_TRUE; GLsizei maxLength = 2048, length; char infoLog[maxLength]; glGetShaderInfoLog (shader, maxLength, &length, infoLog); if (length > 0) { if (isCompiled == GL_TRUE) std::cout << "Compilation messages:\n"; std::cout << infoLog << std::endl; } return ok; } bool link_program() { std::cout << "Link program...\n"; glLinkProgram (m_program); GLint status; glGetProgramiv (m_program, GL_LINK_STATUS, &status); bool ok = status == GL_TRUE; GLsizei maxLength = 2048, length; char infoLog[maxLength]; glGetProgramInfoLog (m_program, maxLength, &length, infoLog); if (length > 0) { if (status == GL_TRUE) std::cout << "Linking messages:\n"; else std::cout << "### Linking errors:\n"; std::cout << infoLog << std::endl; } return ok; } bool load_shader_code (const ShaderType type, const std::string path) { if (type < T_VERTEX || type >= T_NUM) return false; if (path == "") return false; std::ifstream shader_file; std::stringstream shader_stream; shader_file.exceptions (std::ifstream::failbit | std::ifstream::badbit); try { std::cout << "Loading \"" << path << "\" ..." << std::endl; shader_file.open (path); shader_stream << shader_file.rdbuf(); shader_file.close(); m_shader_str[type] = shader_stream.str(); } catch (...) { std::cerr << "### Load error: " << strerror(errno) << std::endl; return false; } return true; } void print_shaders() { const char* categ_name = get_shader_categ_name(m_shader_categ); for (auto type = T_VERTEX; type < T_NUM; type = static_cast((int)type+1)) { std::string& shader_str = m_shader_str[type]; if (shader_str.length() == 0) continue; const char* type_name = get_shader_type_name (type); std::cout << "\n-------- " << type_name << " shader for " << categ_name << " --------\n\n" << shader_str << std::endl; } } }; // ShaderProg //------------------------------------ A P P ---------------------------------- const double FRAMES_PER_SEC = 30.0; const double ANIM_DURATION = 18.0; // En salle TP mettre à 0 si l'affichage "bave" const int NUM_SAMPLES = 16; enum CamProj { P_ORTHO, P_FRUSTUM, P_MAX }; class MyApp { bool m_ok = false; GLFWwindow* m_window = nullptr; double m_aspect_ratio = 1.0; bool m_anim_flag = false; int m_cube_color = 1; float m_radius = 0.5; float m_anim_angle = 0, m_start_angle = 0; float m_cam_z, m_cam_r, m_cam_near, m_cam_far; bool m_depth_flag = true; CamProj m_cam_proj; Triangles* m_triangles = nullptr; TrianTex* m_trian_tex = nullptr; WireCube* m_wire_cube_rgb = nullptr; WireCube* m_wire_cube_white = nullptr; Kite* m_kite1 = nullptr; Kite* m_kite2 = nullptr; bool m_flag_phong = false; std::string m_shader_paths[ShaderProg::C_NUM][ShaderProg::T_NUM]; std::string m_program_categ_to_print; ShaderProg* m_prog_color = nullptr; ShaderProg* m_prog_texture = nullptr; ShaderProg* m_prog_diffuse = nullptr; ShaderProg* m_prog_specular = nullptr; vmath::vec4 m_mousePos; // mouse_x, mouse_y, width, height GLuint m_texture_id1, m_texture_id2; const char* m_texture_path1 = "side1.png"; const char* m_texture_path2 = "side2.png"; GLuint m_UBO_id; void load_programs() { m_prog_color = new ShaderProg { ShaderProg::C_COLOR, m_shader_paths[ShaderProg::C_COLOR] }; m_prog_color->compile_program(); m_prog_texture = new ShaderProg { ShaderProg::C_TEXTURE, m_shader_paths[ShaderProg::C_TEXTURE] }; m_prog_texture->compile_program(); m_prog_diffuse = new ShaderProg { ShaderProg::C_DIFFUSE, m_shader_paths[ShaderProg::C_DIFFUSE] }; m_prog_diffuse->compile_program(); m_prog_specular = new ShaderProg { ShaderProg::C_SPECULAR, m_shader_paths[ShaderProg::C_SPECULAR] }; m_prog_specular->compile_program(); } void tear_programs() { delete m_prog_color; m_prog_color = nullptr; delete m_prog_texture; m_prog_texture = nullptr; delete m_prog_diffuse; m_prog_diffuse = nullptr; delete m_prog_specular; m_prog_specular = nullptr; } void print_program_shaders (std::string& categ) { if (categ.length() > 0) { if (categ == "color") m_prog_color->print_shaders(); else if (categ == "texture") m_prog_texture->print_shaders(); else if (categ == "diffuse") m_prog_diffuse->print_shaders(); else if (categ == "specular") m_prog_specular->print_shaders(); else std::cerr << "### Error: program " << categ << " unknown" << std::endl; } } void initGL() { std::cout << __func__ << std::endl; glEnable (GL_DEPTH_TEST); load_programs(); print_program_shaders (m_program_categ_to_print); // Init position de la souris au milieu de la fenêtre int width, height; glfwGetWindowSize (m_window, &width, &height); m_mousePos = {width/2.0f, height/2.0f, (float) width, (float) height}; // Création des textures m_texture_id1 = load_texture (m_texture_path1); m_texture_id2 = load_texture (m_texture_path2); // Création des objets graphiques m_triangles = new Triangles {}; m_trian_tex = new TrianTex {}; m_wire_cube_white = new WireCube {true, 0.5}; m_wire_cube_rgb = new WireCube {false, 0.5}; m_kite1 = new Kite {false}; m_kite2 = new Kite {true}; // Création UBO avec taille réservée glGenBuffers (1, &m_UBO_id); glBindBuffer (GL_UNIFORM_BUFFER, m_UBO_id); glBufferData (GL_UNIFORM_BUFFER, sizeof(UBO_Uniforms), NULL, GL_STATIC_DRAW); glBindBuffer (GL_UNIFORM_BUFFER, 0); // On relie le UBO et chaque shader au binding point glBindBufferBase (GL_UNIFORM_BUFFER, UBO_BINDING_POINT, m_UBO_id); std::vector program_ids { m_prog_color->get_program(), m_prog_texture->get_program(), m_prog_diffuse->get_program(), m_prog_specular->get_program() }; for (GLuint prog : program_ids) { glUniformBlockBinding (prog, glGetUniformBlockIndex (prog, "Uniforms"), UBO_BINDING_POINT); } } void tearGL() { // Destruction des objets graphiques delete m_triangles; delete m_trian_tex; delete m_wire_cube_white; delete m_wire_cube_rgb; delete m_kite1; delete m_kite2; glDeleteBuffers (1, &m_UBO_id); tear_programs(); } void animate() { auto frac_part = [](double x){ return x - std::floor(x); }; // Change la coordonnée en fonction du temps double time = glfwGetTime(); // durée depuis init double slice = time / ANIM_DURATION; double a = frac_part(slice); m_anim_angle = m_start_angle + a*360.0; } void displayGL() { //glClearColor (0.95, 1.0, 0.8, 1.0); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); vmath::mat4 mat_proj, mat_cam, mat_world, mat_MVP; vmath::mat3 mat_Nor; set_projection (mat_proj, mat_cam); // On met les données dans le UBO glBindBuffer (GL_UNIFORM_BUFFER, m_UBO_id); glBufferSubData (GL_UNIFORM_BUFFER, offsetof(UBO_Uniforms, matProj), sizeof(vmath::mat4), &mat_proj); glBufferSubData (GL_UNIFORM_BUFFER, offsetof(UBO_Uniforms, matCam), sizeof(vmath::mat4), &mat_cam); glBufferSubData (GL_UNIFORM_BUFFER, offsetof(UBO_Uniforms, mousePos), sizeof(vmath::vec4), &m_mousePos); GLfloat time_f = glfwGetTime(); // GLdouble nécessite v4.0+, mal géré glBufferSubData (GL_UNIFORM_BUFFER, offsetof(UBO_Uniforms, time), sizeof(GLfloat), &time_f); glBindBuffer (GL_UNIFORM_BUFFER, 0); ShaderProg* prog = nullptr; //-------- En haut à gauche -------- prog = m_prog_color; prog->use_program(); mat_world = vmath::translate (-0.8f, +0.7f, 0.f) * vmath::scale (0.7f) * vmath::rotate (m_anim_angle, 0.f, 1.f, 0.15f); glUniformMatrix4fv (prog->get_uniform ("matWorld"), 1, GL_FALSE, mat_world); m_triangles->draw(); if (m_cube_color == 1) m_wire_cube_white->draw(); else if (m_cube_color == 2) m_wire_cube_rgb->draw(); //-------- En haut à droite -------- prog = m_prog_texture; prog->use_program(); mat_world = vmath::translate (0.8f, +0.7f, 0.f) * vmath::scale (0.7f) * vmath::rotate (m_anim_angle, 0.f, 1.f, 0.15f); glUniformMatrix4fv (prog->get_uniform ("matWorld"), 1, GL_FALSE, mat_world); m_trian_tex->draw (m_texture_id1, m_texture_id2); //-------- En bas à gauche -------- prog = m_prog_diffuse; prog->use_program(); mat_world = vmath::translate (-0.8f, -0.7f, 0.f) * vmath::scale (0.9f) * vmath::rotate (m_anim_angle, 0.f, 1.f, 0.15f) * vmath::rotate (-20.0f, 1.f, 0.f, 0.f); mat_Nor = vmath::normal (mat_world); glUniformMatrix4fv (prog->get_uniform ("matWorld"), 1, GL_FALSE, mat_world); glUniformMatrix3fv (prog->get_uniform ("matNor"), 1, GL_FALSE, mat_Nor); if (m_flag_phong) m_kite2->draw(); else m_kite1->draw(); //-------- En bas à droite -------- prog = m_prog_specular; prog->use_program(); mat_world = vmath::translate (0.8f, -0.7f, 0.f) * vmath::scale (0.9f) * vmath::rotate (m_anim_angle, 0.f, 1.f, 0.15f) * vmath::rotate (-20.0f, 1.f, 0.f, 0.f); mat_Nor = vmath::normal (mat_world); glUniformMatrix4fv (prog->get_uniform ("matWorld"), 1, GL_FALSE, mat_world); glUniformMatrix3fv (prog->get_uniform ("matNor"), 1, GL_FALSE, mat_Nor); if (m_flag_phong) m_kite2->draw(); else m_kite1->draw(); } void set_projection (vmath::mat4& mat_proj, vmath::mat4& mat_cam) { mat_proj = vmath::mat4::identity(); GLfloat hr = m_cam_r, wr = hr * m_aspect_ratio; switch (m_cam_proj) { case P_ORTHO : mat_proj = vmath::ortho (-wr, wr, -hr, hr, m_cam_near, m_cam_far); break; case P_FRUSTUM : mat_proj = vmath::frustum (-wr, wr, -hr, hr, m_cam_near, m_cam_far); break; default: ; } vmath::vec3 eye {0, 0, (float)m_cam_z}, center {0, 0, 0}, up {0, 1., 0}; mat_cam = vmath::lookat (eye, center, up); } GLuint load_texture (const char* path) { GLuint texture_id; glGenTextures (1, &texture_id); glBindTexture (GL_TEXTURE_2D, texture_id); // Options de filtrage pour le mipmap glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Charge l'image et génère la texture int width, height, n_comp; std::cout << "Loading texture \"" << path << "\" ..." << std::endl; unsigned char *data = stbi_load (path, &width, &height, &n_comp, 0); if (!data) { std::cout << "### Loading error: " << stbi_failure_reason() << std::endl; glDeleteTextures (1, &texture_id); return 0; } glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glGenerateMipmap (GL_TEXTURE_2D); stbi_image_free (data); return texture_id; } void cam_init() { m_cam_z = 3; m_cam_r = 0.5; m_cam_near = 1; m_cam_far = 5; m_cam_proj = P_FRUSTUM; } void set_viewport (int width, int height) { glViewport (0, 0, width, height); m_aspect_ratio = (double) width / height; } static void on_reshape_func (GLFWwindow* window, int width, int height) { std::cout << __func__ << " " << width << " " << height << std::endl; MyApp* that = static_cast(glfwGetWindowUserPointer (window)); that->set_viewport (width, height); } static void print_help() { std::cout << "h help i init a anim p proj zZ cam_z rR radius nN near " << "fF far dD dist b z-buffer c cube u update program o Phong" << std::endl; } void print_projection() { switch (m_cam_proj) { case P_ORTHO : std::cout << "Ortho: "; break; case P_FRUSTUM : std::cout << "Frustum: "; break; default: ; } GLfloat hr = m_cam_r, wr = hr * m_aspect_ratio; std::cout << std::fixed << std::setprecision(1) << -wr << ", " << wr << ", " << -hr << ", " << hr << ", " << m_cam_near << ", " << m_cam_far << " ; " << "cam_z = " << m_cam_z << std::endl; } static void on_mouse_func (GLFWwindow* window, double xpos, double ypos) { //std::cout << __func__ << " " << xpos << " " << ypos << std::endl; MyApp* that = static_cast(glfwGetWindowUserPointer (window)); if (!that->m_ok) return; int width, height; glfwGetWindowSize (window, &width, &height); that->m_mousePos[0] = xpos; that->m_mousePos[1] = height - ypos; // origine en bas à gauche that->m_mousePos[2] = width; that->m_mousePos[3] = height; } static void on_key_func (GLFWwindow* window, int key, int scancode, int action, int mods) { //std::cout << __func__ << " " << key << " " << scancode << " " // << action << " " << mods << std::endl; // action = GLFW_PRESS ou GLFW_REPEAT ou GLFW_RELEASE if (action == GLFW_RELEASE) return; MyApp* that = static_cast(glfwGetWindowUserPointer (window)); int trans_key = translate_qwerty_to_azerty (key, scancode); switch (trans_key) { case GLFW_KEY_I : that->cam_init(); break; case GLFW_KEY_A : that->m_anim_flag = !that->m_anim_flag; if (that->m_anim_flag) { that->m_start_angle = that->m_anim_angle; glfwSetTime (0); } break; case GLFW_KEY_P : { int k = static_cast(that->m_cam_proj) + 1; if (k >= static_cast(P_MAX)) k = 0; that->m_cam_proj = static_cast(k); // Heuristique pour garder sensiblement la même taille if (that->m_cam_proj == P_FRUSTUM) that->m_cam_r /= 2.5; else if (that->m_cam_proj == P_ORTHO) that->m_cam_r *= 2.5; break; } case GLFW_KEY_Z : change_val_mods (that->m_cam_z, mods, 0.1, -100); break; case GLFW_KEY_R : change_val_mods (that->m_cam_r, mods, 0.1, 0.1); break; case GLFW_KEY_N : change_val_mods (that->m_cam_near, mods, 0.1, 0.1); break; case GLFW_KEY_F : change_val_mods (that->m_cam_far, mods, 0.1, 0.1); break; case GLFW_KEY_D : change_val_mods (that->m_cam_z, mods, 0.1, -100); change_val_mods (that->m_cam_near, mods, 0.1, 0.1); change_val_mods (that->m_cam_far, mods, 0.1, 0.1); break; case GLFW_KEY_B : that->m_depth_flag = !that->m_depth_flag; std::cout << "depth_flag is " << that->m_depth_flag << std::endl; if (that->m_depth_flag) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST); break; case GLFW_KEY_C : that->m_cube_color = (that->m_cube_color+1) % 3; break; case GLFW_KEY_U : that->tear_programs(); that->load_programs(); break; case GLFW_KEY_O : that->m_flag_phong = !that->m_flag_phong; break; case GLFW_KEY_H : print_help(); break; case GLFW_KEY_ESCAPE : that->m_ok = false; break; default: return; } that->print_projection(); } template static void change_val_mods (T& val, int mods, double incr, double min_val) { val += (mods & GLFW_MOD_SHIFT) ? incr : -incr; if (val <= min_val) val = min_val; } static int translate_qwerty_to_azerty (int key, int scancode) { // https://www.glfw.org/docs/latest/group__keys.html // QWERTY -> AZERTY switch (key) { case GLFW_KEY_Q : return GLFW_KEY_A; case GLFW_KEY_A : return GLFW_KEY_Q; case GLFW_KEY_W : return GLFW_KEY_Z; case GLFW_KEY_Z : return GLFW_KEY_W; case GLFW_KEY_SEMICOLON : return GLFW_KEY_M; } // Détection des différences non corrigées const char* name = glfwGetKeyName (key, scancode); if (name != NULL) { int capital = toupper(name[0]); if (capital != key) { std::cout << __func__ << " DIFF " << capital << " " << key << std::endl; } } return key; } static void on_error_func (int error, const char* description) { std::cerr << "### Error: " << description << std::endl; } bool parse_args (int argc, char* argv[]) { int i = 1; while (i < argc) { auto type = ShaderProg::get_shader_type_from_argv (argv[i]); if (type != ShaderProg::T_NUM && i+1 < argc) { auto categ = ShaderProg::get_shader_categ_from_name (argv[i+1]); if (categ != ShaderProg::C_NUM && i+2 < argc) { m_shader_paths[categ][type] = argv[i+2]; i += 3; continue; } } if (strcmp(argv[i], "-ps") == 0 && i+1 < argc) { m_program_categ_to_print = argv[i+1]; i += 2 ; continue; } if (strcmp(argv[i], "--help") == 0) { std::cout << "USAGE:\n" << " " << argv[0] << " [-vs|-fs|-gs categ path] [-ps categ]\n" << " categ: " << ShaderProg::get_usage_for_shader_categs() << std::endl; return false; } std::cerr << "### Error, bad arguments. Try --help" << std::endl; return false; } return true; } public: MyApp (int argc, char* argv[]) { if (!parse_args (argc, argv)) return; if (!glfwInit()) { std::cerr << "GLFW: initialization failed" << std::endl; return; } glfwSetErrorCallback (on_error_func); // Hints à spécifier avant la création de la fenêtre // https://www.glfw.org/docs/latest/window.html#window_hints_fb if (NUM_SAMPLES > 0) glfwWindowHint (GLFW_SAMPLES, NUM_SAMPLES); // On demande une version spécifique d'OpenGL glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 3); //glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE); glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Création de la fenêtre m_window = glfwCreateWindow (640, 480, "UBO", NULL, NULL); if (!m_window) { std::cerr << "GLFW: window creation failed" << std::endl; return; } // Les callbacks pour GLFW étant statiques, on mémorise l'instance glfwSetWindowUserPointer (m_window, this); glfwSetWindowSizeCallback (m_window, on_reshape_func); glfwSetCursorPosCallback (m_window, on_mouse_func); glfwSetKeyCallback (m_window, on_key_func); // Rend le contexte GL courant. Tous les appels GL seront placés après. glfwMakeContextCurrent (m_window); glfwSwapInterval (1); m_ok = true; cam_init(); print_help(); // Initialisation de la machinerie GL en utilisant GLAD. gladLoadGL(); std::cout << "Loaded OpenGL " << GLVersion.major << "." << GLVersion.minor << std::endl; // Mise à jour viewport et ratio avec taille réelle de la fenêtre int width, height; glfwGetWindowSize (m_window, &width, &height); set_viewport (width, height); initGL(); } void run() { while (m_ok && !glfwWindowShouldClose (m_window)) { displayGL(); glfwSwapBuffers (m_window); if (m_anim_flag) { glfwWaitEventsTimeout (1.0/FRAMES_PER_SEC); animate(); } else glfwWaitEvents(); } } ~MyApp() { if (m_ok) tearGL(); if (m_window) glfwDestroyWindow (m_window); glfwTerminate(); } }; // MyApp int main(int argc, char* argv[]) { MyApp app {argc, argv}; app.run(); }