Commit c41edb2a authored by TheNumbat's avatar TheNumbat
Browse files

upstream changes

parent c631ed31
......@@ -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")
......
......@@ -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. -->
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!
<!-- * 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!
......
......@@ -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`.
......@@ -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();
......
......@@ -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"};
......
......@@ -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;
};
......
......@@ -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<BSDF_Lambertian, BSDF_Mirror, BSDF_Glass, BSDF_Diffuse, BSDF_Refract> underlying;
};
......
......@@ -34,12 +34,13 @@ private:
std::vector<Node> nodes;
std::vector<Primitive> 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
......@@ -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<std::mutex> lock(obj_mut);
obj_list.push_back(
Object(std::move(mesh), obj.id(), idx, obj.pose.transform()));
......
......@@ -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<Tri_Mesh_Vert> verts;
BVH<Triangle> triangles;
bool flip_normals = false;
};
} // namespace PT
......@@ -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) {
......
......@@ -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();
......
#include "../rays/bvh.h"
#include "debug.h"
#include <stack>
namespace PT {
template <typename Primitive>
void BVH<Primitive>::build(std::vector<Primitive> &&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 <typename Primitive> Trace BVH<Primitive>::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 <typename Primitive>
BVH<Primitive>::BVH(std::vector<Primitive> &&prims, size_t max_leaf_size) {
build(std::move(prims), max_leaf_size);
}
template <typename Primitive> bool BVH<Primitive>::Node::is_leaf() const {
return l == 0 && r == 0;
}
template <typename Primitive>
size_t BVH<Primitive>::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 <typename Primitive> BBox BVH<Primitive>::bbox() const { return nodes[0].bbox; }
template <typename Primitive> std::vector<Primitive> BVH<Primitive>::destructure() {
nodes.clear();
return std::move(primitives);
}
template <typename Primitive> void BVH<Primitive>::clear() {
nodes.clear();
primitives.clear();
}
template <typename Primitive>
size_t BVH<Primitive>::visualize(GL::Lines &lines, GL::Lines &active, size_t level,
const Mat4 &trans) const {
std::stack<std::pair<size_t, size_t>> 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
......@@ -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
......
......@@ -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;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment