From c41edb2a3e644c4a0a35a79f5b4d22aa182971b8 Mon Sep 17 00:00:00 2001 From: TheNumbat Date: Wed, 4 Nov 2020 15:26:30 -0500 Subject: [PATCH] upstream changes --- CMakeLists.txt | 6 +- docs/pathtracer/intersecting_objects.md | 4 +- docs/pathtracer/ray_triangle_intersection.md | 2 +- src/gui/manager.cpp | 2 +- src/gui/widgets.cpp | 1 + src/lib/ray.h | 2 +- src/rays/bsdf.h | 9 ++ src/rays/bvh.h | 5 +- src/rays/pathtracer.cpp | 2 +- src/rays/tri_mesh.h | 3 +- src/scene/object.cpp | 9 +- src/scene/object.h | 1 + src/student/bvh.inl | 155 +++++++++++++++++++ src/student/pathtracer.cpp | 7 +- src/student/tri_mesh.cpp | 6 +- 15 files changed, 198 insertions(+), 16 deletions(-) create mode 100644 src/student/bvh.inl diff --git a/CMakeLists.txt b/CMakeLists.txt index f689adc..7df2e9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ endif() # define sources -set(SOURCES_SCOTTY3D_GUI +set(SOURCES_SCOTTY3D_GUI "src/gui/manager.cpp" "src/gui/manager.h" "src/gui/model.cpp" @@ -107,6 +107,7 @@ if(SCOTTY3D_BUILD_REF) "src/reference/shapes.cpp" "src/reference/bsdf.cpp" "src/reference/bbox.cpp" + "src/reference/bvh.inl" "src/reference/skeleton.cpp" "src/reference/env_light.cpp" "src/reference/tri_mesh.cpp") @@ -121,6 +122,7 @@ else() "src/student/bbox.cpp" "src/student/debug.h" "src/student/debug.cpp" + "src/student/bvh.inl" "src/student/env_light.cpp" "src/student/skeleton.cpp" "src/student/tri_mesh.cpp") @@ -232,7 +234,7 @@ include_directories(${ASSIMP_INCLUDE_DIRS}) -# link libraries +# link libraries if(WIN32) target_include_directories(Scotty3D PRIVATE "deps/win") diff --git a/docs/pathtracer/intersecting_objects.md b/docs/pathtracer/intersecting_objects.md index 197be2d..34f38ad 100644 --- a/docs/pathtracer/intersecting_objects.md +++ b/docs/pathtracer/intersecting_objects.md @@ -18,7 +18,7 @@ In order to correctly implement `hit` you need to understand some of the fields * `dir`: represents the 3D direction of the ray (this direction will be normalized) * `time_bounds`: correspond to the minimum and maximum points on the ray with its x-component as the lower bound and y-component as the upper bound. That is, intersections that lie outside the [`ray.time_bounds.x`, `ray.time_bounds.y`] range should not be considered valid intersections with the primitive. - +One important detail of the Ray structure is that `time_bounds` is a mutable field of the Ray. This means that this fields can be modified by constant member functions such as `Triangle::hit`. When finding the first intersection of a ray and the scene, you almost certainly want to update the ray's `time_bounds` value after finding each hit with scene geometry. By bounding the ray as tightly as possible, your ray tracer will be able to avoid unnecessary tests with scene geometry that is known to not be able to result in a closest hit, resulting in higher performance. --- @@ -37,7 +37,7 @@ There are two important details you should be aware of about intersection: * `position`: the exact position of the hit point. This can be easily computed by the `time` above as with the ray's `point` and `dir`. * `normal`: the normal of the surface at the hit point. This normal should be the interpolated normal (obtained via interpolation of the per-vertex normals according to the barycentric coordinates of the hit point) -* When intersection occurs with the back-face of a triangle (the side of the triangle opposite the direction of the normal) you should flip the returned normal to point in that direction. That is, always return a normal pointing in the direction the ray came from! + Once you've successfully implemented triangle intersection, you will be able to render many of the scenes in the media directory. However, your ray tracer will be very slow! diff --git a/docs/pathtracer/ray_triangle_intersection.md b/docs/pathtracer/ray_triangle_intersection.md index 5f8bf31..c1153b4 100644 --- a/docs/pathtracer/ray_triangle_intersection.md +++ b/docs/pathtracer/ray_triangle_intersection.md @@ -27,4 +27,4 @@ Of which you should notice a few common subexpressions that, if exploited in an A few final notes and thoughts: -If the denominator _dot((e1 x d), e2)_ is zero, what does that mean about the relationship of the ray and the triangle? Can a triangle with this area be hit by a ray? Given _u_ and _v_, how do you know if the ray hits the triangle? Don't forget that the intersection point on the ray should be within the ray's `time_bound`. \ No newline at end of file +If the denominator _dot((e1 x d), e2)_ is zero, what does that mean about the relationship of the ray and the triangle? Can a triangle with this area be hit by a ray? Given _u_ and _v_, how do you know if the ray hits the triangle? Don't forget that the intersection point on the ray should be within the ray's `time_bound`. diff --git a/src/gui/manager.cpp b/src/gui/manager.cpp index d085e5b..603ebee 100644 --- a/src/gui/manager.cpp +++ b/src/gui/manager.cpp @@ -293,7 +293,7 @@ Mode Manager::item_options(Undo &undo, Mode cur_mode, Scene_Item &item, Pose &ol } ImGui::SameLine(); if (ImGui::Button("Flip Normals")) { - obj.get_mesh().flip(); + obj.flip_normals(); } if (ImGui::Checkbox("Smooth Normals", &obj.opt.smooth_normals)) { update(); diff --git a/src/gui/widgets.cpp b/src/gui/widgets.cpp index 9f71799..1e1ccf4 100644 --- a/src/gui/widgets.cpp +++ b/src/gui/widgets.cpp @@ -548,6 +548,7 @@ void Widget_Render::begin(Scene &scene, Widget_Camera &cam, Camera &user_cam) { ImGui::SetNextWindowFocus(); render_window_focus = false; } + ImGui::SetNextWindowSize({675.0f, 625.0f}, ImGuiCond_Once); ImGui::Begin("Render Image", &render_window, ImGuiWindowFlags_NoCollapse); static const char *method_names[] = {"Rasterize", "Path Trace"}; diff --git a/src/lib/ray.h b/src/lib/ray.h index 9367839..53d6579 100644 --- a/src/lib/ray.h +++ b/src/lib/ray.h @@ -33,7 +33,7 @@ struct Ray { /// The direction the ray travels in Vec3 dir; /// The minimum and maximum time/distance at which this ray should exist - Vec2 time_bounds; + mutable Vec2 time_bounds; /// Recursive depth of ray size_t depth = 0; }; diff --git a/src/rays/bsdf.h b/src/rays/bsdf.h index 3e9ae3c..f4bafc8 100644 --- a/src/rays/bsdf.h +++ b/src/rays/bsdf.h @@ -111,6 +111,15 @@ public: underlying); } + bool is_sided() const { + return std::visit(overloaded{[](const BSDF_Lambertian &) { return false; }, + [](const BSDF_Mirror &) { return false; }, + [](const BSDF_Glass &) { return true; }, + [](const BSDF_Diffuse &) { return false; }, + [](const BSDF_Refract &) { return true; }}, + underlying); + } + private: std::variant underlying; }; diff --git a/src/rays/bvh.h b/src/rays/bvh.h index 89da0e9..8e2d7b7 100644 --- a/src/rays/bvh.h +++ b/src/rays/bvh.h @@ -34,12 +34,13 @@ private: std::vector nodes; std::vector primitives; + size_t root_idx = 0; }; } // namespace PT #ifdef SCOTTY3D_BUILD_REF -#include "../reference/bvh.cpp" +#include "../reference/bvh.inl" #else -#include "../student/bvh.cpp" +#include "../student/bvh.inl" #endif diff --git a/src/rays/pathtracer.cpp b/src/rays/pathtracer.cpp index 9683fef..e2b82cc 100644 --- a/src/rays/pathtracer.cpp +++ b/src/rays/pathtracer.cpp @@ -131,7 +131,7 @@ void Pathtracer::build_scene(Scene &layout_scene) { obj_list.push_back( Object(std::move(shape), obj.id(), idx, obj.pose.transform())); } else { - Tri_Mesh mesh(obj.posed_mesh(), obj.get_mesh().flipped()); + Tri_Mesh mesh(obj.posed_mesh()); std::lock_guard lock(obj_mut); obj_list.push_back( Object(std::move(mesh), obj.id(), idx, obj.pose.transform())); diff --git a/src/rays/tri_mesh.h b/src/rays/tri_mesh.h index 4038b76..999d35f 100644 --- a/src/rays/tri_mesh.h +++ b/src/rays/tri_mesh.h @@ -32,7 +32,7 @@ private: class Tri_Mesh { public: Tri_Mesh() = default; - Tri_Mesh(const GL::Mesh &mesh, bool flip = false); + Tri_Mesh(const GL::Mesh &mesh); BBox bbox() const; Trace hit(const Ray &ray) const; @@ -44,7 +44,6 @@ public: private: std::vector verts; BVH triangles; - bool flip_normals = false; }; } // namespace PT diff --git a/src/scene/object.cpp b/src/scene/object.cpp index ee861b4..1251967 100644 --- a/src/scene/object.cpp +++ b/src/scene/object.cpp @@ -53,8 +53,10 @@ void Scene_Object::try_make_editable(PT::Shape_Type prev) { } std::string err = halfedge.from_mesh(_mesh); - if (err.empty()) + if (err.empty()) { editable = true; + opt.smooth_normals = true; + } mesh_dirty = true; skel_dirty = true; @@ -120,6 +122,11 @@ void Scene_Object::sync_anim_mesh() { skel_dirty = pose_dirty = false; } +void Scene_Object::flip_normals() { + halfedge.flip(); + mesh_dirty = true; +} + void Scene_Object::sync_mesh() { if (editable && mesh_dirty) { diff --git a/src/scene/object.h b/src/scene/object.h index d3cdfde..e51eafe 100644 --- a/src/scene/object.h +++ b/src/scene/object.h @@ -49,6 +49,7 @@ public: bool is_editable() const; bool is_shape() const; void try_make_editable(PT::Shape_Type prev = PT::Shape_Type::none); + void flip_normals(); void set_mesh_dirty(); void set_skel_dirty(); diff --git a/src/student/bvh.inl b/src/student/bvh.inl new file mode 100644 index 0000000..4233bf6 --- /dev/null +++ b/src/student/bvh.inl @@ -0,0 +1,155 @@ + +#include "../rays/bvh.h" +#include "debug.h" +#include + +namespace PT { + +template +void BVH::build(std::vector &&prims, size_t max_leaf_size) { + + // NOTE (PathTracer): + // This BVH is parameterized on the type of the primitive it contains. This allows + // us to build a BVH over any type that defines a certain interface. Specifically, + // we use this to both build a BVH over triangles within each Tri_Mesh, and over + // a variety of Objects (which might be Tri_Meshes, Spheres, etc.) in Pathtracer. + // + // The Primitive interface must implement these two functions: + // BBox bbox() const; + // Trace hit(const Ray& ray) const; + // Hence, you may call bbox() and hit() on any value of type Primitive. + // + // Finally, also note that while a BVH is a tree structure, our BVH nodes don't + // contain pointers to children, but rather indicies. This is because instead + // of allocating each node individually, the BVH class contains a vector that + // holds all of the nodes. Hence, to get the child of a node, you have to + // look up the child index in this vector (e.g. nodes[node.l]). Similarly, + // to create a new node, don't allocate one yourself - use BVH::new_node, which + // returns the index of a newly added node. + + // Keep these + nodes.clear(); + primitives = std::move(prims); + + // TODO (PathTracer): Task 3 + // Construct a BVH from the given vector of primitives and maximum leaf + // size configuration. The starter code builds a BVH with a + // single leaf node (which is also the root) that encloses all the + // primitives. + + // Replace these + BBox box; + for (const Primitive &prim : primitives) + box.enclose(prim.bbox()); + + new_node(box, 0, primitives.size(), 0, 0); + root_idx = 0; +} + +template Trace BVH::hit(const Ray &ray) const { + + // TODO (PathTracer): Task 3 + // Implement ray - BVH intersection test. A ray intersects + // with a BVH aggregate if and only if it intersects a primitive in + // the BVH that is not an aggregate. + + // The starter code simply iterates through all the primitives. + // Again, remember you can use hit() on any Primitive value. + + Trace ret; + for (const Primitive &prim : primitives) { + Trace hit = prim.hit(ray); + ret = Trace::min(ret, hit); + } + return ret; +} + +template +BVH::BVH(std::vector &&prims, size_t max_leaf_size) { + build(std::move(prims), max_leaf_size); +} + +template bool BVH::Node::is_leaf() const { + return l == 0 && r == 0; +} + +template +size_t BVH::new_node(BBox box, size_t start, size_t size, size_t l, size_t r) { + Node n; + n.bbox = box; + n.start = start; + n.size = size; + n.l = l; + n.r = r; + nodes.push_back(n); + return nodes.size() - 1; +} + +template BBox BVH::bbox() const { return nodes[0].bbox; } + +template std::vector BVH::destructure() { + nodes.clear(); + return std::move(primitives); +} + +template void BVH::clear() { + nodes.clear(); + primitives.clear(); +} + +template +size_t BVH::visualize(GL::Lines &lines, GL::Lines &active, size_t level, + const Mat4 &trans) const { + + std::stack> tstack; + tstack.push({root_idx, 0}); + size_t max_level = 0; + + if (nodes.empty()) + return max_level; + + while (!tstack.empty()) { + + auto [idx, lvl] = tstack.top(); + max_level = std::max(max_level, lvl); + const Node &node = nodes[idx]; + tstack.pop(); + + Vec3 color = lvl == level ? Vec3(1.0f, 0.0f, 0.0f) : Vec3(1.0f); + GL::Lines &add = lvl == level ? active : lines; + + BBox box = node.bbox; + box.transform(trans); + Vec3 min = box.min, max = box.max; + + auto edge = [&](Vec3 a, Vec3 b) { add.add(a, b, color); }; + + edge(min, Vec3{max.x, min.y, min.z}); + edge(min, Vec3{min.x, max.y, min.z}); + edge(min, Vec3{min.x, min.y, max.z}); + edge(max, Vec3{min.x, max.y, max.z}); + edge(max, Vec3{max.x, min.y, max.z}); + edge(max, Vec3{max.x, max.y, min.z}); + edge(Vec3{min.x, max.y, min.z}, Vec3{max.x, max.y, min.z}); + edge(Vec3{min.x, max.y, min.z}, Vec3{min.x, max.y, max.z}); + edge(Vec3{min.x, min.y, max.z}, Vec3{max.x, min.y, max.z}); + edge(Vec3{min.x, min.y, max.z}, Vec3{min.x, max.y, max.z}); + edge(Vec3{max.x, min.y, min.z}, Vec3{max.x, max.y, min.z}); + edge(Vec3{max.x, min.y, min.z}, Vec3{max.x, min.y, max.z}); + + if (node.l) + tstack.push({node.l, lvl + 1}); + if (node.r) + tstack.push({node.r, lvl + 1}); + + if (!node.l && !node.r) { + for (size_t i = node.start; i < node.start + node.size; i++) { + size_t c = primitives[i].visualize(lines, active, level - lvl, trans); + max_level = std::max(c, max_level); + } + } + } + return max_level; +} + +} // namespace PT diff --git a/src/student/pathtracer.cpp b/src/student/pathtracer.cpp index 061e6f8..96d3b3d 100644 --- a/src/student/pathtracer.cpp +++ b/src/student/pathtracer.cpp @@ -37,13 +37,18 @@ Spectrum Pathtracer::trace_ray(const Ray &ray) { return {}; } + // If we're using a two-sided material, treat back-faces the same as front-faces + const BSDF &bsdf = materials[hit.material]; + if(!bsdf.is_sided() && dot(hit.normal, ray.dir) > 0.0f) { + hit.normal = -hit.normal; + } + // Set up a coordinate frame at the hit point, where the surface normal becomes {0, 1, 0} // This gives us out_dir and later in_dir in object space, where computations involving the // normal become much easier. For example, cos(theta) = dot(N,dir) = dir.y! Mat4 object_to_world = Mat4::rotate_to(hit.normal); Mat4 world_to_object = object_to_world.T(); Vec3 out_dir = world_to_object.rotate(ray.point - hit.position).unit(); - const BSDF &bsdf = materials[hit.material]; // Now we can compute the rendering equation at this point. // We split it into two stages: sampling lighting (i.e. directly connecting diff --git a/src/student/tri_mesh.cpp b/src/student/tri_mesh.cpp index c14fdc9..79aa077 100644 --- a/src/student/tri_mesh.cpp +++ b/src/student/tri_mesh.cpp @@ -9,6 +9,9 @@ BBox Triangle::bbox() const { // TODO (PathTracer): Task 2 // compute the bounding box of the triangle + // Beware of flat/zero-volume boxes! You may need to + // account for that here, or later on in BBox::intersect + BBox box; return box; } @@ -57,13 +60,12 @@ void Tri_Mesh::build(const GL::Mesh &mesh) { triangles.build(std::move(tris), 4); } -Tri_Mesh::Tri_Mesh(const GL::Mesh &mesh, bool flip) { build(mesh); flip_normals = flip; } +Tri_Mesh::Tri_Mesh(const GL::Mesh &mesh) { build(mesh); } BBox Tri_Mesh::bbox() const { return triangles.bbox(); } Trace Tri_Mesh::hit(const Ray &ray) const { Trace t = triangles.hit(ray); - if(flip_normals) t.normal = -t.normal; return t; } -- GitLab