// GLAD #include "glad/glad.h" // GLM #define GLM_ENABLE_EXPERIMENTAL #include "glm/ext/matrix_transform.hpp" #include "glm/ext/vector_float3.hpp" #include "glm/gtc/type_ptr.hpp" #include "glm/trigonometric.hpp" #include "glm/gtx/rotate_vector.hpp" #include "glm/gtx/string_cast.hpp" // SDL #include #include #include #include #include #include #include // STDLIB #include #include #include #include #include #define WINDOW_WIDTH 1280 #define WINDOW_HEIGHT 720 #define WINDOW_HALF_WIDTH 640 #define WINDOW_HALF_HEIGHT 360 enum exit_codes : int { EXIT_CODE_SUCCESS, EXIT_CODE_SDL_INIT_FAILED, EXIT_CODE_WINDOW_CREATION_FAILED, EXIT_CODE_OPENGL_CONTEXT_FAILED, EXIT_CODE_GLAD_LOADER_FAILED, }; struct Camera { // Point in world coordinates where the camera is located glm::vec3 position; // view_direction is an actual direction vector. When used with glm::lookAt, we add it to the position // to calculate the target the camera is looking at glm::vec3 view_direction; // The up vector in world coordinates glm::vec3 up; }; struct App { SDL_Window *window; SDL_GLContext context; SDL_Event event; // VAO is an object that stores the state needed to supply the GPU with the vertex data. // Think of it as a specification or a C struct that defines the types of data stored for the // vertex array including the attributes for each vertex as well the indices buffer if it exists. GLuint vao; // The buffer that contains all the vertex data // e.g. assume 3 vertices with position, colour and uv, the buffer would look like this // ----------------------------------------------------------------------------------- // | position1 | colour1 | uv1 | position2 | colour2 | uv2 | position3 | colour3 | uv3 | // ----------------------------------------------------------------------------------- GLuint vbo; // Holds the indices of the vertices that draw each triangle. Each triangle constitutes what // OpenGL refers to as element, hence the name Element Buffer Object GLuint ebo; GLuint shader_program; Camera camera; float speed; glm::mat4 translation; glm::mat4 rotation; glm::mat4 scale; // Model matrix to be sent as uniform to the vertex shader glm::mat4 model_mat; GLint u_model_idx; // View matrix to be sent as uniform to the vertex shader glm::mat4 view_mat; GLint u_view_idx; // Projection matrix to be sent as uniform to the vertex shader glm::mat4 projection_mat; GLint u_projection_idx; glm::vec2 current_mouse; glm::vec2 prev_mouse; bool running; }; int init (App &app); void create_vertex_spec (App &app); void create_graphics_pipeline(App &app); void run_main_loop (App &app); void cleanup (App &app); GLuint create_shader_program (const std::string &vertex_shader_source, const std::string &fragment_shader_source); GLuint compile_shader (GLuint type, const std::string &source); std::string load_shader (const std::string &filepath); void handle_object_movement (App &app); void handle_camera_movement (App &app); int main() { App app = {}; int result = init(app); if (result != EXIT_CODE_SUCCESS) { return result; } // Setup the geometry create_vertex_spec(app); // Setup graphics pipeline. At the very minimum creating vertex and fragment shaders create_graphics_pipeline(app); run_main_loop(app); cleanup(app); return EXIT_CODE_SUCCESS; } int init(App &app) { if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { fprintf(stderr, "Failed to initialise SDL: %s", SDL_GetError()); return EXIT_CODE_SDL_INIT_FAILED; } uint32_t flags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI; app.window = SDL_CreateWindow("Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, flags); if (!app.window) { fprintf(stderr, "Failed to create window: %s", SDL_GetError()); return EXIT_CODE_WINDOW_CREATION_FAILED; } SDL_WarpMouseInWindow(app.window, WINDOW_HALF_WIDTH, WINDOW_HALF_HEIGHT); SDL_SetRelativeMouseMode(SDL_TRUE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 6); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); app.context = SDL_GL_CreateContext(app.window); if (!app.context) { fprintf(stderr, "Failed to create OpenGL context: %s", SDL_GetError()); return EXIT_CODE_OPENGL_CONTEXT_FAILED; } // Init GLAD if (!gladLoadGLLoader(SDL_GL_GetProcAddress)) { fprintf(stderr, "Failed to initialise glad"); return EXIT_CODE_GLAD_LOADER_FAILED; } printf("%s\n%s\n%s\n%s\n", glGetString(GL_VENDOR), glGetString(GL_RENDERER), glGetString(GL_VERSION), glGetString(GL_SHADING_LANGUAGE_VERSION) ); app.speed = 0.02f; app.translation = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 1.5f)); app.rotation = glm::mat4(1.0f); app.scale = glm::mat4(1.0f); app.camera.position = glm::vec3(0.0f, 0.0f, 3.0f); app.camera.view_direction = glm::vec3(0.0f, 0.0f, -1.0f); app.camera.up = glm::vec3(0.0f, 1.0f, 0.0f); app.model_mat = app.translation * app.rotation * app.scale; app.view_mat = glm::lookAt(app.camera.position, app.camera.position + app.camera.view_direction, app.camera.up); app.projection_mat = glm::perspective(glm::radians(60.0f), (float)WINDOW_WIDTH / (float)WINDOW_HEIGHT, 0.1f, 20.0f ); app.current_mouse = glm::vec2(WINDOW_HALF_WIDTH, WINDOW_HALF_HEIGHT); app.prev_mouse = glm::vec2(WINDOW_HALF_WIDTH, WINDOW_HALF_HEIGHT); return EXIT_CODE_SUCCESS; } void create_vertex_spec(App &app) { const std::vector vertices = { // vert0 -0.5f, -0.5f, 0.0f, // position 1.0f, 0.0f, 0.0f, // colour // vert1 0.5f, -0.5f, 0.0f, // position 0.0f, 1.0f, 0.0f, // colour // vert2 -0.5f, 0.5f, 0.0f, // position 0.0f, 0.0f, 1.0f, // colour // vert3 0.5f, 0.5f, 0.0f, // position 1.0f, 1.0f, 0.0f, // colour }; const std::vector indices = { // first triangle 0, 1, 2, // second triangle 2, 1, 3, }; // Create and activate the VAO glGenVertexArrays(1, &app.vao); glBindVertexArray(app.vao); // Create and set up the VBO glGenBuffers(1, &app.vbo); glBindBuffer(GL_ARRAY_BUFFER, app.vbo); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GLfloat), (void *)vertices.data(), GL_STATIC_DRAW ); // Create and set up the EBO glGenBuffers(1, &app.ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, app.ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint), (void *)indices.data(), GL_STATIC_DRAW ); GLsizei vertex_stride = 6 * sizeof(GLfloat); GLvoid *position_start_offset = (void *)0; GLvoid *colour_start_offset = (void *)(3 * sizeof(GLfloat)); // Defines the vertex attributes and their indices. This defines the attribute for the currently // bound VAO glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); // Defines the number of components for the attribute, its type, the stride between the component // for each vertex and the offset of the first instance of the component in the buffer glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertex_stride, position_start_offset); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, vertex_stride, colour_start_offset); // Cleanup. // The order matters. Unbind VAO first then disable the attributes. If you do it the // other way around, the VAO will store that the attributes are disabled and nothing will be // drawn during the rendering stage glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); } void create_graphics_pipeline(App &app) { const std::string vs_source = load_shader("shaders/vert.glsl"); const std::string fs_source = load_shader("shaders/frag.glsl"); app.shader_program = create_shader_program(vs_source, fs_source); const char *u_model = "u_model"; app.u_model_idx = glGetUniformLocation(app.shader_program, u_model); if (app.u_model_idx < 0) { printf("Failed to find uniform %s\n", u_model); } const char *u_view = "u_view"; app.u_view_idx = glGetUniformLocation(app.shader_program, u_view); if (app.u_view_idx < 0) { printf("Failed to find uniform %s\n", u_view); } const char *u_projection = "u_projection"; app.u_projection_idx = glGetUniformLocation(app.shader_program, u_projection); if (app.u_projection_idx < 0) { printf("Failed to find uniform %s\n", u_projection); } } void run_main_loop(App &app) { app.running = true; while (app.running) { while (SDL_PollEvent(&app.event)) { switch (app.event.type) { case SDL_QUIT: app.running = false; break; case SDL_KEYDOWN: // handle_object_movement(app); handle_camera_movement(app); break; case SDL_MOUSEMOTION: handle_camera_movement(app); break; } } // Pre draw setup glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); glViewport (0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); glClearColor(0.36f, 0.34f, 0.42f, 1.0f); glClear (GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); glUseProgram(app.shader_program); if (app.u_model_idx >= 0) { glUniformMatrix4fv(app.u_model_idx, 1, GL_FALSE, glm::value_ptr(app.model_mat)); } if (app.u_view_idx >= 0) { glUniformMatrix4fv(app.u_view_idx, 1, GL_FALSE, glm::value_ptr(app.view_mat)); } if (app.u_projection_idx >= 0) { glUniformMatrix4fv(app.u_projection_idx, 1, GL_FALSE, glm::value_ptr(app.projection_mat)); } // End pre draw setup // Draw call glBindVertexArray(app.vao); glDrawElements (GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void *)0); // End draw call // Not necessary if we only have one shader program glUseProgram(0); SDL_GL_SwapWindow(app.window); } } void cleanup(App &app) { SDL_GL_DeleteContext(app.context); SDL_DestroyWindow(app.window); SDL_Quit(); } GLuint create_shader_program(const std::string &vertex_shader_source, const std::string &fragment_shader_source) { GLuint shader_program = glCreateProgram(); GLuint vertex_shader = compile_shader(GL_VERTEX_SHADER, vertex_shader_source); GLuint fragment_shader = compile_shader(GL_FRAGMENT_SHADER, fragment_shader_source); glAttachShader(shader_program, vertex_shader); glAttachShader(shader_program, fragment_shader); glLinkProgram (shader_program); GLint link_status; glGetProgramiv(shader_program, GL_LINK_STATUS, &link_status); if (link_status != GL_TRUE) { GLsizei log_length = 0; GLchar message[1024]; glGetProgramInfoLog(shader_program, 1024, &log_length, message); fprintf(stderr, "Failed to link program: %s\n", message); } // Validate the shader_program glValidateProgram(shader_program); GLint validation_status; glGetProgramiv(shader_program, GL_VALIDATE_STATUS, &validation_status); if (validation_status != GL_TRUE) { GLsizei log_length = 0; GLchar message[1024]; glGetProgramInfoLog(shader_program, 1024, &log_length, message); fprintf(stderr, "Failed to validate program: %s\n", message); } return shader_program; } GLuint compile_shader(GLuint type, const std::string &source) { GLuint shader = glCreateShader(type); const char *shader_source = source.c_str(); glShaderSource (shader, 1, &shader_source, NULL); glCompileShader(shader); GLint compile_status; glGetShaderiv(shader, GL_COMPILE_STATUS, &compile_status); if (compile_status != GL_TRUE) { GLsizei log_length = 0; GLchar message[1024]; glGetShaderInfoLog(shader, 1024, &log_length, message); fprintf(stderr, "Failed to compile shader: %s\n", message); } return shader; } std::string load_shader(const std::string &filepath) { FILE *fp = fopen(filepath.c_str(), "r"); if (!fp) { return ""; } std::string output = {}; char buf[1024] = {0}; while (fgets(buf, sizeof(buf), fp)) { output += buf; } return output; } void handle_object_movement(App &app) { switch (app.event.key.keysym.sym) { case SDLK_w: app.translation = glm::translate(app.translation, glm::vec3(0.0f, 0.0f, app.speed)); break; case SDLK_s: app.translation = glm::translate(app.translation, glm::vec3(0.0f, 0.0f, -app.speed)); break; case SDLK_d: app.translation = glm::translate(app.translation, glm::vec3(app.speed, 0.0f, 0.0f)); break; case SDLK_a: app.translation = glm::translate(app.translation, glm::vec3(-app.speed, 0.0f, 0.0f)); break; case SDLK_RIGHT: app.rotation = glm::rotate(app.rotation, glm::radians(10.0f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case SDLK_LEFT: app.rotation = glm::rotate(app.rotation, glm::radians(-10.0f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case SDLK_r: app.scale = glm::scale(app.scale, glm::vec3(1.2f)); break; case SDLK_e: app.scale = glm::scale(app.scale, glm::vec3(0.8f)); break; default: return; } app.model_mat = app.translation * app.rotation * app.scale; } void handle_camera_movement(App &app) { if (app.event.type == SDL_MOUSEMOTION) { glm::vec2 offset = glm::vec2(app.event.motion.xrel, 0) * 0.1f; app.prev_mouse += offset; app.camera.view_direction = glm::rotate(app.camera.view_direction, glm::radians(offset.x), app.camera.up); } else { switch (app.event.key.keysym.sym) { case SDLK_w: app.camera.position += app.camera.view_direction * app.speed; break; case SDLK_s: app.camera.position -= app.camera.view_direction * app.speed; break; case SDLK_d: app.camera.position += glm::normalize(glm::cross(app.camera.view_direction, app.camera.up)) * app.speed; break; case SDLK_a: app.camera.position -= glm::normalize(glm::cross(app.camera.view_direction, app.camera.up)) * app.speed; break; default: return; } } app.view_mat = glm::lookAt(app.camera.position, app.camera.position + app.camera.view_direction, app.camera.up); }