From 245a3c03cbfc3796cc80e49ce60505b1b4da6194 Mon Sep 17 00:00:00 2001 From: redstrate <54911369+redstrate@users.noreply.github.com> Date: Thu, 30 Jul 2020 10:06:47 -0400 Subject: [PATCH] Add octree for faster mesh rendering --- CMakeLists.txt | 2 ++ include/aabb.h | 51 +++++++++++++++++++++++++++ include/octree.h | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ include/scene.h | 71 +++++++++++++++++++++++++++++++------- src/main.cpp | 31 ++++++++++++++++- src/scene.cpp | 89 +++++++++++++++++++++++++++++++++++++++++++----- 6 files changed, 312 insertions(+), 21 deletions(-) create mode 100644 include/aabb.h create mode 100644 include/octree.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 729b9c5..92a5157 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,8 @@ add_executable(raytracer 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}) diff --git a/include/aabb.h b/include/aabb.h new file mode 100644 index 0000000..b4ffdb3 --- /dev/null +++ b/include/aabb.h @@ -0,0 +1,51 @@ +#pragma once + +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); + } + + 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; + const float t3 = (min.y - ray.origin.y) / ray.direction.y; + 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) + return false; + + // if tmin > tmax, ray doesn't intersect AABB + if(tmin > tmax) + return false; + + if(tmin < 0.0f) + return true; + + return true; + } +}; diff --git a/include/octree.h b/include/octree.h new file mode 100644 index 0000000..fceb75b --- /dev/null +++ b/include/octree.h @@ -0,0 +1,89 @@ +#pragma once + +#include "aabb.h" + +constexpr int max_contained_types = 16; +constexpr int max_octree_depth = 2; + +constexpr auto child_pattern = { + glm::vec3(+1, -1, -1), + glm::vec3(+1, -1, +1), + glm::vec3(+1, +1, -1), + glm::vec3(+1, +1, +1), + glm::vec3(-1, -1, -1), + glm::vec3(-1, -1, +1), + glm::vec3(-1, +1, -1), + glm::vec3(-1, +1, +1), +}; + +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)) + child->add(object, extent); + } + } else { + contained_objects.push_back(object); + + 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) { + 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)) + child->add(object, extent); + } + } + + contained_objects.clear(); + } +}; + +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 0ad1365..fa5cd81 100644 --- a/include/scene.h +++ b/include/scene.h @@ -2,18 +2,32 @@ #include #include +#include +#include #include #include "ray.h" #include "intersections.h" #include "lighting.h" +#include "octree.h" constexpr glm::vec3 light_position = glm::vec3(5); constexpr float light_bias = 0.01f; constexpr int max_depth = 2; constexpr int num_indirect_samples = 4; +struct TriangleBox { + uint64_t vertice_index = 0; + tinyobj::mesh_t* mesh; + AABB extent; +}; + +struct Object; + +glm::vec3 fetch_position(const Object& object, const tinyobj::mesh_t& mesh, const int32_t index, const int32_t vertex); +glm::vec3 fetch_normal(const Object& object, const tinyobj::mesh_t& mesh, const int32_t index, const int32_t vertex); + struct Object { glm::vec3 position = glm::vec3(0); glm::vec3 color = glm::vec3(1); @@ -21,30 +35,62 @@ struct Object { tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; -}; + + std::unique_ptr> octree; -struct Scene { - std::vector objects; - - Object& load_from_file(const std::string_view path) { - Object o; + void create_octree() { + octree = std::make_unique>(glm::vec3(-1), glm::vec3(1)); - tinyobj::LoadObj(&o.attrib, &o.shapes, &o.materials, nullptr, path.data()); - - return objects.emplace_back(o); + 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); + } + } } }; -glm::vec3 fetch_position(const Object& object, const tinyobj::mesh_t& mesh, const int32_t index, const int32_t vertex); -glm::vec3 fetch_normal(const Object& object, const tinyobj::mesh_t& mesh, const int32_t index, const int32_t vertex); +struct Scene { + std::vector> objects; + + Object& load_from_file(const std::string_view path) { + auto o = std::make_unique(); + + tinyobj::LoadObj(&o->attrib, &o->shapes, &o->materials, nullptr, path.data()); + + return *objects.emplace_back(std::move(o)); + } + + void generate_acceleration() { + for(auto& object : objects) { + object->create_octree(); + } + } +}; struct HitResult { glm::vec3 position, normal; - Object object; + const Object* object = nullptr; }; std::optional test_mesh(const Ray ray, const Object& object, const tinyobj::mesh_t& mesh, float& tClosest); std::optional test_scene(const Ray ray, const Scene& scene, float tClosest = std::numeric_limits::infinity()); +std::optional test_scene_octree(const Ray ray, const Scene& scene, float tClosest = std::numeric_limits::infinity()); struct SceneResult { HitResult hit; @@ -52,3 +98,4 @@ struct SceneResult { }; std::optional cast_scene(const Ray ray, const Scene& scene, const int depth = 0); + diff --git a/src/main.cpp b/src/main.cpp index 23d5a5b..7a0c6fe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,7 +23,7 @@ #include // scene information -constexpr int32_t width = 128, height = 128; +constexpr int32_t width = 256, height = 256; const Camera camera = [] { Camera camera; @@ -178,6 +178,25 @@ 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)) { + 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) + walk_node(*child); + } + + ImGui::TreePop(); + } +} + +void walk_object(Object& object) { + walk_node(object.octree->root); +} + int main(int, char*[]) { SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER); @@ -224,6 +243,8 @@ int main(int, char*[]) { auto& plane = scene.load_from_file("plane.obj"); plane.position.y = -1; plane.color = {1, 0, 0}; + + scene.generate_acceleration(); } ImGui::EndMenu(); @@ -243,6 +264,14 @@ int main(int, char*[]) { image_dirty = false; } + for(auto& object : scene.objects) { + if(ImGui::TreeNode("Object")) { + walk_object(*object); + + ImGui::TreePop(); + } + } + ImGui::Render(); auto& io = ImGui::GetIO(); diff --git a/src/scene.cpp b/src/scene.cpp index feac526..b75eb21 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -52,18 +52,91 @@ std::optional test_mesh(const Ray ray, const Object& object, const ti return {}; } +std::optional test_triangle(const Ray ray, const Object& object, const tinyobj::mesh_t& mesh, const size_t i, float& tClosest) { + bool intersection = false; + 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) { + 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) + return result; + else + return {}; +} + std::optional test_scene(const Ray ray, const Scene& scene, float tClosest) { bool intersection = false; HitResult result = {}; for(auto& object : scene.objects) { - for(uint32_t i = 0; i < object.shapes.size(); i++) { - auto mesh = object.shapes[i].mesh; + 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; + result.object = object.get(); + } + } + } + + 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) + find_hit_ray_search(*child, ray, out); + } else { + out->push_back(&node); + } + } +} + +template +std::vector*> find_hit_ray(Node& node, const Ray ray) { + std::vector*> vec; + + find_hit_ray_search(node, ray, &vec); + + return vec; +} + +std::optional test_scene_octree(const Ray ray, const Scene& scene, float tClosest) { + bool intersection = false; + HitResult result = {}; + + for(auto& object : scene.objects) { + for(auto& node : find_hit_ray(object->octree->root, ray)) { + for(auto& triangle_object : node->contained_objects) { + if(const auto hit = test_triangle(ray, *object, *triangle_object.mesh, triangle_object.vertice_index, tClosest)) { + intersection = true; + result = hit.value(); + result.object = object.get(); + } } } } @@ -75,7 +148,7 @@ std::optional test_scene(const Ray ray, const Scene& scene, float tCl } // methods adapated from https://users.cg.tuwien.ac.at/zsolnai/gfx/smallpaint/ -inline std::tuple orthogonal_system(const glm::vec3& v1) { +std::tuple orthogonal_system(const glm::vec3& v1) { glm::vec3 v2; if(glm::abs(v1.x) > glm::abs(v1.y)) { // project to the y = 0 plane and construct a normalized orthogonal vector in this plane @@ -101,7 +174,7 @@ std::optional cast_scene(const Ray ray, const Scene& scene, const i if(depth > max_depth) return {}; - if(auto hit = test_scene(ray, scene)) { + if(auto hit = test_scene_octree(ray, scene)) { const float diffuse = lighting::point_light(hit->position, light_position, hit->normal); // direct lighting calculation @@ -112,7 +185,7 @@ std::optional cast_scene(const Ray ray, const Scene& scene, const i const Ray shadow_ray(hit->position + (hit->normal * light_bias), light_dir); - const float shadow = test_scene(shadow_ray, scene) ? 0.0f : 1.0f; + const float shadow = test_scene_octree(shadow_ray, scene) ? 0.0f : 1.0f; direct = diffuse * shadow * glm::vec3(1); } @@ -143,7 +216,7 @@ std::optional cast_scene(const Ray ray, const Scene& scene, const i SceneResult result = {}; result.hit = *hit; - result.color = (indirect + direct) * hit->object.color; + result.color = (indirect + direct) * hit->object->color; result.indirect = indirect; return result;