Add reflection component, fix indirect sampling
This commit is contained in:
parent
245a3c03cb
commit
c6433f8736
4 changed files with 160 additions and 86 deletions
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
69
src/main.cpp
69
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<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++) {
|
||||
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();
|
||||
|
||||
|
|
106
src/scene.cpp
106
src/scene.cpp
|
@ -1,9 +1,13 @@
|
|||
#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 auto vx = object.attrib.vertices[3*idx.vertex_index+0];
|
||||
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];
|
||||
|
||||
|
@ -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 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,21 +123,19 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(intersection)
|
||||
return result;
|
||||
|
@ -170,32 +166,41 @@ 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 = test_scene_octree(shadow_ray, scene) ? 0.0f : 1.0f;
|
||||
const float shadow = scene_func(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);
|
||||
if(num_indirect_samples > 0) {
|
||||
for(int i = 0; i < num_indirect_samples; i++) {
|
||||
const float theta = drand48() * M_PI;
|
||||
const float theta = scene.distribution() * pi;
|
||||
const float cos_theta = cos(theta);
|
||||
const float sin_theta = sin(theta);
|
||||
|
||||
|
@ -208,16 +213,15 @@ std::optional<SceneResult> cast_scene(const Ray ray, const Scene& scene, const i
|
|||
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;
|
||||
if(const auto indirect_result = cast_scene(Ray(ray.origin, rotated_dir), scene, use_bvh, depth + 1))
|
||||
result.indirect += indirect_result->combined * cos_theta;
|
||||
}
|
||||
|
||||
indirect /= num_indirect_samples;
|
||||
result.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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue