Update README and move scene functions into scene.cpp
This commit is contained in:
parent
efd466fc78
commit
783de0a217
7 changed files with 181 additions and 158 deletions
|
@ -16,7 +16,8 @@ add_executable(raytracer
|
||||||
include/image.h
|
include/image.h
|
||||||
include/tiny_obj_loader.h
|
include/tiny_obj_loader.h
|
||||||
include/scene.h
|
include/scene.h
|
||||||
src/main.cpp)
|
src/main.cpp
|
||||||
|
src/scene.cpp)
|
||||||
target_include_directories(raytracer PUBLIC include PRIVATE ${GLM_INCLUDE_DIR})
|
target_include_directories(raytracer PUBLIC include PRIVATE ${GLM_INCLUDE_DIR})
|
||||||
target_link_libraries(raytracer PUBLIC stb SDL2::Core imgui glad)
|
target_link_libraries(raytracer PUBLIC stb SDL2::Core imgui glad)
|
||||||
set_target_properties(raytracer PROPERTIES
|
set_target_properties(raytracer PROPERTIES
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
# Raytracer
|
# Raytracer
|
||||||
|
|
||||||
A multi-threaded raytracer using glm, tinyobjloader, stb and C++17.
|
A multi-threaded raytracer using glm, tinyobjloader and C++17. The UI is written in imgui and image display is
|
||||||
|
rendered using OpenGL. I tried to write this to not be insanely fast or compact like other raytracers, but to be readable and understandable.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
The example image shown above is rendered using simple direct light computation and naive indirect light sampling.
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
constexpr float epsilon = std::numeric_limits<float>().epsilon();
|
constexpr float epsilon = std::numeric_limits<float>().epsilon();
|
||||||
|
|
||||||
namespace intersections {
|
namespace intersections {
|
||||||
bool ray_sphere(const Ray ray, const glm::vec4 sphere) {
|
inline bool ray_sphere(const Ray ray, const glm::vec4 sphere) {
|
||||||
const glm::vec3 diff = ray.origin - glm::vec3(sphere);
|
const glm::vec3 diff = ray.origin - glm::vec3(sphere);
|
||||||
const float b = glm::dot(ray.direction, diff);
|
const float b = glm::dot(ray.direction, diff);
|
||||||
const float c = glm::dot(diff, diff) - sphere.w * sphere.w;
|
const float c = glm::dot(diff, diff) - sphere.w * sphere.w;
|
||||||
|
@ -22,7 +22,7 @@ namespace intersections {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float ray_triangle(const Ray ray,
|
inline float ray_triangle(const Ray ray,
|
||||||
const glm::vec3 v0,
|
const glm::vec3 v0,
|
||||||
const glm::vec3 v1,
|
const glm::vec3 v1,
|
||||||
const glm::vec3 v2,
|
const glm::vec3 v2,
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
namespace lighting {
|
namespace lighting {
|
||||||
float point_light(const glm::vec3 pos, const glm::vec3 light, const glm::vec3 normal) {
|
inline float point_light(const glm::vec3 pos, const glm::vec3 light, const glm::vec3 normal) {
|
||||||
const glm::vec3 dir = light - pos;
|
const glm::vec3 dir = glm::normalize(light - pos);
|
||||||
const float n_dot_l = glm::max(glm::dot(normal, glm::normalize(dir)), 0.0f);
|
const float n_dot_l = glm::max(glm::dot(normal, dir), 0.0f);
|
||||||
|
|
||||||
return n_dot_l;
|
return n_dot_l;
|
||||||
}
|
}
|
||||||
|
|
164
include/scene.h
164
include/scene.h
|
@ -1,9 +1,19 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
#include <tiny_obj_loader.h>
|
#include <tiny_obj_loader.h>
|
||||||
|
|
||||||
|
#include "ray.h"
|
||||||
|
#include "intersections.h"
|
||||||
|
#include "lighting.h"
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
struct Object {
|
struct Object {
|
||||||
glm::vec3 position = glm::vec3(0);
|
glm::vec3 position = glm::vec3(0);
|
||||||
glm::vec3 color = glm::vec3(1);
|
glm::vec3 color = glm::vec3(1);
|
||||||
|
@ -25,164 +35,20 @@ struct Scene {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
inline glm::vec3 fetch_position(const Object& object, const tinyobj::mesh_t& mesh, const int32_t index, const int32_t vertex) {
|
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];
|
glm::vec3 fetch_normal(const Object& object, const tinyobj::mesh_t& mesh, const int32_t index, const int32_t 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];
|
|
||||||
|
|
||||||
return glm::vec3(vx, vy, vz);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline 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];
|
|
||||||
|
|
||||||
return glm::vec3(nx, ny, nz);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct HitResult {
|
struct HitResult {
|
||||||
glm::vec3 position, normal;
|
glm::vec3 position, normal;
|
||||||
Object object;
|
Object object;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<HitResult> test_mesh(const Ray ray, const Object& object, const tinyobj::mesh_t& mesh, float& tClosest) {
|
std::optional<HitResult> test_mesh(const Ray ray, const Object& object, const tinyobj::mesh_t& mesh, float& tClosest);
|
||||||
bool intersection = false;
|
std::optional<HitResult> test_scene(const Ray ray, const Scene& scene, float tClosest = std::numeric_limits<float>::infinity());
|
||||||
HitResult result = {};
|
|
||||||
|
|
||||||
for(size_t i = 0; i < mesh.num_face_vertices.size(); i++) {
|
|
||||||
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::numeric_limits<float>::infinity()) {
|
|
||||||
bool intersection = false;
|
|
||||||
HitResult result = {};
|
|
||||||
|
|
||||||
for(auto& object : scene.objects) {
|
|
||||||
for(uint32_t i = 0; i < object.shapes.size(); i++) {
|
|
||||||
auto mesh = object.shapes[i].mesh;
|
|
||||||
|
|
||||||
if(const auto hit = test_mesh(ray, object, mesh, tClosest)) {
|
|
||||||
intersection = true;
|
|
||||||
result = hit.value();
|
|
||||||
result.object = object;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(intersection)
|
|
||||||
return result;
|
|
||||||
else
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
struct SceneResult {
|
struct SceneResult {
|
||||||
HitResult hit;
|
HitResult hit;
|
||||||
glm::vec3 color, indirect;
|
glm::vec3 color, indirect;
|
||||||
};
|
};
|
||||||
|
|
||||||
// methods adapated from https://users.cg.tuwien.ac.at/zsolnai/gfx/smallpaint/
|
std::optional<SceneResult> cast_scene(const Ray ray, const Scene& scene, const int depth = 0);
|
||||||
inline std::tuple<glm::vec3, glm::vec3> orthogonal_system(const glm::vec3& v1) {
|
|
||||||
glm::vec3 v2;
|
|
||||||
if(glm::abs(v1.x) > glm::abs(v1.y)) {
|
|
||||||
// project to the y = 0 plane and construct a normalized orthogonal vector in this plane
|
|
||||||
const float inverse_length = 1.0f / sqrtf(v1.x * v1.x + v1.z * v1.z);
|
|
||||||
v2 = glm::vec3(-v1.z * inverse_length, 0.0f, v1.x * inverse_length);
|
|
||||||
} else {
|
|
||||||
// project to the x = 0 plane and construct a normalized orthogonal vector in this plane
|
|
||||||
const float inverse_length = 1.0f / sqrtf(v1.y * v1.y + v1.z * v1.z);
|
|
||||||
v2 = glm::vec3(0.0f, v1.z * inverse_length, -v1.y * inverse_length);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {v2, glm::cross(v1, v2)};
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec3 hemisphere(const double u1, const double u2) {
|
|
||||||
const double r = sqrt(1.0 - u1 * u1);
|
|
||||||
const double phi = 2 * M_PI * 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 = 0) {
|
|
||||||
if(depth > max_depth)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
if(auto hit = test_scene(ray, scene)) {
|
|
||||||
const float diffuse = lighting::point_light(hit->position, light_position, hit->normal);
|
|
||||||
|
|
||||||
//shadow calculation
|
|
||||||
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(shadow_ray, scene) ? 0.0f : 1.0f;
|
|
||||||
|
|
||||||
direct = diffuse * shadow * glm::vec3(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
indirect /= num_indirect_samples;
|
|
||||||
|
|
||||||
SceneResult result = {};
|
|
||||||
result.hit = *hit;
|
|
||||||
result.color = (indirect + direct) * hit->object.color;
|
|
||||||
result.indirect = indirect;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
153
src/scene.cpp
Normal file
153
src/scene.cpp
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
#include "scene.h"
|
||||||
|
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
return glm::vec3(nx, ny, nz);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<HitResult> test_mesh(const Ray ray, const Object& object, const tinyobj::mesh_t& mesh, float& tClosest) {
|
||||||
|
bool intersection = false;
|
||||||
|
HitResult result = {};
|
||||||
|
|
||||||
|
for(size_t i = 0; i < mesh.num_face_vertices.size(); i++) {
|
||||||
|
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) {
|
||||||
|
bool intersection = false;
|
||||||
|
HitResult result = {};
|
||||||
|
|
||||||
|
for(auto& object : scene.objects) {
|
||||||
|
for(uint32_t i = 0; i < object.shapes.size(); i++) {
|
||||||
|
auto mesh = object.shapes[i].mesh;
|
||||||
|
|
||||||
|
if(const auto hit = test_mesh(ray, object, mesh, tClosest)) {
|
||||||
|
intersection = true;
|
||||||
|
result = hit.value();
|
||||||
|
result.object = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(intersection)
|
||||||
|
return result;
|
||||||
|
else
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// methods adapated from https://users.cg.tuwien.ac.at/zsolnai/gfx/smallpaint/
|
||||||
|
inline std::tuple<glm::vec3, glm::vec3> orthogonal_system(const glm::vec3& v1) {
|
||||||
|
glm::vec3 v2;
|
||||||
|
if(glm::abs(v1.x) > glm::abs(v1.y)) {
|
||||||
|
// project to the y = 0 plane and construct a normalized orthogonal vector in this plane
|
||||||
|
const float inverse_length = 1.0f / sqrtf(v1.x * v1.x + v1.z * v1.z);
|
||||||
|
v2 = glm::vec3(-v1.z * inverse_length, 0.0f, v1.x * inverse_length);
|
||||||
|
} else {
|
||||||
|
// project to the x = 0 plane and construct a normalized orthogonal vector in this plane
|
||||||
|
const float inverse_length = 1.0f / sqrtf(v1.y * v1.y + v1.z * v1.z);
|
||||||
|
v2 = glm::vec3(0.0f, v1.z * inverse_length, -v1.y * inverse_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {v2, glm::cross(v1, v2)};
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 hemisphere(const double u1, const double u2) {
|
||||||
|
const double r = sqrt(1.0 - u1 * u1);
|
||||||
|
const double phi = 2 * M_PI * 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) {
|
||||||
|
if(depth > max_depth)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if(auto hit = test_scene(ray, scene)) {
|
||||||
|
const float diffuse = lighting::point_light(hit->position, light_position, hit->normal);
|
||||||
|
|
||||||
|
// 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(shadow_ray, scene) ? 0.0f : 1.0f;
|
||||||
|
|
||||||
|
direct = diffuse * shadow * glm::vec3(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
indirect /= num_indirect_samples;
|
||||||
|
|
||||||
|
SceneResult result = {};
|
||||||
|
result.hit = *hit;
|
||||||
|
result.color = (indirect + direct) * hit->object.color;
|
||||||
|
result.indirect = indirect;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue