1
Fork 0

Add octree for faster mesh rendering

This commit is contained in:
redstrate 2020-07-30 10:06:47 -04:00
parent 783de0a217
commit 245a3c03cb
6 changed files with 312 additions and 21 deletions

View file

@ -16,6 +16,8 @@ add_executable(raytracer
include/image.h include/image.h
include/tiny_obj_loader.h include/tiny_obj_loader.h
include/scene.h include/scene.h
include/aabb.h
include/octree.h
src/main.cpp src/main.cpp
src/scene.cpp) src/scene.cpp)
target_include_directories(raytracer PUBLIC include PRIVATE ${GLM_INCLUDE_DIR}) target_include_directories(raytracer PUBLIC include PRIVATE ${GLM_INCLUDE_DIR})

51
include/aabb.h Normal file
View file

@ -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;
}
};

89
include/octree.h Normal file
View file

@ -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<typename UnderlyingType>
struct Node {
using NodeType = Node<UnderlyingType>;
AABB extent;
int depth = 0;
std::vector<UnderlyingType> contained_objects;
std::array<std::unique_ptr<NodeType>, 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<NodeType>();
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<typename UnderlyingType>
struct Octree {
using NodeType = Node<UnderlyingType>;
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);
}
};

View file

@ -2,18 +2,32 @@
#include <optional> #include <optional>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <array>
#include <iostream>
#include <tiny_obj_loader.h> #include <tiny_obj_loader.h>
#include "ray.h" #include "ray.h"
#include "intersections.h" #include "intersections.h"
#include "lighting.h" #include "lighting.h"
#include "octree.h"
constexpr glm::vec3 light_position = glm::vec3(5); constexpr glm::vec3 light_position = glm::vec3(5);
constexpr float light_bias = 0.01f; constexpr float light_bias = 0.01f;
constexpr int max_depth = 2; constexpr int max_depth = 2;
constexpr int num_indirect_samples = 4; 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 { struct Object {
glm::vec3 position = glm::vec3(0); glm::vec3 position = glm::vec3(0);
glm::vec3 color = glm::vec3(1); glm::vec3 color = glm::vec3(1);
@ -21,30 +35,62 @@ struct Object {
tinyobj::attrib_t attrib; tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes; std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials; std::vector<tinyobj::material_t> materials;
};
struct Scene { std::unique_ptr<Octree<TriangleBox>> octree;
std::vector<Object> objects;
Object& load_from_file(const std::string_view path) { void create_octree() {
Object o; octree = std::make_unique<Octree<TriangleBox>>(glm::vec3(-1), glm::vec3(1));
tinyobj::LoadObj(&o.attrib, &o.shapes, &o.materials, nullptr, path.data()); 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);
return objects.emplace_back(o); 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); struct Scene {
glm::vec3 fetch_normal(const Object& object, const tinyobj::mesh_t& mesh, const int32_t index, const int32_t vertex); std::vector<std::unique_ptr<Object>> objects;
Object& load_from_file(const std::string_view path) {
auto o = std::make_unique<Object>();
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 { struct HitResult {
glm::vec3 position, normal; glm::vec3 position, normal;
Object object; const Object* object = nullptr;
}; };
std::optional<HitResult> test_mesh(const Ray ray, const Object& object, const tinyobj::mesh_t& mesh, float& tClosest); std::optional<HitResult> test_mesh(const Ray ray, const Object& object, const tinyobj::mesh_t& mesh, float& tClosest);
std::optional<HitResult> test_scene(const Ray ray, const Scene& scene, float tClosest = std::numeric_limits<float>::infinity()); std::optional<HitResult> test_scene(const Ray ray, const Scene& scene, float tClosest = std::numeric_limits<float>::infinity());
std::optional<HitResult> test_scene_octree(const Ray ray, const Scene& scene, float tClosest = std::numeric_limits<float>::infinity());
struct SceneResult { struct SceneResult {
HitResult hit; HitResult hit;
@ -52,3 +98,4 @@ struct SceneResult {
}; };
std::optional<SceneResult> cast_scene(const Ray ray, const Scene& scene, const int depth = 0); std::optional<SceneResult> cast_scene(const Ray ray, const Scene& scene, const int depth = 0);

View file

@ -23,7 +23,7 @@
#include <tiny_obj_loader.h> #include <tiny_obj_loader.h>
// scene information // scene information
constexpr int32_t width = 128, height = 128; constexpr int32_t width = 256, height = 256;
const Camera camera = [] { const Camera camera = [] {
Camera camera; Camera camera;
@ -178,6 +178,25 @@ void dump_to_file() {
stbi_write_png("output.png", width, height, 3, pixels, width * 3); stbi_write_png("output.png", width, height, 3, pixels, width * 3);
} }
template<typename UnderlyingType>
void walk_node(Node<UnderlyingType>& 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*[]) { int main(int, char*[]) {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER); SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
@ -224,6 +243,8 @@ int main(int, char*[]) {
auto& plane = scene.load_from_file("plane.obj"); auto& plane = scene.load_from_file("plane.obj");
plane.position.y = -1; plane.position.y = -1;
plane.color = {1, 0, 0}; plane.color = {1, 0, 0};
scene.generate_acceleration();
} }
ImGui::EndMenu(); ImGui::EndMenu();
@ -243,6 +264,14 @@ int main(int, char*[]) {
image_dirty = false; image_dirty = false;
} }
for(auto& object : scene.objects) {
if(ImGui::TreeNode("Object")) {
walk_object(*object);
ImGui::TreePop();
}
}
ImGui::Render(); ImGui::Render();
auto& io = ImGui::GetIO(); auto& io = ImGui::GetIO();

View file

@ -52,18 +52,91 @@ std::optional<HitResult> test_mesh(const Ray ray, const Object& object, const ti
return {}; return {};
} }
std::optional<HitResult> 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<float>::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<HitResult> test_scene(const Ray ray, const Scene& scene, float tClosest) { std::optional<HitResult> test_scene(const Ray ray, const Scene& scene, float tClosest) {
bool intersection = false; bool intersection = false;
HitResult result = {}; HitResult result = {};
for(auto& object : scene.objects) { for(auto& object : scene.objects) {
for(uint32_t i = 0; i < object.shapes.size(); i++) { for(uint32_t i = 0; i < object->shapes.size(); i++) {
auto mesh = object.shapes[i].mesh; 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; intersection = true;
result = hit.value(); result = hit.value();
result.object = object; result.object = object.get();
}
}
}
if(intersection)
return result;
else
return {};
}
template<typename T>
Node<T>* find_hit_ray_search(Node<T>& node, const Ray ray, std::vector<Node<T>*>* 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<typename T>
std::vector<Node<T>*> find_hit_ray(Node<T>& node, const Ray ray) {
std::vector<Node<T>*> vec;
find_hit_ray_search(node, ray, &vec);
return vec;
}
std::optional<HitResult> 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<HitResult> test_scene(const Ray ray, const Scene& scene, float tCl
} }
// methods adapated from https://users.cg.tuwien.ac.at/zsolnai/gfx/smallpaint/ // methods adapated from https://users.cg.tuwien.ac.at/zsolnai/gfx/smallpaint/
inline std::tuple<glm::vec3, glm::vec3> orthogonal_system(const glm::vec3& v1) { std::tuple<glm::vec3, glm::vec3> orthogonal_system(const glm::vec3& v1) {
glm::vec3 v2; 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 // project to the y = 0 plane and construct a normalized orthogonal vector in this plane
@ -101,7 +174,7 @@ std::optional<SceneResult> cast_scene(const Ray ray, const Scene& scene, const i
if(depth > max_depth) if(depth > max_depth)
return {}; 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); const float diffuse = lighting::point_light(hit->position, light_position, hit->normal);
// direct lighting calculation // direct lighting calculation
@ -112,7 +185,7 @@ std::optional<SceneResult> cast_scene(const Ray ray, const Scene& scene, const i
const Ray shadow_ray(hit->position + (hit->normal * light_bias), light_dir); 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); direct = diffuse * shadow * glm::vec3(1);
} }
@ -143,7 +216,7 @@ std::optional<SceneResult> cast_scene(const Ray ray, const Scene& scene, const i
SceneResult result = {}; SceneResult result = {};
result.hit = *hit; result.hit = *hit;
result.color = (indirect + direct) * hit->object.color; result.color = (indirect + direct) * hit->object->color;
result.indirect = indirect; result.indirect = indirect;
return result; return result;