1
Fork 0

Add reflection component, fix indirect sampling

This commit is contained in:
redstrate 2020-07-31 22:52:16 -04:00
parent 245a3c03cb
commit c6433f8736
4 changed files with 160 additions and 86 deletions

View file

@ -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)

View file

@ -4,6 +4,7 @@
#include <glm/glm.hpp>
#include <array>
#include <iostream>
#include <random>
#include <tiny_obj_loader.h>
@ -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<TriangleBox>> octree;
void create_octree() {
octree = std::make_unique<Octree<TriangleBox>>(glm::vec3(-1), glm::vec3(1));
octree = std::make_unique<Octree<TriangleBox>>(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<std::unique_ptr<Object>> 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<Object>();
@ -89,13 +100,13 @@ struct HitResult {
};
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_octree(const Ray ray, const Scene& scene, float tClosest = std::numeric_limits<float>::infinity());
std::optional<HitResult> test_scene(const Ray ray, const Scene& scene);
std::optional<HitResult> 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<SceneResult> cast_scene(const Ray ray, const Scene& scene, const int depth = 0);
std::optional<SceneResult> cast_scene(const Ray ray, Scene& scene, const bool use_bvh, const int depth = 0);

View file

@ -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<glm::vec4, width, height> 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<int>(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();

View file

@ -1,11 +1,15 @@
#include "scene.h"
#include <glm/gtx/perpendicular.hpp>
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<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;
return true;
}
}
}
std::optional<HitResult> 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<HitResult> test_mesh(const Ray ray, const Object& object, const ti
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) {
bool intersection = false;
HitResult result = {};
float tClosest = std::numeric_limits<float>::infinity();
for(auto& object : scene.objects) {
for(uint32_t i = 0; i < object->shapes.size(); i++) {
@ -125,18 +123,16 @@ std::vector<Node<T>*> find_hit_ray(Node<T>& node, const Ray ray) {
return vec;
}
std::optional<HitResult> test_scene_octree(const Ray ray, const Scene& scene, float tClosest) {
std::optional<HitResult> test_scene_octree(const Ray ray, const Scene& scene) {
bool intersection = false;
HitResult result = {};
float tClosest = std::numeric_limits<float>::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<SceneResult> 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<SceneResult> 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<decltype(test_scene)> 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 {