diff --git a/include/camera.h b/include/camera.h index 5207d3d..e6a49aa 100644 --- a/include/camera.h +++ b/include/camera.h @@ -19,7 +19,7 @@ public: const float h2 = height / 2.0f; const float w2 = width / 2.0f; - glm::vec3 ray_dir = position + (h2 / tan(glm::radians(fov) / 2)) * direction + (y - h2) * up + (float)(x - w2) * right; + const glm::vec3 ray_dir = position + (h2 / tan(glm::radians(fov) / 2)) * direction + (y - h2) * up + static_cast(x - w2) * right; return Ray(position, ray_dir); } diff --git a/include/scene.h b/include/scene.h index af66db6..88fe50d 100644 --- a/include/scene.h +++ b/include/scene.h @@ -6,6 +6,7 @@ struct Object { glm::vec3 position = glm::vec3(0); + glm::vec3 color = glm::vec3(1); tinyobj::attrib_t attrib; std::vector shapes; @@ -25,42 +26,42 @@ struct Scene { }; inline glm::vec3 fetch_position(const Object& object, const tinyobj::mesh_t& mesh, const int32_t index, const int32_t vertex) { - tinyobj::index_t idx = mesh.indices[(index * 3) +vertex]; + const tinyobj::index_t idx = mesh.indices[(index * 3) +vertex]; - tinyobj::real_t vx = object.attrib.vertices[3*idx.vertex_index+0]; - tinyobj::real_t vy = object.attrib.vertices[3*idx.vertex_index+1]; - tinyobj::real_t vz = object.attrib.vertices[3*idx.vertex_index+2]; + 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) { - tinyobj::index_t idx = mesh.indices[(index * 3) + vertex]; + const tinyobj::index_t idx = mesh.indices[(index * 3) + vertex]; - tinyobj::real_t nx = object.attrib.normals[3*idx.normal_index+0]; - tinyobj::real_t ny = object.attrib.normals[3*idx.normal_index+1]; - tinyobj::real_t nz = object.attrib.normals[3*idx.normal_index+2]; + 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 { glm::vec3 position, normal; - tinyobj::mesh_t* mesh = nullptr; + Object object; }; std::optional 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++) { + 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::infinity(), u, v; - if (intersections::ray_triangle(ray, v0, v1, v2, t, u, v)) { - if (t < tClosest && t > epsilon) { + 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); @@ -86,13 +87,13 @@ std::optional test_scene(const Ray ray, const Scene& scene, float tCl HitResult result = {}; for(auto& object : scene.objects) { - for (uint32_t i = 0; i < object.shapes.size(); i++) { + for(uint32_t i = 0; i < object.shapes.size(); i++) { auto mesh = object.shapes[i].mesh; - if(auto hit = test_mesh(ray, object, mesh, tClosest)) { + if(const auto hit = test_mesh(ray, object, mesh, tClosest)) { intersection = true; result = hit.value(); - result.mesh = &mesh; + result.object = object; } } } @@ -102,3 +103,86 @@ std::optional test_scene(const Ray ray, const Scene& scene, float tCl 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 { + HitResult hit; + glm::vec3 color, indirect; +}; + +// methods adapated from https://users.cg.tuwien.ac.at/zsolnai/gfx/smallpaint/ +inline std::tuple 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 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 {}; + } +} diff --git a/misc/output.png b/misc/output.png index aaf4462..45058c8 100644 Binary files a/misc/output.png and b/misc/output.png differ diff --git a/src/main.cpp b/src/main.cpp index e0ee502..23d5a5b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,18 +23,16 @@ #include // scene information -constexpr int32_t width = 512, height = 512; -constexpr glm::vec3 light_position = glm::vec3(5); +constexpr int32_t width = 128, height = 128; + const Camera camera = [] { Camera camera; camera.look_at(glm::vec3(0, 0, 4), glm::vec3(0)); return camera; }(); -constexpr glm::vec3 model_color = glm::vec3(1.0f, 1.0f, 1.0f); // internal variables -constexpr float light_bias = 0.01f; constexpr int32_t tile_size = 32; constexpr int32_t num_tiles_x = width / tile_size; constexpr int32_t num_tiles_y = height / tile_size; @@ -49,22 +47,8 @@ bool calculate_tile(const int32_t from_x, const int32_t to_width, const int32_t for (int32_t x = from_x; x < (from_x + to_width); x++) { Ray ray_camera = camera.get_ray(x, y, width, height); - if (auto hit = test_scene(ray_camera, scene)) { - const float diffuse = lighting::point_light(hit->position, light_position, hit->normal); - - //shadow calculation - float shadow = 0.0f; - 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); - - if(test_scene(shadow_ray, scene)) - shadow = 1.0f; - } - - const glm::vec3 finalColor = model_color * diffuse * (1.0f - shadow); - colors.get(x, y) = glm::vec4(finalColor, 1.0f); + if(auto result = cast_scene(ray_camera, scene)) { + colors.get(x, y) = glm::vec4(result->color, 1.0f); image_dirty = true; } @@ -182,8 +166,8 @@ 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++) { + for(int32_t y = height - 1; y >= 0; y--) { + for(int32_t x = 0; x < width; x++) { const glm::ivec4 c = colors.get(x, y); pixels[i++] = c.r; pixels[i++] = c.g; @@ -239,6 +223,7 @@ int main(int, char*[]) { auto& plane = scene.load_from_file("plane.obj"); plane.position.y = -1; + plane.color = {1, 0, 0}; } ImGui::EndMenu();