// 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, it gets added to the // position to calculate the target point the camera is looking at glm::vec3 view_direction; // The up vector in world coordinates glm::vec3 up; // pitch, yaw glm::vec2 rotation; // View matrix to be sent as uniform to the vertex shader glm::mat4 view_mat; // Projection matrix to be sent as uniform to the vertex shader glm::mat4 projection_mat; }; struct Transform { glm::mat4 translation; glm::mat4 rotation; glm::mat4 scale; }; struct Mesh { // 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; GLuint vertices_count; }; struct Model { Transform transform; Mesh mesh; // Model matrix to be sent as uniform to the vertex shader glm::mat4 model_mat; GLint u_model_idx; GLint u_view_idx; GLint u_projection_idx; }; struct App { SDL_Window *window; SDL_GLContext context; SDL_Event event; Model plane; Model floor; Camera camera; float speed; glm::vec2 prev_mouse; bool running; }; int init (App &app); void create_vertex_spec (Model &model, const std::vector &vertices, const std::vector &indices); void create_graphics_pipeline (Model &model, Camera &camera, const char *vs_file, const char *fs_file); void run_main_loop (App &app); void cleanup (App &app); void render_model (const Model &model, const Camera &camera); 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); glm::vec3 rotation_to_view_direction(const Camera &camera); int main() { App app = {}; int result = init(app); if (result != EXIT_CODE_SUCCESS) { return result; } const std::vector plane_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 plane_indices = { // first triangle 0, 1, 2, // second triangle 2, 1, 3, }; const std::vector floor_vertices = { // vert0 -10.0f, 0.0f, 10.0f, // position 0.23529f, 0.43137f, 0.44313f, // colour // vert1 10.0f, 0.0f, 10.0f, // position 0.23529f, 0.43137f, 0.44313f, // colour // vert2 -10.0f, 0.0f, -10.0f, // position 0.23529f, 0.43137f, 0.44313f, // colour // vert3 10.0f, 0.0f, -10.0f, // position 0.23529f, 0.43137f, 0.44313f, // colour }; const std::vector floor_indices = { // first triangle 0, 1, 2, // second triangle 2, 1, 3, }; // Setup the geometry create_vertex_spec(app.plane, plane_vertices, plane_indices); create_vertex_spec(app.floor, floor_vertices, floor_indices); // Setup graphics pipeline. At the very minimum creating vertex and fragment shaders create_graphics_pipeline(app.plane, app.camera, "shaders/vert.glsl", "shaders/frag.glsl"); create_graphics_pipeline(app.floor, app.camera, "shaders/vert.glsl", "shaders/frag.glsl"); 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.04f; app.plane.transform.translation = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.5f, -1.0f)); app.plane.transform.rotation = glm::mat4(1.0f); app.plane.transform.scale = glm::mat4(1.0f); app.plane.model_mat = app.plane.transform.translation * app.plane.transform.rotation * app.plane.transform.scale; app.floor.transform.translation = glm::mat4(1.0f); app.floor.transform.rotation = glm::mat4(1.0f); app.floor.transform.scale = glm::mat4(1.0f); app.floor.model_mat = app.floor.transform.translation * app.floor.transform.rotation * app.floor.transform.scale; app.camera.rotation = glm::vec2(-5.0f, -90.0f); app.camera.position = glm::vec3(0.0f, 0.5f, 3.0f); app.camera.view_direction = rotation_to_view_direction(app.camera); app.camera.up = glm::vec3(0.0f, 1.0f, 0.0f); app.camera.view_mat = glm::lookAt(app.camera.position, app.camera.position + app.camera.view_direction, app.camera.up); app.camera.projection_mat = glm::perspective(glm::radians(60.0f), (float)WINDOW_WIDTH / (float)WINDOW_HEIGHT, 0.1f, 20.0f); app.prev_mouse = glm::vec2(WINDOW_HALF_WIDTH, WINDOW_HALF_HEIGHT); return EXIT_CODE_SUCCESS; } void create_vertex_spec(Model &model, const std::vector &vertices, const std::vector &indices) { // Create and activate the VAO glGenVertexArrays(1, &model.mesh.vao); glBindVertexArray(model.mesh.vao); // Create and set up the VBO glGenBuffers(1, &model.mesh.vbo); glBindBuffer(GL_ARRAY_BUFFER, model.mesh.vbo); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(GLfloat), (void *)vertices.data(), GL_STATIC_DRAW ); // Create and set up the EBO glGenBuffers(1, &model.mesh.ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model.mesh.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); model.mesh.vertices_count = indices.size(); } void create_graphics_pipeline(Model &model, Camera &camera, const char *vs_file, const char *fs_file) { const std::string vs_source = load_shader(vs_file); const std::string fs_source = load_shader(fs_file); model.mesh.shader_program = create_shader_program(vs_source, fs_source); const char *u_model = "u_model"; model.u_model_idx = glGetUniformLocation(model.mesh.shader_program, u_model); if (model.u_model_idx < 0) { printf("Failed to find uniform %s\n", u_model); } const char *u_view = "u_view"; model.u_view_idx = glGetUniformLocation(model.mesh.shader_program, u_view); if (model.u_view_idx < 0) { printf("Failed to find uniform %s\n", u_view); } const char *u_projection = "u_projection"; model.u_projection_idx = glGetUniformLocation(model.mesh.shader_program, u_projection); if (model.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: if (app.event.key.keysym.sym == SDLK_ESCAPE) { app.running = false; } else { 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); // Render models render_model(app.floor, app.camera); render_model(app.plane, app.camera); // 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(); } void render_model(const Model &model, const Camera &camera) { glUseProgram(model.mesh.shader_program); if (model.u_model_idx >= 0) { glUniformMatrix4fv(model.u_model_idx, 1, GL_FALSE, glm::value_ptr(model.model_mat)); } if (model.u_view_idx >= 0) { glUniformMatrix4fv(model.u_view_idx, 1, GL_FALSE, glm::value_ptr(camera.view_mat)); } if (model.u_projection_idx >= 0) { glUniformMatrix4fv(model.u_projection_idx, 1, GL_FALSE, glm::value_ptr(camera.projection_mat)); } glBindVertexArray(model.mesh.vao); glDrawElements (GL_TRIANGLES, model.mesh.vertices_count, GL_UNSIGNED_INT, (void *)0); } 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; } // Example of moving an object void handle_object_movement(App &app) { switch (app.event.key.keysym.sym) { case SDLK_w: app.plane.transform.translation = glm::translate(app.plane.transform.translation, glm::vec3(0.0f, 0.0f, app.speed)); break; case SDLK_s: app.plane.transform.translation = glm::translate(app.plane.transform.translation, glm::vec3(0.0f, 0.0f, -app.speed)); break; case SDLK_d: app.plane.transform.translation = glm::translate(app.plane.transform.translation, glm::vec3(app.speed, 0.0f, 0.0f)); break; case SDLK_a: app.plane.transform.translation = glm::translate(app.plane.transform.translation, glm::vec3(-app.speed, 0.0f, 0.0f)); break; case SDLK_RIGHT: app.plane.transform.rotation = glm::rotate(app.plane.transform.rotation, glm::radians(10.0f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case SDLK_LEFT: app.plane.transform.rotation = glm::rotate(app.plane.transform.rotation, glm::radians(-10.0f), glm::vec3(0.0f, 1.0f, 0.0f)); break; case SDLK_r: app.plane.transform.scale = glm::scale(app.plane.transform.scale, glm::vec3(1.2f)); break; case SDLK_e: app.plane.transform.scale = glm::scale(app.plane.transform.scale, glm::vec3(0.8f)); break; default: return; } app.plane.model_mat = app.plane.transform.translation * app.plane.transform.rotation * app.plane.transform.scale; app.floor.model_mat = app.floor.transform.translation * app.floor.transform.rotation * app.floor.transform.scale; } void handle_camera_movement(App &app) { if (app.event.type == SDL_MOUSEMOTION) { glm::vec2 offset = glm::vec2(-app.event.motion.yrel, app.event.motion.xrel) * 0.1f; app.prev_mouse += offset; app.camera.rotation += offset; app.camera.rotation.x = glm::clamp(app.camera.rotation.x, -89.0f, 89.0f); app.camera.view_direction = rotation_to_view_direction(app.camera); } 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.camera.view_mat = glm::lookAt(app.camera.position, app.camera.position + app.camera.view_direction, app.camera.up); } glm::vec3 rotation_to_view_direction(const Camera &camera) { glm::vec3 direction; direction.x = cos(glm::radians(camera.rotation.y)) * cos(glm::radians(camera.rotation.x)); direction.y = sin(glm::radians(camera.rotation.x)); direction.z = sin(glm::radians(camera.rotation.y)) * cos(glm::radians(camera.rotation.x)); return glm::normalize(direction); }