// GLAD #include "glad/glad.h" // STB Image #define STB_IMAGE_IMPLEMENTATION #include "stb_image.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" // Assimp #include "assimp/Importer.hpp" #include "assimp/postprocess.h" #include "assimp/scene.h" #include "assimp/mesh.h" #include "assimp/material.h" #include "assimp/types.h" // SDL #include #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 #define min(a, b) (a < b ? a : b) #define max(a, b) (a > b ? a : b) #define clamp(v, a, b) (min(max(v, a), b)) #define WIREFRAME 0 #define NORMALS 0 #define MSAA 1 #define POINT_LIGHTS 1 #if MSAA #define MSAA_SAMPLE_COUNT 4 #endif 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, EXIT_CODE_INCOMPLETE_FRAME_BUFFER, }; class Shader { public: Shader(const std::string &vert_file, const std::string &frag_file, const std::string &geo_file = ""); ~Shader(); void activate(); void set_int(const char *name, int value); void set_float(const char *name, float value); void set_vec3(const char *name, glm::vec3 vector); void set_vec4(const char *name, glm::vec4 vector); void set_mat3(const char *name, glm::mat3 matrix); void set_mat4(const char *name, glm::mat4 matrix); void set_uniform_block_binding_point(const char *block_name, GLuint binding_point); GLuint program; private: void link_program(GLuint vert, GLuint frag, GLuint geo); GLuint load_and_compile_shader(const std::string &filepath, GLenum shader_type); std::string load_shader_from_file(const std::string &filepath); static const char *get_shader_type_string(GLenum shader_type); }; enum TextureType : unsigned char { TEXTURE_TYPE_DIFFUSE, TEXTURE_TYPE_SPECULAR, }; class Texture2D { public: Texture2D(const char *filename, GLint texture_unit, TextureType type); ~Texture2D(); void activate() const; std::string name(unsigned int index) const; int width; int height; int channels; const char *filename; GLint texture_unit; TextureType type; private: GLuint texture; GLint format; }; struct Vertex { glm::vec3 position; glm::vec3 normal; glm::vec2 tex_coord; }; class Mesh { public: std::vector vertices; std::vector indices; std::vector textures; Mesh(std::vector vertices, std::vector indices, std::vector textures); void activate(); void deactivate(); void draw(Shader &Shader); void draw_instanced(Shader &Shader, unsigned int count = 1); private: GLuint vao, vbo, ebo; void setup_mesh(); void set_mesh_textures_for_drawing(Shader &shader); }; class Model { public: Model(const char *path) { load_model(path); } void draw(Shader &shader); private: std::vector loaded_textures; std::vector meshes; std::string directory; GLint texture_unit = GL_TEXTURE0; void load_model(std::string path); void process_node(aiNode *node, const aiScene *scene); Mesh process_mesh(aiMesh *mesh, const aiScene *scene); std::vector load_material_textures(aiMaterial *mat, aiTextureType type); }; struct FrameBuffer { GLuint fbo; GLuint color; GLuint depth_stencil; GLuint width; GLuint height; }; FrameBuffer create_normal_frame_buffer(unsigned int width, unsigned int height); #if MSAA FrameBuffer create_multisample_frame_buffer(unsigned int width, unsigned int height); void blit_multisample_frame_buffer(const FrameBuffer &multisample_buffer, const FrameBuffer &normal_buffer); #endif void delete_frame_buffer(FrameBuffer &buffer); GLuint load_cubemap(std::vector textures); int main() { if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { return EXIT_CODE_SDL_INIT_FAILED; } SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); #if MSAA SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, MSAA_SAMPLE_COUNT); #endif SDL_Window *window = SDL_CreateWindow("Window", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); if (!window) { return EXIT_CODE_WINDOW_CREATION_FAILED; } SDL_GLContext context = SDL_GL_CreateContext(window); if (!context) { return EXIT_CODE_OPENGL_CONTEXT_FAILED; } if (gladLoadGLLoader(SDL_GL_GetProcAddress) == 0) { return EXIT_CODE_GLAD_LOADER_FAILED; } #if MSAA glEnable(GL_MULTISAMPLE); #endif SDL_SetRelativeMouseMode(SDL_TRUE); SDL_WarpMouseInWindow(window, WINDOW_HALF_WIDTH, WINDOW_HALF_HEIGHT); glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); #if MSAA FrameBuffer multisample_buffer = create_multisample_frame_buffer(WINDOW_WIDTH, WINDOW_HEIGHT); FrameBuffer offscreen_buffer = create_normal_frame_buffer(WINDOW_WIDTH, WINDOW_HEIGHT); #else FrameBuffer offscreen_buffer = create_normal_frame_buffer(WINDOW_WIDTH, WINDOW_HEIGHT); #endif std::vector vertices = { // positions // normals // texture coords Vertex{glm::vec3(-0.5f, -0.5f, -0.5f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec2(0.0f, 1.0f)}, Vertex{glm::vec3( 0.5f, 0.5f, 0.5f), glm::vec3( 1.0f, 0.0f, 0.0f), glm::vec2(1.0f, 0.0f)}, Vertex{glm::vec3( 0.5f, 0.5f, -0.5f), glm::vec3( 1.0f, 0.0f, 0.0f), glm::vec2(1.0f, 1.0f)}, Vertex{glm::vec3(-0.5f, 0.5f, 0.5f), glm::vec3( 0.0f, 0.0f, 1.0f), glm::vec2(0.0f, 1.0f)}, Vertex{glm::vec3( 0.5f, -0.5f, 0.5f), glm::vec3( 1.0f, 0.0f, 0.0f), glm::vec2(0.0f, 0.0f)}, Vertex{glm::vec3( 0.5f, -0.5f, -0.5f), glm::vec3( 0.0f, -1.0f, 0.0f), glm::vec2(1.0f, 1.0f)}, Vertex{glm::vec3(-0.5f, -0.5f, -0.5f), glm::vec3( 0.0f, 0.0f, -1.0f), glm::vec2(0.0f, 0.0f)}, Vertex{glm::vec3( 0.5f, -0.5f, 0.5f), glm::vec3( 0.0f, 0.0f, 1.0f), glm::vec2(1.0f, 0.0f)}, Vertex{glm::vec3(-0.5f, -0.5f, -0.5f), glm::vec3( 0.0f, -1.0f, 0.0f), glm::vec2(0.0f, 1.0f)}, Vertex{glm::vec3( 0.5f, 0.5f, 0.5f), glm::vec3( 0.0f, 0.0f, 1.0f), glm::vec2(1.0f, 1.0f)}, Vertex{glm::vec3( 0.5f, -0.5f, 0.5f), glm::vec3( 0.0f, -1.0f, 0.0f), glm::vec2(1.0f, 0.0f)}, Vertex{glm::vec3(-0.5f, -0.5f, 0.5f), glm::vec3( 0.0f, -1.0f, 0.0f), glm::vec2(0.0f, 0.0f)}, Vertex{glm::vec3( 0.5f, 0.5f, -0.5f), glm::vec3( 0.0f, 0.0f, -1.0f), glm::vec2(1.0f, 1.0f)}, Vertex{glm::vec3( 0.5f, 0.5f, 0.5f), glm::vec3( 0.0f, 1.0f, 0.0f), glm::vec2(1.0f, 0.0f)}, Vertex{glm::vec3( 0.5f, -0.5f, -0.5f), glm::vec3( 0.0f, 0.0f, -1.0f), glm::vec2(1.0f, 0.0f)}, Vertex{glm::vec3(-0.5f, 0.5f, 0.5f), glm::vec3( 0.0f, 1.0f, 0.0f), glm::vec2(0.0f, 0.0f)}, Vertex{glm::vec3(-0.5f, 0.5f, -0.5f), glm::vec3( 0.0f, 1.0f, 0.0f), glm::vec2(0.0f, 1.0f)}, Vertex{glm::vec3(-0.5f, -0.5f, 0.5f), glm::vec3( 0.0f, 0.0f, 1.0f), glm::vec2(0.0f, 0.0f)}, Vertex{glm::vec3(-0.5f, -0.5f, 0.5f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec2(0.0f, 0.0f)}, Vertex{glm::vec3(-0.5f, 0.5f, -0.5f), glm::vec3( 0.0f, 0.0f, -1.0f), glm::vec2(0.0f, 1.0f)}, Vertex{glm::vec3(-0.5f, 0.5f, -0.5f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec2(1.0f, 1.0f)}, Vertex{glm::vec3( 0.5f, 0.5f, -0.5f), glm::vec3( 0.0f, 1.0f, 0.0f), glm::vec2(1.0f, 1.0f)}, Vertex{glm::vec3(-0.5f, 0.5f, 0.5f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec2(1.0f, 0.0f)}, Vertex{glm::vec3( 0.5f, -0.5f, -0.5f), glm::vec3( 1.0f, 0.0f, 0.0f), glm::vec2(0.0f, 1.0f)}, }; std::vector indices = { 6, 14, 12, 12, 19, 6, 17, 7, 9, 9, 3, 17, 22, 20, 0, 0, 18, 22, 1, 2, 23, 23, 4, 1, 8, 5, 10, 10, 11, 8, 16, 21, 13, 13, 15, 16 }; Model suzanne = {"models/suzanne/suzanne.obj"}; Mesh light = {vertices, indices, {}}; std::vector skybox_vertices = { Vertex{glm::vec3(-1.0f, 1.0f, -1.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(1.0f, 1.0f)}, Vertex{glm::vec3(-1.0f, -1.0f, -1.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(1.0f, 1.0f)}, Vertex{glm::vec3( 1.0f, -1.0f, -1.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(1.0f, 1.0f)}, Vertex{glm::vec3( 1.0f, 1.0f, -1.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(1.0f, 1.0f)}, Vertex{glm::vec3(-1.0f, -1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(1.0f, 1.0f)}, Vertex{glm::vec3(-1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(1.0f, 1.0f)}, Vertex{glm::vec3( 1.0f, -1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(1.0f, 1.0f)}, Vertex{glm::vec3( 1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(1.0f, 1.0f)}, }; std::vector skybox_indices = { 0, 1, 2, 2, 3, 0, 4, 1, 0, 0, 5, 4, 2, 6, 7, 7, 3, 2, 4, 5, 7, 7, 6, 4, 0, 3, 7, 7, 5, 0, 1, 4, 2, 2, 4, 6, }; Mesh skybox = {skybox_vertices, skybox_indices, {}}; std::vector screen_vertices = { Vertex{glm::vec3(-1.0f, -1.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(0.0f, 0.0f)}, Vertex{glm::vec3( 1.0f, -1.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(1.0f, 0.0f)}, Vertex{glm::vec3(-1.0f, 1.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(0.0f, 1.0f)}, Vertex{glm::vec3( 1.0f, 1.0f, 0.0f), glm::vec3(1.0f, 1.0f, 1.0f), glm::vec2(1.0f, 1.0f)}, }; std::vector screen_indices = { 0, 1, 2, 2, 1, 3, }; Mesh screen = {screen_vertices, screen_indices, {}}; Shader main_shader {"shaders/vert.glsl", "shaders/frag.glsl"}; Shader skybox_shader {"shaders/sb_vert.glsl", "shaders/sb_frag.glsl"}; Shader reflective_shader {"shaders/vert.glsl", "shaders/reflective_frag.glsl"}; Shader refractive_shader {"shaders/vert.glsl", "shaders/refractive_frag.glsl"}; Shader post_processing {"shaders/pp_vert.glsl", "shaders/pp_frag.glsl"}; #if NORMALS Shader normal_vis_shader {"shaders/normal_vert.glsl", "shaders/normal_frag.glsl", "shaders/normal_geo.glsl"}; #endif #if POINT_LIGHTS Shader light_shader {"shaders/light_vert.glsl", "shaders/light_frag.glsl"}; std::vector light_shader_model_mats; #endif const float camera_speed = 25.0f; glm::vec3 camera_position = glm::vec3(-2.5f, 0.0f, 8.0f); glm::vec3 camera_forward = glm::vec3(0.0f); glm::vec3 world_up = glm::vec3(0.0f, 1.0f, 0.0f); glm::vec3 light_ambient = glm::vec3(0.2f, 0.2f, 0.2f); glm::vec3 light_diffuse = glm::vec3(0.75f, 0.75f, 0.75f); glm::vec3 light_specular = glm::vec3(1.0f, 1.0f, 1.0f); float yaw = -75.0f; float pitch = 0.0f; glm::mat4 model = glm::mat4(1.0f); glm::mat4 view = glm::mat4(1.0f); glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)WINDOW_WIDTH / (float)WINDOW_HEIGHT, 0.1f, 100.0f); glm::mat3 normal_mat = glm::mat3(1.0f); std::vector point_light_positions = { glm::vec3( 0.7f, 0.2f, 2.0f), glm::vec3( 2.3f, -3.3f, -4.0f), glm::vec3(-4.0f, 2.0f, -12.0f), glm::vec3( 1.0f, 0.0f, -18.0f) }; // Setup material main_shader.set_float("material.shininess", 32.0f); // Setup lights main_shader.set_vec3("directional_light.direction", glm::vec3(-0.2f, -1.0f, -0.3f)); main_shader.set_vec3("directional_light.ambient", light_ambient); main_shader.set_vec3("directional_light.diffuse", light_diffuse * 0.25f); main_shader.set_vec3("directional_light.specular", light_specular); main_shader.set_vec3("spot_light.ambient", light_ambient); main_shader.set_vec3("spot_light.diffuse", light_diffuse * 0.5f); main_shader.set_vec3("spot_light.specular", light_specular * 0.25f); for (int i = 0; i < point_light_positions.size(); ++i) { char base[256] = {0}; char position[512] = {0}; char ambient[512] = {0}; char diffuse[512] = {0}; char specular[512] = {0}; char constant[512] = {0}; char linear[512] = {0}; char quadratic[512] = {0}; snprintf(base, sizeof(base) - 1, "point_lights[%d]", i); snprintf(position, sizeof(position) - 1, "%s.position", base); snprintf(ambient, sizeof(ambient) - 1, "%s.ambient", base); snprintf(diffuse, sizeof(diffuse) - 1, "%s.diffuse", base); snprintf(specular, sizeof(specular) - 1, "%s.specular", base); snprintf(constant, sizeof(constant) - 1, "%s.constant", base); snprintf(linear, sizeof(linear) - 1, "%s.linear", base); snprintf(quadratic, sizeof(quadratic) - 1, "%s.quadratic", base); main_shader.set_vec3(position, point_light_positions[i]); main_shader.set_vec3(ambient, light_ambient); main_shader.set_vec3(diffuse, light_diffuse * 0.25f); main_shader.set_vec3(specular, light_specular * 0.5f); main_shader.set_float(constant, 1.0f); main_shader.set_float(linear, 0.09f); main_shader.set_float(quadratic, 0.032f); memset(base, 0, sizeof(base)); memset(position, 0, sizeof(position)); memset(ambient, 0, sizeof(ambient)); memset(diffuse, 0, sizeof(diffuse)); memset(specular, 0, sizeof(specular)); memset(constant, 0, sizeof(constant)); memset(linear, 0, sizeof(linear)); memset(quadratic, 0, sizeof(quadratic)); #if POINT_LIGHTS glm::mat4 light_shader_model_mat = glm::translate(glm::mat4(1.0f), point_light_positions[i]); light_shader_model_mat = glm::scale(light_shader_model_mat, glm::vec3(0.2f)); light_shader_model_mats.push_back(light_shader_model_mat); #endif } #if POINT_LIGHTS std::size_t vec4_size = sizeof(glm::vec4); light.activate(); GLuint instanceVBO; glGenBuffers(1, &instanceVBO); glBindBuffer(GL_ARRAY_BUFFER, instanceVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(glm::mat4) * light_shader_model_mats.size(), glm::value_ptr(light_shader_model_mats[0]), GL_STATIC_DRAW); // Set up attribute pointers for each column of the matrix for (unsigned int i = 3; i < 7; ++i) { glVertexAttribPointer(i, 4, GL_FLOAT, GL_FALSE, 4 * vec4_size, (void *)((i - 3) * vec4_size)); glEnableVertexAttribArray(i); glVertexAttribDivisor(i, 1); } light.deactivate(); glBindBuffer(GL_ARRAY_BUFFER, 0); #endif // Load cube map std::vector cube_map_textures = { "images/skybox/right.jpg", "images/skybox/left.jpg", "images/skybox/top.jpg", "images/skybox/bottom.jpg", "images/skybox/front.jpg", "images/skybox/back.jpg", }; GLuint cubemap = load_cubemap(cube_map_textures); // Generate and bind uniform buffer GLuint ubo; GLuint ubo_binding_point = 0; glGenBuffers(1, &ubo); glBindBuffer(GL_UNIFORM_BUFFER, ubo); glBufferData(GL_UNIFORM_BUFFER, 144, NULL, GL_STATIC_DRAW); // Bind the uniform buffer object to a binding point // Can also be done using glBindBufferRange instead of glBindBufferBase glBindBufferBase(GL_UNIFORM_BUFFER, ubo_binding_point, ubo); // Set the shaders binding points main_shader.set_uniform_block_binding_point ("Common", ubo_binding_point); skybox_shader.set_uniform_block_binding_point ("Common", ubo_binding_point); reflective_shader.set_uniform_block_binding_point("Common", ubo_binding_point); refractive_shader.set_uniform_block_binding_point("Common", ubo_binding_point); #if NORMALS normal_vis_shader.set_uniform_block_binding_point("Common", ubo_binding_point); #endif #if POINT_LIGHTS light_shader.set_uniform_block_binding_point ("Common", ubo_binding_point); #endif // Add projection matrix to uniform buffer glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(projection)); glBindBuffer(GL_UNIFORM_BUFFER, 0); const float sensitivity = 0.1f; int last_mouse_x = WINDOW_HALF_WIDTH; int last_mouse_y = WINDOW_HALF_HEIGHT; uint32_t last_frame = SDL_GetTicks(); float delta = 0.0f; bool running = true; SDL_Event event = {}; while (running) { uint32_t ticks = SDL_GetTicks(); delta = (ticks - last_frame) * 0.001f; last_frame = ticks; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: running = false; break; case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_ESCAPE) { running = false; } else if (event.key.keysym.sym == SDLK_w) { camera_position += camera_speed * delta * camera_forward; } else if (event.key.keysym.sym == SDLK_s) { camera_position -= camera_speed * delta * camera_forward; } else if (event.key.keysym.sym == SDLK_d) { camera_position += camera_speed * delta * glm::normalize(glm::cross(camera_forward, world_up)); } else if (event.key.keysym.sym == SDLK_a) { camera_position -= camera_speed * delta * glm::normalize(glm::cross(camera_forward, world_up)); } break; case SDL_MOUSEMOTION: { float x_offset = event.motion.xrel; float y_offset = -event.motion.yrel; last_mouse_x = last_mouse_x + event.motion.xrel; last_mouse_y = last_mouse_y + event.motion.yrel; x_offset *= sensitivity; y_offset *= sensitivity; yaw += x_offset; pitch += y_offset; if(pitch > 89.0f) { pitch = 89.0f; } if(pitch < -89.0f) { pitch = -89.0f; } } break; case SDL_WINDOWEVENT: if (event.window.event == SDL_WINDOWEVENT_RESIZED) { SDL_Window *wnd = SDL_GetWindowFromID(event.window.windowID); if (!wnd) { continue; } int w, h; SDL_GL_GetDrawableSize(wnd, &w, &h); glViewport(0, 0, w, h); SDL_WarpMouseInWindow(wnd, (int)(w * 0.5f), (int)(h * 0.5f)); // Recreate offscreen frame buffer delete_frame_buffer(offscreen_buffer); offscreen_buffer = create_normal_frame_buffer(w, h); #if MSAA delete_frame_buffer(multisample_buffer); multisample_buffer = create_multisample_frame_buffer(w, h); #endif } break; } } camera_forward.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch)); camera_forward.y = sin(glm::radians(pitch)); camera_forward.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch)); camera_forward = glm::normalize(camera_forward); view = glm::lookAt(camera_position, camera_position + camera_forward, world_up); // Add view matrix and camera_position to uniform buffer glBindBuffer(GL_UNIFORM_BUFFER, ubo); glBufferSubData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), sizeof(glm::mat4), glm::value_ptr(view)); glBufferSubData(GL_UNIFORM_BUFFER, 2 * sizeof(glm::mat4), sizeof(glm::vec3), glm::value_ptr(camera_position)); glBindBuffer(GL_UNIFORM_BUFFER, 0); main_shader.set_vec3("spot_light.position", camera_position); main_shader.set_vec3("spot_light.direction", camera_forward); main_shader.set_float("spot_light.cutoff", glm::cos(glm::radians(12.5))); main_shader.set_float("spot_light.outer_cutoff", glm::cos(glm::radians(17.5))); skybox_shader.set_mat4("sb_view", glm::mat4(glm::mat3(view))); // Main render pass #if MSAA glBindFramebuffer(GL_FRAMEBUFFER, multisample_buffer.fbo); #else glBindFramebuffer(GL_FRAMEBUFFER, offscreen_buffer.fbo); #endif glClearColor(0.04f, 0.08f, 0.08f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glEnable(GL_CULL_FACE); model = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.0f)); normal_mat = glm::transpose(glm::inverse(model)); main_shader.activate(); main_shader.set_mat4("model", model); main_shader.set_mat3("normal_mat", normal_mat); suzanne.draw(main_shader); #if NORMALS normal_mat = glm::transpose(glm::inverse(view * model)); normal_vis_shader.activate(); normal_vis_shader.set_mat4("model", model); normal_vis_shader.set_mat3("normal_mat", normal_mat); suzanne.draw(normal_vis_shader); #endif model = glm::translate(glm::mat4(1.0f), glm::vec3(3.0f, 0.0f, 0.0f)); normal_mat = glm::transpose(glm::inverse(model)); reflective_shader.set_mat4("model", model); reflective_shader.set_mat3("normal_mat", normal_mat); suzanne.draw(reflective_shader); glDisable(GL_CULL_FACE); model = glm::translate(glm::mat4(1.0f), glm::vec3(-3.0f, 0.0f, 0.0f)); normal_mat = glm::transpose(glm::inverse(model)); refractive_shader.set_mat4("model", model); refractive_shader.set_mat3("normal_mat", normal_mat); suzanne.draw(refractive_shader); #if POINT_LIGHTS // Draw point lights light_shader.set_vec3("light_diffuse", light_diffuse); light.draw_instanced(light_shader, point_light_positions.size()); #endif // Draw skybox skybox_shader.activate(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap); skybox_shader.set_int("cubemap", 0); skybox.draw(skybox_shader); #if WIREFRAME // wireframe mode glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); #endif #if MSAA blit_multisample_frame_buffer(multisample_buffer, offscreen_buffer); #endif // Post processing pass glBindFramebuffer(GL_FRAMEBUFFER, 0); glClearColor(1.0f, 1.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); post_processing.activate(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, offscreen_buffer.color); post_processing.set_int("image_texture", 0); screen.draw(post_processing); SDL_GL_SwapWindow(window); } SDL_GL_DeleteContext(context); SDL_DestroyWindow(window); SDL_Quit(); return EXIT_CODE_SUCCESS; } Shader::Shader(const std::string &vert_file, const std::string &frag_file, const std::string &geo_file) { GLuint vert = load_and_compile_shader(vert_file, GL_VERTEX_SHADER); GLuint frag = load_and_compile_shader(frag_file, GL_FRAGMENT_SHADER); GLuint geo = 0; if (geo_file.size() > 0) { geo = load_and_compile_shader(geo_file, GL_GEOMETRY_SHADER); } link_program(vert, frag, geo); glDeleteShader(vert); glDeleteShader(frag); if (geo > 0) { glDeleteShader(geo); } } Shader::~Shader() { if (program > 0) { glDeleteProgram(program); } } void Shader::activate() { if (program > 0) { glUseProgram(program); } } void Shader::set_int(const char *name, int value) { activate(); glUniform1i(glGetUniformLocation(program, name), value); } void Shader::set_float(const char *name, float value) { activate(); glUniform1f(glGetUniformLocation(program, name), value); } void Shader::set_vec3(const char *name, glm::vec3 vector) { activate(); glUniform3f(glGetUniformLocation(program, name), vector.x, vector.y, vector.z); } void Shader::set_vec4(const char *name, glm::vec4 vector) { activate(); glUniform4f(glGetUniformLocation(program, name), vector.x, vector.y, vector.z, vector.w); } void Shader::set_mat3(const char *name, glm::mat3 matrix) { activate(); glUniformMatrix3fv(glGetUniformLocation(program, name), 1, GL_FALSE, glm::value_ptr(matrix)); } void Shader::set_mat4(const char *name, glm::mat4 matrix) { activate(); glUniformMatrix4fv(glGetUniformLocation(program, name), 1, GL_FALSE, glm::value_ptr(matrix)); } void Shader::set_uniform_block_binding_point(const char *block_name, GLuint binding_point) { GLuint block_index = glGetUniformBlockIndex(program, block_name); glUniformBlockBinding(program, block_index, binding_point); } void Shader::link_program(GLuint vert, GLuint frag, GLuint geo) { program = glCreateProgram(); glAttachShader(program, vert); glAttachShader(program, frag); if (geo > 0) { glAttachShader(program, geo); } glLinkProgram(program); GLint program_linked; glGetProgramiv(program, GL_LINK_STATUS, &program_linked); if (program_linked != GL_TRUE) { GLsizei log_length = 0; GLchar message[1024]; glGetProgramInfoLog(program, 1024, &log_length, message); printf("Failed to link program: %s\n", message); program = 0; } } GLuint Shader::load_and_compile_shader(const std::string &filepath, GLenum shader_type) { std::string src = load_shader_from_file(filepath); const char *shader_src = src.c_str(); GLuint shader = glCreateShader(shader_type); glShaderSource(shader, 1, &shader_src, NULL); glCompileShader(shader); GLint shader_compiled; glGetShaderiv(shader, GL_COMPILE_STATUS, &shader_compiled); if (shader_compiled != GL_TRUE) { GLsizei log_length = 0; GLchar message[1024]; glGetShaderInfoLog(shader, 1024, &log_length, message); printf("Failed to compile %s shader: %s\n", get_shader_type_string(shader_type), message); return 0; } return shader; } std::string Shader::load_shader_from_file(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; } const char *Shader::get_shader_type_string(GLenum shader_type) { const char *output; switch (shader_type) { case GL_COMPUTE_SHADER: output = "compute"; break; case GL_VERTEX_SHADER: output = "vertex"; break; case GL_TESS_CONTROL_SHADER: output = "tess_control"; break; case GL_TESS_EVALUATION_SHADER: output = "tess_evaluation"; break; case GL_GEOMETRY_SHADER: output = "geometry"; break; case GL_FRAGMENT_SHADER: output = "fragment"; break; default: output = "UNKNOWN"; break; } return output; } Texture2D::Texture2D(const char *filename, GLint texture_unit, TextureType type) : filename(filename), texture_unit(texture_unit), type(type) { uint8_t *image = stbi_load(filename, &width, &height, &channels, 0); if (!image) { return; } // TODO (Abdelrahman): This doesn't handle all formats format = channels > 3 ? GL_RGBA : GL_RGB; glGenTextures(1, &texture); activate(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, image); glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); stbi_image_free(image); } Texture2D::~Texture2D() {} void Texture2D::activate() const { glActiveTexture(texture_unit); glBindTexture(GL_TEXTURE_2D, texture); } std::string Texture2D::name(unsigned int index) const { std::string output = "material."; switch (type) { case TEXTURE_TYPE_DIFFUSE: output += "diffuse"; break; case TEXTURE_TYPE_SPECULAR: output += "specular"; break; } output += std::to_string(index); return output.c_str(); } Mesh::Mesh(std::vector vertices, std::vector indices, std::vector textures) : vertices(vertices), indices(indices), textures(textures) { setup_mesh(); } void Mesh::setup_mesh() { glGenVertexArrays(1, &vao); glGenBuffers(1, &ebo); glGenBuffers(1, &vbo); glBindVertexArray(vao); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint), indices.data(), GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(), GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(offsetof(Vertex, normal))); glEnableVertexAttribArray(1); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *)(offsetof(Vertex, tex_coord))); glEnableVertexAttribArray(2); } void Mesh::set_mesh_textures_for_drawing(Shader &shader) { unsigned int diffuse = 1; unsigned int specular = 1; unsigned int index; for (int i = 0; i < textures.size(); ++i) { const Texture2D &texture = textures[i]; index = texture.type == TEXTURE_TYPE_DIFFUSE ? diffuse++ : specular++; shader.set_int(texture.name(index).c_str(), texture.texture_unit - GL_TEXTURE0); texture.activate(); } } void Mesh::activate() { glBindVertexArray(vao); } void Mesh::deactivate() { glBindVertexArray(0); } void Mesh::draw(Shader &shader) { set_mesh_textures_for_drawing(shader); activate(); glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, (void *)0); deactivate(); } void Mesh::draw_instanced(Shader &shader, unsigned int count) { set_mesh_textures_for_drawing(shader); glBindVertexArray(vao); glDrawElementsInstanced(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, (void *)0, count); glBindVertexArray(0); } void Model::draw(Shader &shader) { shader.activate(); for (int i = 0; i < meshes.size(); ++i) { meshes[i].draw(shader); } } void Model::load_model(std::string path) { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs); if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) { printf("Failed to load model: %s. Error: %s\n", path.c_str(), importer.GetErrorString()); return; } directory = path.substr(0, path.find_last_of('/')); process_node(scene->mRootNode, scene); } void Model::process_node(aiNode *node, const aiScene *scene) { for (unsigned int i = 0; i < node->mNumMeshes; ++i) { aiMesh *mesh = scene->mMeshes[node->mMeshes[i]]; meshes.push_back(process_mesh(mesh, scene)); } for (unsigned int i = 0; i < node->mNumChildren; ++i) { process_node(node->mChildren[i], scene); } } Mesh Model::process_mesh(aiMesh *mesh, const aiScene *scene) { std::vector vertices; std::vector indices; std::vector textures; for (unsigned int i = 0; i < mesh->mNumVertices; ++i) { Vertex vertex; vertex.position = glm::vec3(mesh->mVertices[i].x, mesh->mVertices[i].y, mesh->mVertices[i].z); vertex.normal = glm::vec3(mesh->mNormals[i].x, mesh->mNormals[i].y, mesh->mNormals[i].z); if (mesh->mTextureCoords[0]) { vertex.tex_coord = glm::vec2(mesh->mTextureCoords[0][i].x, mesh->mTextureCoords[0][i].y); } else { vertex.tex_coord = glm::vec2(0.0f, 0.0f); } vertices.push_back(vertex); } for (unsigned int i = 0; i < mesh->mNumFaces; ++i) { aiFace face = mesh->mFaces[i]; for (unsigned int j = 0; j < face.mNumIndices; ++j) { indices.push_back(face.mIndices[j]); } } if (mesh->mMaterialIndex >= 0) { aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex]; std::vector diffuse_maps = load_material_textures(material, aiTextureType_DIFFUSE); textures.insert(textures.end(), diffuse_maps.begin(), diffuse_maps.end()); std::vector specular_maps = load_material_textures(material, aiTextureType_SPECULAR); textures.insert(textures.end(), specular_maps.begin(), specular_maps.end()); } return Mesh(vertices, indices, textures); } std::vector Model::load_material_textures(aiMaterial *material, aiTextureType type) { std::vector textures; for (unsigned int i = 0; i < material->GetTextureCount(type); ++i) { aiString path; material->GetTexture(type, i, &path); std::string absolute_path = directory + '/' + path.C_Str(); bool skip = false; for (unsigned int j = 0; j < loaded_textures.size(); ++j) { if (std::strcmp(loaded_textures[j].filename, absolute_path.c_str()) == 0) { textures.push_back(loaded_textures[j]); skip = true; break; } } if (!skip) { TextureType tex_type = type == aiTextureType_DIFFUSE ? TEXTURE_TYPE_DIFFUSE : TEXTURE_TYPE_SPECULAR; Texture2D texture = {absolute_path.c_str(), texture_unit++, tex_type}; textures.push_back(texture); } } return textures; } FrameBuffer create_normal_frame_buffer(unsigned int width, unsigned int height) { FrameBuffer buffer = {}; glGenFramebuffers(1, &buffer.fbo); glBindFramebuffer(GL_FRAMEBUFFER, buffer.fbo); // Create color texture glGenTextures(1, &buffer.color); glBindTexture(GL_TEXTURE_2D, buffer.color); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, buffer.color, 0); // Create depth and stencil buffers glGenRenderbuffers(1, &buffer.depth_stencil); glBindRenderbuffer(GL_RENDERBUFFER, buffer.depth_stencil); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, buffer.depth_stencil); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { printf("Incomplete frame buffer\n"); exit(EXIT_CODE_INCOMPLETE_FRAME_BUFFER); } glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(GL_TEXTURE_2D, 0); glBindRenderbuffer(GL_RENDERBUFFER, 0); buffer.width = width; buffer.height = height; return buffer; } #if MSAA FrameBuffer create_multisample_frame_buffer(unsigned int width, unsigned int height) { FrameBuffer buffer = {}; glGenFramebuffers(1, &buffer.fbo); glBindFramebuffer(GL_FRAMEBUFFER, buffer.fbo); // Create color texture glGenTextures(1, &buffer.color); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, buffer.color); glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, MSAA_SAMPLE_COUNT, GL_RGB, width, height, GL_TRUE); glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D_MULTISAMPLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, buffer.color, 0); // Create depth and stencil buffers glGenRenderbuffers(1, &buffer.depth_stencil); glBindRenderbuffer(GL_RENDERBUFFER, buffer.depth_stencil); glRenderbufferStorageMultisample(GL_RENDERBUFFER, MSAA_SAMPLE_COUNT, GL_DEPTH24_STENCIL8, width, height); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, buffer.depth_stencil); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { printf("Incomplete frame buffer\n"); exit(EXIT_CODE_INCOMPLETE_FRAME_BUFFER); } glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0); glBindRenderbuffer(GL_RENDERBUFFER, 0); buffer.width = width; buffer.height = height; return buffer; } void blit_multisample_frame_buffer(const FrameBuffer &multisample_buffer, const FrameBuffer &normal_buffer) { glBindFramebuffer(GL_READ_FRAMEBUFFER, multisample_buffer.fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, normal_buffer.fbo); glBlitFramebuffer( 0, 0, multisample_buffer.width, multisample_buffer.height, 0, 0, normal_buffer.width, normal_buffer.height, GL_COLOR_BUFFER_BIT, GL_NEAREST ); } #endif void delete_frame_buffer(FrameBuffer &buffer) { glDeleteFramebuffers(1, &buffer.fbo); glDeleteTextures(1, &buffer.color); glDeleteRenderbuffers(1, &buffer.depth_stencil); } GLuint load_cubemap(std::vector textures) { GLuint cubemap; glGenTextures(1, &cubemap); glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap); int width, height, channels; uint8_t *tex; for (unsigned int i = 0; i < textures.size(); ++i) { tex = stbi_load(textures[i].c_str(), &width, &height, &channels, 0); glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, tex); stbi_image_free(tex); } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); return cubemap; }