/* Dessin avec shaders et wirecube CC BY-SA Edouard.Thiel@univ-amu.fr - 04/01/2025 */ #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(). // RQ: provoque un warning avec -O2, supprimé avec -fno-strict-aliasing #include "vmath.h" #include //------------------------------ T R I A N G L E S ---------------------------- class Triangles { GLint m_vPos_loc, m_vCol_loc; GLfloat m_vertices[18] = { -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 m_colors[18] = { 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 }; public: Triangles (GLint vPos_loc, GLint vCol_loc) : m_vPos_loc {vPos_loc}, m_vCol_loc {vCol_loc} {} void draw() { glVertexAttribPointer (m_vPos_loc, 3, GL_FLOAT, GL_FALSE, 0, m_vertices); glVertexAttribPointer (m_vCol_loc, 3, GL_FLOAT, GL_FALSE, 0, m_colors); glEnableVertexAttribArray (m_vPos_loc); glEnableVertexAttribArray (m_vCol_loc); glDrawArrays (GL_TRIANGLES, 0, 6); glDisableVertexAttribArray (m_vPos_loc); glDisableVertexAttribArray (m_vCol_loc); } }; // Triangles //------------------------------ W I R E C U B E ---------------------------- class WireCube { int m_cube_color; GLfloat m_radius; GLint m_vPos_loc, m_vCol_loc; std::vector m_vertices; std::vector m_colors; public: WireCube (int cube_color, GLfloat radius, GLint vPos_loc, GLint vCol_loc) : m_cube_color {cube_color}, m_radius {radius}, m_vPos_loc {vPos_loc}, m_vCol_loc {vCol_loc} { int sa[] = {-1, -1, 1, 1}, sb[] = {-1, 1, -1, 1}; GLfloat r = m_radius; auto vpush = [](std::vector& v, GLfloat a, GLfloat b, GLfloat c) { v.push_back(a); v.push_back(b); v.push_back(c); }; for (int i = 0; i < 4; i++) { vpush (m_vertices, -r, r*sa[i], r*sb[i]); vpush (m_vertices, r, r*sa[i], r*sb[i]); } for (int i = 0; i < 4; i++) { vpush (m_vertices, r*sa[i], -r, r*sb[i]); vpush (m_vertices, r*sa[i], r, r*sb[i]); } for (int i = 0; i < 4; i++) { vpush (m_vertices, r*sa[i], r*sb[i], -r); vpush (m_vertices, r*sa[i], r*sb[i], r); } if (m_cube_color == 1) { for (int i = 0; i < 24; i++) vpush (m_colors, 1.0, 1.0, 1.0); } else if (m_cube_color == 2) { for (int i = 0; i < 8; i++) vpush (m_colors, 1.0, 0.0, 0.0); for (int i = 0; i < 8; i++) vpush (m_colors, 0.0, 1.0, 0.0); for (int i = 0; i < 8; i++) vpush (m_colors, 0.0, 0.0, 1.0); } } void draw() { glVertexAttribPointer (m_vPos_loc, 3, GL_FLOAT, GL_FALSE, 0, static_cast(m_vertices.data()) ); glVertexAttribPointer (m_vCol_loc, 3, GL_FLOAT, GL_FALSE, 0, static_cast(m_colors.data()) ); glEnableVertexAttribArray (m_vPos_loc); glEnableVertexAttribArray (m_vCol_loc); glDrawArrays (GL_LINES, 0, m_vertices.size()/3); glDisableVertexAttribArray (m_vPos_loc); glDisableVertexAttribArray (m_vCol_loc); } }; // WireCube //------------------------------------ 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; 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 = 2; float m_radius = 0.5; float m_anim_angle = 0, m_start_angle = 0; const char* m_vertex_shader_text = "#version 330\n" "in vec4 vPos;\n" "in vec3 vCol;\n" "out vec3 color;\n" "uniform mat4 matMVP;\n" "\n" "void main()\n" "{\n" " gl_Position = matMVP * vPos;\n" " color = vCol;\n" "}\n"; const char* m_fragment_shader_text = "#version 330\n" "in vec3 color;\n" "out vec4 fragColor;\n" "\n" "void main()\n" "{\n" " fragColor = vec4(color, 1.0);\n" "}\n"; GLuint m_program = 0; GLint m_vPos_loc, m_vCol_loc; GLint m_matMVP_loc; 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 initGL() { std::cout << __func__ << std::endl; glEnable (GL_DEPTH_TEST); const GLuint vertex_shader = glCreateShader (GL_VERTEX_SHADER); glShaderSource (vertex_shader, 1, &m_vertex_shader_text, NULL); compile_shader (vertex_shader, "vertex"); const GLuint fragment_shader = glCreateShader (GL_FRAGMENT_SHADER); glShaderSource (fragment_shader, 1, &m_fragment_shader_text, NULL); compile_shader (fragment_shader, "fragment"); m_program = glCreateProgram(); glAttachShader (m_program, vertex_shader); glAttachShader (m_program, fragment_shader); link_program (m_program); // Récupère l'identifiant des "variables" dans les shaders m_vPos_loc = glGetAttribLocation (m_program, "vPos"); m_vCol_loc = glGetAttribLocation (m_program, "vCol"); m_matMVP_loc = glGetUniformLocation (m_program, "matMVP"); } void displayGL() { //glClearColor (0.95, 1.0, 0.8, 1.0); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram (m_program); vmath::mat4 matrix = vmath::mat4::identity(); GLfloat hr = m_radius, wr = hr * m_aspect_ratio; matrix = matrix * vmath::frustum (-wr, wr, -hr, hr, 1.0, 5.0); vmath::vec3 eye {0, 0, 3.0}, center {0, 0, 0}, up {0, 1., 0}; matrix = matrix * vmath::lookat (eye, center, up); // Rotation de la scène pour l'animation, tout en float pour le template matrix = matrix * vmath::rotate (m_anim_angle, 0.f, 1.f, 0.15f); glUniformMatrix4fv (m_matMVP_loc, 1, GL_FALSE, matrix); // Dessins Triangles triangles {m_vPos_loc, m_vCol_loc}; triangles.draw(); if (m_cube_color > 0) { WireCube wire_cube {m_cube_color, 0.5, m_vPos_loc, m_vCol_loc}; wire_cube.draw(); } } void compile_shader (GLuint shader, const char* name) { std::cout << "Compile " << name << " shader...\n"; glCompileShader (shader); GLint isCompiled = 0; glGetShaderiv (shader, GL_COMPILE_STATUS, &isCompiled); if (isCompiled == GL_FALSE) m_ok = false; GLsizei maxLength = 2048, length; char infoLog[maxLength]; glGetShaderInfoLog (shader, maxLength, &length, infoLog); if (length == 0) return; if (isCompiled == GL_TRUE) std::cout << "Compilation messages:\n"; else std::cout << "### Compilation errors:\n"; std::cout << infoLog << std::endl; } void link_program (GLuint program) { std::cout << "Link program...\n"; glLinkProgram (program); GLint status; glGetProgramiv (program, GL_LINK_STATUS, &status); if (status == GL_FALSE) m_ok = false; GLsizei maxLength = 2048, length; char infoLog[maxLength]; glGetProgramInfoLog (program, maxLength, &length, infoLog); if (length == 0) return; if (status == GL_TRUE) std::cout << "Linking messages:\n"; else std::cout << "### Linking errors:\n"; std::cout << infoLog << std::endl; } 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 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_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_C : that->m_cube_color = (that->m_cube_color+1) % 3; break; case GLFW_KEY_ESCAPE : that->m_ok = false; break; } } 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; } public: MyApp() { 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); // Création de la fenêtre m_window = glfwCreateWindow (640, 480, "Shaders et cube", 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); 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; // 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_window) glfwDestroyWindow (m_window); glfwTerminate(); } }; // MyApp int main() { MyApp app; app.run(); }