From c6433f87369a9a9c9ad82e407d7abd1918a3099d Mon Sep 17 00:00:00 2001 From: redstrate <54911369+redstrate@users.noreply.github.com> Date: Fri, 31 Jul 2020 22:52:16 -0400 Subject: [PATCH] Add reflection component, fix indirect sampling --- include/aabb.h | 4 +- include/scene.h | 23 ++++++-- src/main.cpp | 73 +++++++++++++++++++++--- src/scene.cpp | 146 +++++++++++++++++++++++++----------------------- 4 files changed, 160 insertions(+), 86 deletions(-) diff --git a/include/aabb.h b/include/aabb.h index b4ffdb3..4bb310f 100644 --- a/include/aabb.h +++ b/include/aabb.h @@ -36,8 +36,8 @@ struct AABB { 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(tmax < 0) + // return false; // if tmin > tmax, ray doesn't intersect AABB if(tmin > tmax) diff --git a/include/scene.h b/include/scene.h index fa5cd81..d9be5fe 100644 --- a/include/scene.h +++ b/include/scene.h @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -15,7 +16,7 @@ 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; +inline int num_indirect_samples = 4; struct TriangleBox { uint64_t vertice_index = 0; @@ -39,7 +40,7 @@ struct Object { std::unique_ptr> octree; void create_octree() { - octree = std::make_unique>(glm::vec3(-1), glm::vec3(1)); + 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++) { @@ -68,6 +69,16 @@ 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(); @@ -89,13 +100,13 @@ struct HitResult { }; 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()); +std::optional test_scene(const Ray ray, const Scene& scene); +std::optional test_scene_octree(const Ray ray, const Scene& scene); struct SceneResult { HitResult hit; - glm::vec3 color, indirect; + glm::vec3 direct, indirect, reflect, combined; }; -std::optional cast_scene(const Ray ray, const Scene& scene, const int depth = 0); +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 7a0c6fe..e89b0fe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -24,10 +24,11 @@ // scene information constexpr int32_t width = 256, height = 256; +bool use_bvh = true; const Camera camera = [] { Camera camera; - camera.look_at(glm::vec3(0, 0, 4), glm::vec3(0)); + camera.look_at(glm::vec3(4), glm::vec3(0)); return camera; }(); @@ -42,13 +43,45 @@ Scene scene = {}; Image colors = {}; bool image_dirty = false; +enum class DisplayMode { + 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)) { - colors.get(x, y) = glm::vec4(result->color, 1.0f); + if(auto result = cast_scene(ray_camera, scene, use_bvh)) { + glm::vec3 chosen_display; + switch(display_mode) { + case DisplayMode::Combined: + chosen_display = result->combined; + break; + case DisplayMode::Direct: + chosen_display = result->direct; + break; + case DisplayMode::Indirect: + chosen_display = result->indirect; + break; + case DisplayMode::Reflect: + chosen_display = result->reflect; + break; + } + + colors.get(x, y) = glm::vec4(chosen_display, 1.0f); image_dirty = true; } @@ -165,6 +198,7 @@ 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++) { @@ -205,7 +239,12 @@ int main(int, char*[]) { 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, 640, 480, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); + SDL_Window* window = SDL_CreateWindow("raytracer", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + 640, + 480, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); SDL_GLContext gl_context = SDL_GL_CreateContext(window); SDL_GL_MakeCurrent(window, gl_context); @@ -238,7 +277,8 @@ int main(int, char*[]) { if(ImGui::BeginMainMenuBar()) { if(ImGui::BeginMenu("File")) { if(ImGui::Button("Load")) { - scene.load_from_file("suzanne.obj"); + auto& sphere = scene.load_from_file("sphere.obj"); + sphere.color = {0, 0, 0}; auto& plane = scene.load_from_file("plane.obj"); plane.position.y = -1; @@ -253,6 +293,25 @@ int main(int, char*[]) { ImGui::EndMainMenuBar(); } + ImGui::Checkbox("Use BVH", &use_bvh); + ImGui::InputInt("Indirect Samples", &num_indirect_samples); + + if(ImGui::BeginCombo("Display Mode", diplay_mode_strings[static_cast(display_mode)])) { + if(ImGui::Selectable("Combined")) + display_mode = DisplayMode::Combined; + + if(ImGui::Selectable("Direct")) + display_mode = DisplayMode::Direct; + + if(ImGui::Selectable("Indirect")) + display_mode = DisplayMode::Indirect; + + if(ImGui::Selectable("Reflect")) + display_mode = DisplayMode::Reflect; + + ImGui::EndCombo(); + } + if(ImGui::Button("Render")) render(); diff --git a/src/scene.cpp b/src/scene.cpp index b75eb21..fab7e2b 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -1,11 +1,15 @@ #include "scene.h" +#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 tinyobj::index_t idx = mesh.indices[(index * 3) + vertex]; - const auto vx = object.attrib.vertices[3*idx.vertex_index+0]; - const auto vy = object.attrib.vertices[3*idx.vertex_index+1]; - const auto vz = object.attrib.vertices[3*idx.vertex_index+2]; + 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); } @@ -13,13 +17,36 @@ glm::vec3 fetch_position(const Object& object, const tinyobj::mesh_t& mesh, cons 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+0]; - const auto ny = object.attrib.normals[3*idx.normal_index+1]; - const auto nz = object.attrib.normals[3*idx.normal_index+2]; + 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) { + 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; + + return true; + } + } +} + std::optional test_mesh(const Ray ray, const Object& object, const tinyobj::mesh_t& mesh, float& tClosest) { bool intersection = false; HitResult result = {}; @@ -52,39 +79,10 @@ 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) { +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++) { @@ -125,18 +123,16 @@ std::vector*> find_hit_ray(Node& node, const Ray ray) { return vec; } -std::optional test_scene_octree(const Ray ray, const Scene& scene, float tClosest) { +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(const auto hit = test_triangle(ray, *object, *triangle_object.mesh, triangle_object.vertice_index, tClosest)) { - intersection = true; - result = hit.value(); + if(test_triangle(ray, *object, *triangle_object.mesh, triangle_object.vertice_index, tClosest, intersection, result)) result.object = object.get(); - } } } } @@ -170,54 +166,62 @@ glm::vec3 hemisphere(const double u1, const double u2) { return glm::vec3(cos(phi) * r, sin(phi) * r, u1); } -std::optional cast_scene(const Ray ray, const Scene& scene, const int depth) { +glm::vec3 reflect(const glm::vec3& I, const glm::vec3& N) { + return I - 2 * glm::dot(I, N) * N; +} + +std::optional cast_scene(const Ray ray, Scene& scene, const bool use_bvh, const int depth) { if(depth > max_depth) return {}; - if(auto hit = test_scene_octree(ray, scene)) { + const std::function scene_func = use_bvh ? test_scene_octree : test_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) - glm::vec3 direct(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; - const float shadow = test_scene_octree(shadow_ray, scene) ? 0.0f : 1.0f; - - direct = diffuse * shadow * glm::vec3(1); + 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)) + 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 - glm::vec3 indirect(0); - for(int i = 0; i < num_indirect_samples; i++) { - const float theta = drand48() * M_PI; - const float cos_theta = cos(theta); - const float sin_theta = sin(theta); + 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)) + result.indirect += indirect_result->combined * cos_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, depth + 1)) - indirect += indirect_result->color * cos_theta; + result.indirect /= num_indirect_samples; } - indirect /= num_indirect_samples; - - SceneResult result = {}; result.hit = *hit; - result.color = (indirect + direct) * hit->object->color; - result.indirect = indirect; + result.combined = (result.indirect + result.direct + result.reflect); return result; } else {