From 81d5045037f99281f8e43eea15171f05ebeb09d8 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Tue, 16 Aug 2022 07:41:12 -0400 Subject: [PATCH] Reformat code --- .clang-format | 33 ++++++ .editorconfig | 8 ++ CMakeLists.txt | 30 ++--- README.md | 4 +- include/aabb.h | 33 +++--- include/camera.h | 14 ++- include/image.h | 13 +-- include/intersections.h | 54 +++++---- include/lighting.h | 4 +- include/octree.h | 52 ++++----- include/scene.h | 43 ++++--- src/main.cpp | 249 ++++++++++++++++++++-------------------- src/scene.cpp | 152 +++++++++++++----------- 13 files changed, 370 insertions(+), 319 deletions(-) create mode 100644 .clang-format create mode 100644 .editorconfig diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..359c7ac --- /dev/null +++ b/.clang-format @@ -0,0 +1,33 @@ +--- +AllowShortIfStatementsOnASingleLine: Never +CompactNamespaces: 'false' +DisableFormat: 'false' +IndentCaseLabels: 'true' +IndentPPDirectives: BeforeHash +IndentWidth: '4' +Language: Cpp +NamespaceIndentation: All +PointerAlignment: Left +ReflowComments: 'true' +SortIncludes: 'true' +SortUsingDeclarations: 'true' +SpacesInCStyleCastParentheses: 'false' +Standard: Cpp11 +TabWidth: '0' +UseTab: Never +AllowShortEnumsOnASingleLine: false +BraceWrapping: + AfterEnum: true +AccessModifierOffset: -4 +SpaceAfterTemplateKeyword: 'false' +AllowAllParametersOfDeclarationOnNextLine: false +AlignAfterOpenBracket: AlwaysBreak +BinPackArguments: false +BinPackParameters: false +ColumnLimit: 120 +AllowShortBlocksOnASingleLine: 'false' +AllowShortCaseLabelsOnASingleLine: 'false' +AllowShortFunctionsOnASingleLine: 'Empty' +AllowShortLambdasOnASingleLine: 'Empty' +AllowShortLoopsOnASingleLine: 'false' +SeparateDefinitionBlocks : 'Always' diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ce0d2bb --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +insert_final_newline = true +indent_style = space +indent_size = 4 +tab_width = 4 +max_line_length = 120 diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c2e6fd..b4a143f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,21 +9,21 @@ find_package(SDL2 REQUIRED) add_subdirectory(extern) add_executable(raytracer - include/camera.h - include/intersections.h - include/lighting.h - include/ray.h - include/image.h - include/tiny_obj_loader.h - include/scene.h - include/aabb.h - include/octree.h - src/main.cpp - src/scene.cpp) + include/camera.h + include/intersections.h + include/lighting.h + include/ray.h + include/image.h + include/tiny_obj_loader.h + include/scene.h + include/aabb.h + include/octree.h + src/main.cpp + src/scene.cpp) target_include_directories(raytracer PUBLIC include PRIVATE ${GLM_INCLUDE_DIR}) target_link_libraries(raytracer PUBLIC stb SDL2::Main imgui glad) set_target_properties(raytracer PROPERTIES - CXX_STANDARD 17 - CXX_STANDARD_REQUIRED YES - CXX_EXTENSIONS NO -) + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO + ) diff --git a/README.md b/README.md index 5e1b43e..257ef72 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Raytracer + A multi-threaded raytracer using glm, tinyobjloader and C++17. The UI is written in imgui and image display is -rendered using OpenGL. I tried to write this to not be insanely fast or compact like other raytracers, but to be readable and understandable. +rendered using OpenGL. I tried to write this to not be insanely fast or compact like other raytracers, but to be +readable and understandable. ![example result](misc/output.png) diff --git a/include/aabb.h b/include/aabb.h index 4bb310f..a14f079 100644 --- a/include/aabb.h +++ b/include/aabb.h @@ -2,28 +2,25 @@ struct AABB { glm::vec3 min, max; - + glm::vec3 center() const { return 0.5f * (max + min); } - + glm::vec3 extent() const { return max - center(); } - + bool contains(const glm::vec3 point) const { return glm::all(glm::lessThan(point, max)) && glm::all(glm::greaterThan(point, min)); } - + bool inside(const AABB extent) const { - return(max.x > extent.min.x && - min.x < extent.max.x && - max.y > extent.min.y && - min.y < extent.max.y && - max.z > extent.min.z && - min.z < extent.max.z); + return ( + max.x > extent.min.x && min.x < extent.max.x && max.y > extent.min.y && min.y < extent.max.y && + max.z > extent.min.z && min.z < extent.max.z); } - + bool contains(const Ray& ray) const { const float t1 = (min.x - ray.origin.x) / ray.direction.x; const float t2 = (max.x - ray.origin.x) / ray.direction.x; @@ -31,19 +28,19 @@ struct AABB { const float t4 = (max.y - ray.origin.y) / ray.direction.y; const float t5 = (min.z - ray.origin.z) / ray.direction.z; const float t6 = (max.z - ray.origin.z) / ray.direction.z; - + const float tmin = std::min(std::max(std::min(t1, t2), std::min(t3, t4)), std::min(t5, t6)); const float tmax = std::min(std::min(std::max(t1, t2), std::max(t3, t4)), std::max(t5, t6)); - + // if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us - //if(tmax < 0) + // if(tmax < 0) // return false; - + // if tmin > tmax, ray doesn't intersect AABB - if(tmin > tmax) + if (tmin > tmax) return false; - - if(tmin < 0.0f) + + if (tmin < 0.0f) return true; return true; diff --git a/include/camera.h b/include/camera.h index e6a49aa..47294d0 100644 --- a/include/camera.h +++ b/include/camera.h @@ -5,24 +5,26 @@ class Camera { public: Camera() : position(glm::vec3(0)), direction(glm::vec3(0)) {} + Camera(glm::vec3 position, glm::vec3 direction) : position(position), direction(direction) {} - + void look_at(glm::vec3 eye, glm::vec3 target) { position = eye; direction = glm::normalize(target - eye); } - + Ray get_ray(const int32_t x, const int32_t y, const int32_t width, const int32_t height) const { const glm::vec3 up = glm::vec3(0, 1, 0); const glm::vec3 right = glm::normalize(glm::cross(direction, up)); - + const float h2 = height / 2.0f; const float w2 = width / 2.0f; - - const glm::vec3 ray_dir = position + (h2 / tan(glm::radians(fov) / 2)) * direction + (y - h2) * up + static_cast(x - w2) * right; + + const glm::vec3 ray_dir = position + (h2 / tan(glm::radians(fov) / 2)) * direction + (y - h2) * up + + static_cast(x - w2) * right; return Ray(position, ray_dir); } - + glm::vec3 position, direction; float fov = 45.0f; }; diff --git a/include/image.h b/include/image.h index 09d9c1c..7ad6ca7 100644 --- a/include/image.h +++ b/include/image.h @@ -1,22 +1,21 @@ #pragma once -#include #include +#include -template -class Image { +template class Image { public: void reset() { array = {}; } - + T& get(const int32_t x, const int32_t y) { return array[y * Width + x]; } - + T get(const int32_t x, const int32_t y) const { return array[y * Width + x]; } - - std::array array = {}; + + std::array array = {}; }; diff --git a/include/intersections.h b/include/intersections.h index bf0f1c7..2613cc4 100644 --- a/include/intersections.h +++ b/include/intersections.h @@ -12,53 +12,57 @@ namespace intersections { const float b = glm::dot(ray.direction, diff); const float c = glm::dot(diff, diff) - sphere.w * sphere.w; float t = b * b - c; - + if (t > 0.0) { t = -b - glm::sqrt(t); if (t > 0.0) return true; } - + return false; } - - inline float ray_triangle(const Ray ray, - const glm::vec3 v0, - const glm::vec3 v1, - const glm::vec3 v2, - float& t, - float& u, - float& v) { + + inline float ray_triangle( + const Ray ray, + const glm::vec3 v0, + const glm::vec3 v1, + const glm::vec3 v2, + float& t, + float& u, + float& v) { glm::vec3 e1 = v1 - v0; glm::vec3 e2 = v2 - v0; - + glm::vec3 pvec = glm::cross(ray.direction, e2); float det = glm::dot(e1, pvec); - + // if determinant is zero then ray is // parallel with the triangle plane - if (det > -epsilon && det < epsilon) return false; - float invdet = 1.0/det; - + if (det > -epsilon && det < epsilon) + return false; + float invdet = 1.0 / det; + // calculate distance from m[0] to origin glm::vec3 tvec = ray.origin - v0; - + // u and v are the barycentric coordinates // in triangle if u >= 0, v >= 0 and u + v <= 1 u = glm::dot(tvec, pvec) * invdet; - + // check against one edge and opposite point - if (u < 0.0 || u > 1.0) return false; - + if (u < 0.0 || u > 1.0) + return false; + glm::vec3 qvec = glm::cross(tvec, e1); v = glm::dot(ray.direction, qvec) * invdet; - + // check against other edges - if (v < 0.0 || u + v > 1.0) return false; - - //distance along the ray, i.e. intersect at o + t * d + if (v < 0.0 || u + v > 1.0) + return false; + + // distance along the ray, i.e. intersect at o + t * d t = glm::dot(e2, qvec) * invdet; - + return true; } -}; +}; // namespace intersections diff --git a/include/lighting.h b/include/lighting.h index 92b77f6..2bcb221 100644 --- a/include/lighting.h +++ b/include/lighting.h @@ -6,7 +6,7 @@ namespace lighting { inline float point_light(const glm::vec3 pos, const glm::vec3 light, const glm::vec3 normal) { const glm::vec3 dir = glm::normalize(light - pos); const float n_dot_l = glm::max(glm::dot(normal, dir), 0.0f); - + return n_dot_l; } -}; +}; // namespace lighting diff --git a/include/octree.h b/include/octree.h index c6758cf..8774b88 100644 --- a/include/octree.h +++ b/include/octree.h @@ -18,73 +18,71 @@ constexpr auto child_pattern = { glm::vec3(-1, +1, +1), }; -template -struct Node { +template struct Node { using NodeType = Node; - + AABB extent; int depth = 0; - + std::vector contained_objects; - + std::array, 8> children; bool is_split = false; - + void add(UnderlyingType& object, const AABB extent) { - if(is_split) { - for(auto& child : children) { - if(extent.inside(child->extent)) + if (is_split) { + for (auto& child : children) { + if (extent.inside(child->extent)) child->add(object, extent); } } else { contained_objects.push_back(object); - - if(contained_objects.size() >= max_contained_types && depth < max_octree_depth) + + if (contained_objects.size() >= max_contained_types && depth < max_octree_depth) split(); } } - + void split() { is_split = true; - + const auto center = extent.center(); const auto split_size = extent.extent().x / 2.0f; - + int i = 0; - for(auto& point : child_pattern) { + for (auto& point : child_pattern) { auto child = std::make_unique(); child->depth = depth + 1; - + const auto position = center + point * split_size; child->extent.min = glm::vec3(position.x - split_size, position.y - split_size, position.z - split_size); child->extent.max = glm::vec3(position.x + split_size, position.y + split_size, position.z + split_size); - + children[i++] = std::move(child); } - - for(auto& object : contained_objects) { - for(auto& child : children) { - if(object.extent.inside(child->extent)) + + for (auto& object : contained_objects) { + for (auto& child : children) { + if (object.extent.inside(child->extent)) child->add(object, extent); } } - + contained_objects.clear(); } }; -template -struct Octree { +template struct Octree { using NodeType = Node; - + NodeType root; - + Octree(const glm::vec3 min, const glm::vec3 max) { root = NodeType(); root.extent.min = min; root.extent.max = max; } - + void add(UnderlyingType& object, const AABB extent) { root.add(object, extent); } diff --git a/include/scene.h b/include/scene.h index 8b458c9..e345396 100644 --- a/include/scene.h +++ b/include/scene.h @@ -1,17 +1,17 @@ #pragma once -#include -#include #include +#include #include +#include #include #include -#include "ray.h" #include "intersections.h" #include "lighting.h" #include "octree.h" +#include "ray.h" constexpr glm::vec3 light_position = glm::vec3(5); constexpr float light_bias = 0.01f; @@ -32,34 +32,34 @@ glm::vec3 fetch_normal(const Object& object, const tinyobj::mesh_t& mesh, const struct Object { glm::vec3 position = glm::vec3(0); glm::vec3 color = glm::vec3(1); - + tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; - + std::unique_ptr> octree; void create_octree() { octree = std::make_unique>(glm::vec3(-2), glm::vec3(2)); - - for(auto& shape : shapes) { - for(size_t i = 0; i < shape.mesh.num_face_vertices.size(); i++) { + + for (auto& shape : shapes) { + for (size_t i = 0; i < shape.mesh.num_face_vertices.size(); i++) { const glm::vec3 v0 = fetch_position(*this, shape.mesh, i, 0); const glm::vec3 v1 = fetch_position(*this, shape.mesh, i, 1); const glm::vec3 v2 = fetch_position(*this, shape.mesh, i, 2); - + AABB extent; extent.min = glm::min(v0, v1); extent.min = glm::min(extent.min, v2); - + extent.max = glm::max(v0, v1); extent.max = glm::max(extent.max, v2); - + TriangleBox box = {}; box.vertice_index = i; box.extent = extent; box.mesh = &shape.mesh; - + octree->add(box, box.extent); } } @@ -68,29 +68,29 @@ struct Object { struct Scene { std::vector> objects; - + std::random_device rd; std::mt19937 gen; std::uniform_real_distribution<> dis; - + Scene() : gen(rd()), dis(0.0, 1.0) {} - + float distribution() { return dis(gen); } - + Object& load_from_file(const std::string_view path) { auto o = std::make_unique(); std::string err; - if(!tinyobj::LoadObj(&o->attrib, &o->shapes, &o->materials, &err, path.data())) - std::cerr << "Could not load obj: " << err; - + if (!tinyobj::LoadObj(&o->attrib, &o->shapes, &o->materials, &err, path.data())) + std::cerr << "Could not load obj: " << err; + return *objects.emplace_back(std::move(o)); } - + void generate_acceleration() { - for(auto& object : objects) { + for (auto& object : objects) { object->create_octree(); } } @@ -111,4 +111,3 @@ struct SceneResult { }; std::optional cast_scene(const Ray ray, Scene& scene, const bool use_bvh, const int depth = 0); - diff --git a/src/main.cpp b/src/main.cpp index cf04516..e2daab5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,23 +1,23 @@ -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include #define STB_IMAGE_WRITE_IMPLEMENTATION #include -#include "intersections.h" #include "camera.h" +#include "glad/glad.h" #include "image.h" +#include "imgui.h" +#include "imgui_impl_opengl3.h" +#include "imgui_impl_sdl.h" +#include "intersections.h" #include "lighting.h" #include "scene.h" -#include "glad/glad.h" -#include "imgui.h" -#include "imgui_impl_sdl.h" -#include "imgui_impl_opengl3.h" #define TINYOBJLOADER_IMPLEMENTATION #include @@ -29,7 +29,7 @@ bool use_bvh = true; const Camera camera = [] { Camera camera; camera.look_at(glm::vec3(0, 0, 3), glm::vec3(0)); - + return camera; }(); @@ -50,23 +50,18 @@ enum class DisplayMode { Reflect }; -const std::array diplay_mode_strings = { - "Combined", - "Direct", - "Indirect", - "Reflect" -}; +const std::array diplay_mode_strings = {"Combined", "Direct", "Indirect", "Reflect"}; inline DisplayMode display_mode; bool calculate_tile(const int32_t from_x, const int32_t to_width, const int32_t from_y, const int32_t to_height) { - for(int32_t y = from_y; y < (from_y + to_height); y++) { - for(int32_t x = from_x; x < (from_x + to_width); x++) { + for (int32_t y = from_y; y < (from_y + to_height); y++) { + for (int32_t x = from_x; x < (from_x + to_width); x++) { Ray ray_camera = camera.get_ray(x, y, width, height); - - if(auto result = cast_scene(ray_camera, scene, use_bvh)) { + + if (auto result = cast_scene(ray_camera, scene, use_bvh)) { glm::vec3 chosen_display; - switch(display_mode) { + switch (display_mode) { case DisplayMode::Combined: chosen_display = result->combined; break; @@ -80,14 +75,14 @@ bool calculate_tile(const int32_t from_x, const int32_t to_width, const int32_t chosen_display = result->reflect; break; } - + colors.get(x, y) = glm::vec4(chosen_display, 1.0f); - + image_dirty = true; } } } - + return true; } @@ -98,76 +93,70 @@ GLuint pixels_texture = 0; void setup_gfx() { // create quad for pixel rendering constexpr std::array vertices = { - -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, - 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, - 0.5f, -0.5f, 0.0f, 1.0f, -1.0f, - -0.5f, -0.5f, 0.0f, 0.0f, -1.0f // we render it upside down (to opengl) so we flip our tex coord + -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.5f, + -0.5f, 0.0f, 1.0f, -1.0f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f // we render it upside down (to opengl) so we flip our + // tex coord }; - - constexpr std::array elements = { - 0, 1, 2, - 2, 3, 0 - }; - + + constexpr std::array elements = {0, 1, 2, 2, 3, 0}; + glGenVertexArrays(1, &quad_vao); glBindVertexArray(quad_vao); - + GLuint vbo = 0; glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW); - + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), nullptr); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), reinterpret_cast(3 * sizeof(float))); - + glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); - + GLuint ebo = 0; glGenBuffers(1, &ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, elements.size() * sizeof(uint32_t), elements.data(), GL_STATIC_DRAW); - - constexpr std::string_view vertex_glsl = - "#version 330 core\n" - "layout (location = 0) in vec3 in_position;\n" - "layout (location = 1) in vec2 in_uv;\n" - "out vec2 uv;\n" - "void main()\n" - "{\n" - " gl_Position = vec4(in_position.xyz, 1.0);\n" - " uv = in_uv;\n" - "}\n"; + + constexpr std::string_view vertex_glsl = "#version 330 core\n" + "layout (location = 0) in vec3 in_position;\n" + "layout (location = 1) in vec2 in_uv;\n" + "out vec2 uv;\n" + "void main()\n" + "{\n" + " gl_Position = vec4(in_position.xyz, 1.0);\n" + " uv = in_uv;\n" + "}\n"; const char* vertex_src = vertex_glsl.data(); - - constexpr std::string_view fragment_glsl = - "#version 330 core\n" - "in vec2 uv;\n" - "out vec4 out_color;\n" - "uniform sampler2D pixel_texture;\n" - "void main()\n" - "{\n" - " out_color = texture(pixel_texture, uv);\n" - "}\n"; + + constexpr std::string_view fragment_glsl = "#version 330 core\n" + "in vec2 uv;\n" + "out vec4 out_color;\n" + "uniform sampler2D pixel_texture;\n" + "void main()\n" + "{\n" + " out_color = texture(pixel_texture, uv);\n" + "}\n"; const char* fragment_src = fragment_glsl.data(); - + GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex_shader, 1, &vertex_src, nullptr); glCompileShader(vertex_shader); - + GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment_shader, 1, &fragment_src, nullptr); glCompileShader(fragment_shader); - + pixel_program = glCreateProgram(); - + glAttachShader(pixel_program, vertex_shader); glAttachShader(pixel_program, fragment_shader); glLinkProgram(pixel_program); - + glDeleteShader(vertex_shader); glDeleteShader(fragment_shader); - + glGenTextures(1, &pixels_texture); glBindTexture(GL_TEXTURE_2D, pixels_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); @@ -187,9 +176,9 @@ std::vector> futures; void render() { futures.clear(); colors.reset(); - - for(int32_t y = 0; y < num_tiles_y; y++) { - for(int32_t x = 0; x < num_tiles_x; x++) { + + for (int32_t y = 0; y < num_tiles_y; y++) { + for (int32_t x = 0; x < num_tiles_x; x++) { auto f = std::async(std::launch::async, calculate_tile, x * tile_size, tile_size, y * tile_size, tile_size); futures.push_back(std::move(f)); } @@ -198,10 +187,10 @@ void render() { void dump_to_file() { uint8_t pixels[width * height * 3] = {}; - + int i = 0; - for(int32_t y = height - 1; y >= 0; y--) { - for(int32_t x = 0; x < width; x++) { + for (int32_t y = height - 1; y >= 0; y--) { + for (int32_t x = 0; x < width; x++) { const glm::ivec4 c = colors.get(x, y); pixels[i++] = c.r; pixels[i++] = c.g; @@ -212,17 +201,24 @@ void dump_to_file() { stbi_write_png("output.png", width, height, 3, pixels, width * 3); } -template -void walk_node(Node& node) { - if(ImGui::TreeNode(&node, "min: (%f %f %f)\n max: (%f %f %f)", node.extent.min.x, node.extent.min.y, node.extent.min.z, node.extent.max.x, node.extent.max.y, node.extent.max.z)) { +template void walk_node(Node& node) { + if (ImGui::TreeNode( + &node, + "min: (%f %f %f)\n max: (%f %f %f)", + node.extent.min.x, + node.extent.min.y, + node.extent.min.z, + node.extent.max.x, + node.extent.max.y, + node.extent.max.z)) { ImGui::Text("Is split: %i", node.is_split); ImGui::Text("Contained triangles: %lu", node.contained_objects.size()); - if(node.is_split) { - for(auto& child : node.children) + if (node.is_split) { + for (auto& child : node.children) walk_node(*child); } - + ImGui::TreePop(); } } @@ -233,104 +229,105 @@ void walk_object(Object& object) { int main(int, char*[]) { SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER); - + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - - SDL_Window* window = SDL_CreateWindow("raytracer", - SDL_WINDOWPOS_CENTERED, - SDL_WINDOWPOS_CENTERED, - 800, - 600, - SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); - + + SDL_Window* window = SDL_CreateWindow( + "raytracer", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + 800, + 600, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); + SDL_GLContext gl_context = SDL_GL_CreateContext(window); SDL_GL_MakeCurrent(window, gl_context); SDL_GL_SetSwapInterval(1); - + gladLoadGL(); setup_gfx(); - + ImGui::CreateContext(); ImGui::StyleColorsDark(); ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL3_Init("#version 330 core"); - + bool running = true; - while(running) { + while (running) { SDL_Event event = {}; - while(SDL_PollEvent(&event)) { + while (SDL_PollEvent(&event)) { ImGui_ImplSDL2_ProcessEvent(&event); - if(event.type == SDL_QUIT) + if (event.type == SDL_QUIT) running = false; } - + ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDL2_NewFrame(window); - + ImGui::NewFrame(); - - if(ImGui::BeginMainMenuBar()) { - if(ImGui::BeginMenu("File")) { - if(ImGui::Button("Load Example Models")) { + + if (ImGui::BeginMainMenuBar()) { + if (ImGui::BeginMenu("File")) { + if (ImGui::Button("Load Example Models")) { auto& sphere = scene.load_from_file("misc/sphere.obj"); sphere.color = {1, 1, 1}; - + auto& plane = scene.load_from_file("misc/plane.obj"); plane.position.y = -1; plane.color = {1, 0, 0}; - + scene.generate_acceleration(); } - + ImGui::EndMenu(); } - + ImGui::EndMainMenuBar(); } ImGui::Begin("Render Options"); - + ImGui::Checkbox("Use BVH", &use_bvh); ImGui::InputInt("Indirect Samples", &num_indirect_samples); - - if(ImGui::BeginCombo("Channel Selection", diplay_mode_strings[static_cast(display_mode)])) { - if(ImGui::Selectable("Combined")) + + if (ImGui::BeginCombo("Channel Selection", diplay_mode_strings[static_cast(display_mode)])) { + if (ImGui::Selectable("Combined")) display_mode = DisplayMode::Combined; - - if(ImGui::Selectable("Direct")) + + if (ImGui::Selectable("Direct")) display_mode = DisplayMode::Direct; - - if(ImGui::Selectable("Indirect")) + + if (ImGui::Selectable("Indirect")) display_mode = DisplayMode::Indirect; - - if(ImGui::Selectable("Reflect")) + + if (ImGui::Selectable("Reflect")) display_mode = DisplayMode::Reflect; - + ImGui::EndCombo(); } - - if(ImGui::Button("Render")) + + if (ImGui::Button("Render")) render(); ImGui::SameLine(); - - if(ImGui::Button("Save Output")) + + if (ImGui::Button("Save Output")) dump_to_file(); - if(image_dirty) { + if (image_dirty) { update_texture(); image_dirty = false; } - - for(auto& object : scene.objects) { - if(ImGui::TreeNode("Object")) { + + for (auto& object : scene.objects) { + if (ImGui::TreeNode("Object")) { walk_object(*object); - + ImGui::TreePop(); } } @@ -338,18 +335,18 @@ int main(int, char*[]) { ImGui::End(); ImGui::Render(); - + auto& io = ImGui::GetIO(); glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); glClear(GL_COLOR_BUFFER_BIT); - + glUseProgram(pixel_program); glBindVertexArray(quad_vao); glBindTexture(GL_TEXTURE_2D, pixels_texture); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - + SDL_GL_SwapWindow(window); } diff --git a/src/scene.cpp b/src/scene.cpp index aeb4b55..1880f28 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -1,48 +1,55 @@ #include "scene.h" -#include #include +#include constexpr double pi = 3.14159265358979323846l; glm::vec3 fetch_position(const Object& object, const tinyobj::mesh_t& mesh, const int32_t index, const int32_t vertex) { const tinyobj::index_t idx = mesh.indices[(index * 3) + vertex]; - + const auto vx = object.attrib.vertices[3 * idx.vertex_index]; const auto vy = object.attrib.vertices[3 * idx.vertex_index + 1]; const auto vz = object.attrib.vertices[3 * idx.vertex_index + 2]; - + return glm::vec3(vx, vy, vz); } glm::vec3 fetch_normal(const Object& object, const tinyobj::mesh_t& mesh, const int32_t index, const int32_t vertex) { const tinyobj::index_t idx = mesh.indices[(index * 3) + vertex]; - + const auto nx = object.attrib.normals[3 * idx.normal_index]; const auto ny = object.attrib.normals[3 * idx.normal_index + 1]; const auto nz = object.attrib.normals[3 * idx.normal_index + 2]; - + return glm::vec3(nx, ny, nz); } -bool test_triangle(const Ray ray, const Object& object, const tinyobj::mesh_t& mesh, const size_t i, float& tClosest, bool& intersection, HitResult& result) { +bool test_triangle( + const Ray ray, + const Object& object, + const tinyobj::mesh_t& mesh, + const size_t i, + float& tClosest, + bool& intersection, + HitResult& result) { const glm::vec3 v0 = fetch_position(object, mesh, i, 0) + object.position; const glm::vec3 v1 = fetch_position(object, mesh, i, 1) + object.position; const glm::vec3 v2 = fetch_position(object, mesh, i, 2) + object.position; - + float t = std::numeric_limits::infinity(), u, v; - if(intersections::ray_triangle(ray, v0, v1, v2, t, u, v)) { - if(t < tClosest && t > epsilon) { + if (intersections::ray_triangle(ray, v0, v1, v2, t, u, v)) { + if (t < tClosest && t > epsilon) { const glm::vec3 n0 = fetch_normal(object, mesh, i, 0); const glm::vec3 n1 = fetch_normal(object, mesh, i, 1); const glm::vec3 n2 = fetch_normal(object, mesh, i, 2); - + result.normal = (1 - u - v) * n0 + u * n1 + v * n2; result.position = ray.origin + ray.direction * t; - + tClosest = t; intersection = true; - + return true; } } @@ -51,30 +58,30 @@ bool test_triangle(const Ray ray, const Object& object, const tinyobj::mesh_t& m std::optional test_mesh(const Ray ray, const Object& object, const tinyobj::mesh_t& mesh, float& tClosest) { bool intersection = false; HitResult result = {}; - - for(size_t i = 0; i < mesh.num_face_vertices.size(); i++) { + + for (size_t i = 0; i < mesh.num_face_vertices.size(); i++) { const glm::vec3 v0 = fetch_position(object, mesh, i, 0) + object.position; const glm::vec3 v1 = fetch_position(object, mesh, i, 1) + object.position; const glm::vec3 v2 = fetch_position(object, mesh, i, 2) + object.position; - + float t = std::numeric_limits::infinity(), u, v; - if(intersections::ray_triangle(ray, v0, v1, v2, t, u, v)) { - if(t < tClosest && t > epsilon) { + if (intersections::ray_triangle(ray, v0, v1, v2, t, u, v)) { + if (t < tClosest && t > epsilon) { const glm::vec3 n0 = fetch_normal(object, mesh, i, 0); const glm::vec3 n1 = fetch_normal(object, mesh, i, 1); const glm::vec3 n2 = fetch_normal(object, mesh, i, 2); - + result.normal = (1 - u - v) * n0 + u * n1 + v * n2; result.position = ray.origin + ray.direction * t; - + tClosest = t; - + intersection = true; } } } - - if(intersection) + + if (intersection) return result; else return {}; @@ -84,30 +91,29 @@ std::optional test_scene(const Ray ray, const Scene& scene) { bool intersection = false; HitResult result = {}; float tClosest = std::numeric_limits::infinity(); - - for(auto& object : scene.objects) { - for(uint32_t i = 0; i < object->shapes.size(); i++) { + + for (auto& object : scene.objects) { + for (uint32_t i = 0; i < object->shapes.size(); i++) { auto mesh = object->shapes[i].mesh; - - if(const auto hit = test_mesh(ray, *object, mesh, tClosest)) { + + if (const auto hit = test_mesh(ray, *object, mesh, tClosest)) { intersection = true; result = hit.value(); result.object = object.get(); } } } - - if(intersection) + + if (intersection) return result; else return {}; } -template -Node* find_hit_ray_search(Node& node, const Ray ray, std::vector*>* out) { - if(node.extent.contains(ray)) { - if(node.is_split) { - for(auto& child : node.children) +template Node* find_hit_ray_search(Node& node, const Ray ray, std::vector*>* out) { + if (node.extent.contains(ray)) { + if (node.is_split) { + for (auto& child : node.children) find_hit_ray_search(*child, ray, out); } else { out->push_back(&node); @@ -117,12 +123,11 @@ Node* find_hit_ray_search(Node& node, const Ray ray, std::vector*> return nullptr; } -template -std::vector*> find_hit_ray(Node& node, const Ray ray) { +template std::vector*> find_hit_ray(Node& node, const Ray ray) { std::vector*> vec; - + find_hit_ray_search(node, ray, &vec); - + return vec; } @@ -130,17 +135,24 @@ std::optional test_scene_octree(const Ray ray, const Scene& scene) { bool intersection = false; HitResult result = {}; float tClosest = std::numeric_limits::infinity(); - - for(auto& object : scene.objects) { - for(auto& node : find_hit_ray(object->octree->root, ray)) { - for(auto& triangle_object : node->contained_objects) { - if(test_triangle(ray, *object, *triangle_object.mesh, triangle_object.vertice_index, tClosest, intersection, result)) + + for (auto& object : scene.objects) { + for (auto& node : find_hit_ray(object->octree->root, ray)) { + for (auto& triangle_object : node->contained_objects) { + if (test_triangle( + ray, + *object, + *triangle_object.mesh, + triangle_object.vertice_index, + tClosest, + intersection, + result)) result.object = object.get(); } } } - - if(intersection) + + if (intersection) return result; else return {}; @@ -149,7 +161,7 @@ std::optional test_scene_octree(const Ray ray, const Scene& scene) { // methods adapated from https://users.cg.tuwien.ac.at/zsolnai/gfx/smallpaint/ std::tuple orthogonal_system(const glm::vec3& v1) { glm::vec3 v2; - if(glm::abs(v1.x) > glm::abs(v1.y)) { + if (glm::abs(v1.x) > glm::abs(v1.y)) { // project to the y = 0 plane and construct a normalized orthogonal vector in this plane const float inverse_length = 1.0f / sqrtf(v1.x * v1.x + v1.z * v1.z); v2 = glm::vec3(-v1.z * inverse_length, 0.0f, v1.x * inverse_length); @@ -158,14 +170,14 @@ std::tuple orthogonal_system(const glm::vec3& v1) { const float inverse_length = 1.0f / sqrtf(v1.y * v1.y + v1.z * v1.z); v2 = glm::vec3(0.0f, v1.z * inverse_length, -v1.y * inverse_length); } - + return {v2, glm::cross(v1, v2)}; } glm::vec3 hemisphere(const double u1, const double u2) { const double r = sqrt(1.0 - u1 * u1); const double phi = 2 * pi * u2; - + return glm::vec3(cos(phi) * r, sin(phi) * r, u1); } @@ -174,58 +186,58 @@ glm::vec3 reflect(const glm::vec3& I, const glm::vec3& N) { } std::optional cast_scene(const Ray ray, Scene& scene, const bool use_bvh, const int depth) { - if(depth > max_depth) + if (depth > max_depth) return {}; - + const std::function scene_func = use_bvh ? test_scene_octree : test_scene; - - if(auto hit = scene_func(ray, scene)) { + + if (auto hit = scene_func(ray, scene)) { const float diffuse = lighting::point_light(hit->position, light_position, hit->normal); SceneResult result = {}; // direct lighting calculation // currently only supports only one light (directional) - if(glm::dot(light_position - hit->position, hit->normal) > 0) { + if (glm::dot(light_position - hit->position, hit->normal) > 0) { const glm::vec3 light_dir = glm::normalize(light_position - hit->position); - + const Ray shadow_ray(hit->position + (hit->normal * light_bias), light_dir); - + const float shadow = scene_func(shadow_ray, scene) ? 0.0f : 1.0f; - + result.direct = hit->object->color * diffuse * shadow; } - - if(auto reflect_result = cast_scene(Ray(hit->position, glm::reflect(ray.direction, hit->normal)), scene, use_bvh, depth + 1)) + + if (auto reflect_result = + cast_scene(Ray(hit->position, glm::reflect(ray.direction, hit->normal)), scene, use_bvh, depth + 1)) result.reflect = reflect_result->combined; - + // indirect lighting calculation // we take a hemisphere orthogonal to the normal, and take a constant number of num_indirect_samples // and naive monte carlo without PDF - if(num_indirect_samples > 0) { - for(int i = 0; i < num_indirect_samples; i++) { + if (num_indirect_samples > 0) { + for (int i = 0; i < num_indirect_samples; i++) { const float theta = scene.distribution() * pi; const float cos_theta = cos(theta); const float sin_theta = sin(theta); - + const auto [rotX, rotY] = orthogonal_system(hit->normal); - + const glm::vec3 sampled_dir = hemisphere(cos_theta, sin_theta); const glm::vec3 rotated_dir = { glm::dot({rotX.x, rotY.x, hit->normal.x}, sampled_dir), glm::dot({rotX.y, rotY.y, hit->normal.y}, sampled_dir), - glm::dot({rotX.z, rotY.z, hit->normal.z}, sampled_dir) - }; - - if(const auto indirect_result = cast_scene(Ray(ray.origin, rotated_dir), scene, use_bvh, depth + 1)) + glm::dot({rotX.z, rotY.z, hit->normal.z}, sampled_dir)}; + + if (const auto indirect_result = cast_scene(Ray(ray.origin, rotated_dir), scene, use_bvh, depth + 1)) result.indirect += indirect_result->combined * cos_theta; } - + result.indirect /= num_indirect_samples; } - + result.hit = *hit; result.combined = (result.indirect + result.direct + result.reflect); - + return result; } else { return {};