├── .gitignore ├── CMakeLists.txt ├── src ├── inc │ ├── base.hpp │ ├── base │ │ ├── ray.hpp │ │ ├── rng.hpp │ │ ├── math.hpp │ │ ├── object.hpp │ │ ├── direction.hpp │ │ ├── scene.hpp │ │ ├── parallel.hpp │ │ ├── intersection.hpp │ │ ├── distribution.hpp │ │ ├── sphere.hpp │ │ ├── image.hpp │ │ ├── material.hpp │ │ ├── math │ │ │ ├── vec4.hpp │ │ │ ├── vec3.hpp │ │ │ └── mat4.hpp │ │ ├── camera.hpp │ │ └── kd_tree.hpp │ └── sample │ │ ├── our.hpp │ │ └── our │ │ ├── path │ │ ├── cache-impl.hpp │ │ ├── camera_path-impl.hpp │ │ └── light_path-impl.hpp │ │ ├── path_vertex.hpp │ │ ├── path.hpp │ │ └── renderer-impl.hpp └── main.cpp └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /cmake-build-debug/ 3 | /cmake-build-release/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required( VERSION 3.15 ) 2 | project( simple_ris_bpt ) 3 | 4 | set( CMAKE_CXX_STANDARD 17 ) 5 | 6 | file( GLOB HEADER_FILES src/inc/*.hpp src/inc/*/*.hpp src/inc/*/*/*.hpp src/inc/*/*/*/*.hpp ) 7 | file( GLOB SOURCE_FILES src/main.cpp ) 8 | add_executable( ${PROJECT_NAME} ${HEADER_FILES} ${SOURCE_FILES} ) 9 | target_link_libraries( ${PROJECT_NAME} ) -------------------------------------------------------------------------------- /src/inc/base.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef BASE_HPP 5 | #define BASE_HPP 6 | 7 | #include"base/ray.hpp" 8 | #include"base/rng.hpp" 9 | #include"base/math.hpp" 10 | #include"base/scene.hpp" 11 | #include"base/image.hpp" 12 | #include"base/sphere.hpp" 13 | #include"base/object.hpp" 14 | #include"base/camera.hpp" 15 | #include"base/kd_tree.hpp" 16 | #include"base/parallel.hpp" 17 | #include"base/distribution.hpp" 18 | #include"base/intersection.hpp" 19 | #include"base/material.hpp" 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple_ris_bpt 2 | A simple implementation of [resampling-aware weighting functions for bidirectional path tracing using multiple light sub-paths](https://doi.org/10.1145/3338994) 3 | (K. Nabata, K. Iwasaki, Y. Dobashi, ACM Transactions on Graphics, Vol. 39, No. 2, Article. 15, pp.1-11, 2020) 4 | 5 | ### Project Website 6 | 7 | 8 | 9 | ### How to Build 10 | 11 | We use [CMake](https://cmake.org/) to build this project. 12 | We have tested this project on Windows + Visual Studio 2019 and Mac + CLion. 13 | For Windows+VS2019, create VS project through CMake. 14 | 15 | 16 | ### Disclaimer 17 | This project is intended to assist in re-implementing our method. 18 | -------------------------------------------------------------------------------- /src/inc/base/ray.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef RAY_HPP 5 | #define RAY_HPP 6 | 7 | #include"math.hpp" 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////// 10 | //ray 11 | /////////////////////////////////////////////////////////////////////////////////////////////////// 12 | 13 | class ray 14 | { 15 | public: 16 | 17 | //constructor (o: origin, d: direction (unit vector), t: distance from origin to intersection point) 18 | ray(const vec3 &o, const vec3 &d, const float t = FLT_MAX) : m_o(o), m_d(d), m_t(t) 19 | { 20 | } 21 | 22 | const vec3 &o() const 23 | { 24 | return m_o; 25 | } 26 | const vec3 &d() const 27 | { 28 | return m_d; 29 | } 30 | 31 | float t() const 32 | { 33 | return m_t; 34 | } 35 | float &t() 36 | { 37 | return m_t; 38 | } 39 | 40 | float t_min() const 41 | { 42 | return 1e-3f; 43 | } 44 | 45 | private: 46 | 47 | vec3 m_o; 48 | vec3 m_d; 49 | float m_t; 50 | }; 51 | 52 | /////////////////////////////////////////////////////////////////////////////////////////////////// 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /src/inc/base/rng.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef RANDOM_NUMBER_GENERATOR_HPP 5 | #define RANDOM_NUMBER_GENERATOR_HPP 6 | 7 | #include 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////// 10 | //random_number_generator 11 | /////////////////////////////////////////////////////////////////////////////////////////////////// 12 | 13 | class random_number_generator 14 | { 15 | public: 16 | 17 | //constructor 18 | random_number_generator(const size_t seed = std::mt19937_64::default_seed) : m_engine(seed) 19 | { 20 | } 21 | 22 | //generate uniform random variable [0,1) 23 | float generate_uniform_real() 24 | { 25 | const float tmp = std::generate_canonical(m_engine); 26 | if(tmp < 1){ 27 | return tmp; 28 | }else{ 29 | return 1 - FLT_EPSILON * 0.5f; 30 | } 31 | } 32 | 33 | //generate uniform random variable of integers in [min,max] 34 | size_t generate_uniform_int(const size_t min, const size_t max) 35 | { 36 | return min + (m_engine() % (max - min + 1)); 37 | } 38 | 39 | private: 40 | 41 | std::mt19937_64 m_engine; 42 | }; 43 | 44 | /////////////////////////////////////////////////////////////////////////////////////////////////// 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/inc/base/math.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef MATH_HPP 5 | #define MATH_HPP 6 | 7 | #include 8 | #include 9 | 10 | #include"math/vec3.hpp" 11 | #include"math/vec4.hpp" 12 | #include"math/mat4.hpp" 13 | 14 | /////////////////////////////////////////////////////////////////////////////////////////////////// 15 | //function definitions 16 | /////////////////////////////////////////////////////////////////////////////////////////////////// 17 | 18 | inline float PI() 19 | { 20 | return 3.14159265358979323846f; 21 | } 22 | 23 | /////////////////////////////////////////////////////////////////////////////////////////////////// 24 | 25 | //convert radian to degree 26 | inline float conv_rad_to_deg(const float rad) 27 | { 28 | return rad * (180 / PI()); 29 | } 30 | 31 | /////////////////////////////////////////////////////////////////////////////////////////////////// 32 | 33 | //convert degree to radian 34 | inline float conv_deg_to_rad(const float deg) 35 | { 36 | return deg * (PI() / 180); 37 | } 38 | 39 | /////////////////////////////////////////////////////////////////////////////////////////////////// 40 | 41 | //clamp val in [min,max] 42 | inline float clamp(const float val, const float min, const float max) 43 | { 44 | return (val < min) ? min : (val > max) ? max : val; 45 | } 46 | 47 | /////////////////////////////////////////////////////////////////////////////////////////////////// 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /src/inc/base/object.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef OBJECT_HPP 5 | #define OBJECT_HPP 6 | 7 | #include"sphere.hpp" 8 | #include"material.hpp" 9 | 10 | /////////////////////////////////////////////////////////////////////////////////////////////////// 11 | //object 12 | /////////////////////////////////////////////////////////////////////////////////////////////////// 13 | 14 | class object 15 | { 16 | public: 17 | 18 | object(const sphere &sph, const material &mtl) : m_sph(sph), m_mtl(mtl) 19 | { 20 | } 21 | 22 | bool calc_intersection(ray &r, intersection &isect) const 23 | { 24 | if(m_sph.calc_intersection(r.o(), r.d(), r.t(), r.t_min(), isect)){ 25 | isect = intersection(isect.p(), isect.n(), &m_mtl); 26 | return true; 27 | }else{ 28 | return false; 29 | } 30 | } 31 | 32 | bool intersect(const ray &r) const 33 | { 34 | float t_max = r.t(); 35 | return m_sph.intersect(r.o(), r.d(), t_max, r.t_min()); 36 | } 37 | 38 | sample_point sample(random_number_generator &rng) const 39 | { 40 | sample_point sample = m_sph.sample(rng); 41 | return sample_point(sample.p(), sample.n(), &m_mtl, sample.pdf()); 42 | } 43 | 44 | float light_power() const 45 | { 46 | return m_mtl.is_emissive() ? luminance(m_mtl.Me()) * m_sph.area() : 0; 47 | } 48 | 49 | private: 50 | 51 | sphere m_sph; 52 | material m_mtl; 53 | }; 54 | 55 | /////////////////////////////////////////////////////////////////////////////////////////////////// 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/inc/base/direction.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef DIRECTION_HPP 5 | #define DIRECTION_HPP 6 | 7 | #include"math.hpp" 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////// 10 | //direction 11 | /////////////////////////////////////////////////////////////////////////////////////////////////// 12 | 13 | class direction 14 | { 15 | public: 16 | 17 | //constructor 18 | direction() : m_is_valid() 19 | { 20 | } 21 | direction(const vec3 &w, const vec3 &n) : m_w(w), m_cos(dot(w, n)), m_abs_cos(abs(m_cos)) 22 | { 23 | //invalidate directions of grazing angle 24 | m_is_valid = (m_abs_cos >= 1e-6f); 25 | } 26 | explicit direction(const vec3 &w) : m_w(w), m_cos(1), m_abs_cos(1), m_is_valid(true) 27 | { 28 | } 29 | 30 | //return direction 31 | operator vec3() const 32 | { 33 | return assert(is_valid()), m_w; 34 | } 35 | vec3 operator-() const 36 | { 37 | return assert(is_valid()), -m_w; 38 | } 39 | 40 | //return cosine 41 | float cos() const 42 | { 43 | return assert(is_valid()), m_cos; 44 | } 45 | float abs_cos() const 46 | { 47 | return assert(is_valid()), m_abs_cos; 48 | } 49 | 50 | bool in_upper_hemisphere() const 51 | { 52 | return assert(is_valid()), (m_cos > 0); 53 | } 54 | bool in_lower_hemisphere() const 55 | { 56 | return !(in_upper_hemisphere()); 57 | } 58 | 59 | bool is_valid() const 60 | { 61 | return m_is_valid; 62 | } 63 | bool is_invalid() const 64 | { 65 | return !(is_valid()); 66 | } 67 | 68 | private: 69 | 70 | vec3 m_w; 71 | float m_cos; 72 | float m_abs_cos; 73 | bool m_is_valid; 74 | }; 75 | 76 | /////////////////////////////////////////////////////////////////////////////////////////////////// 77 | 78 | #endif 79 | -------------------------------------------------------------------------------- /src/inc/base/scene.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef SCENE_HPP 5 | #define SCENE_HPP 6 | 7 | #include"object.hpp" 8 | #include"distribution.hpp" 9 | 10 | /////////////////////////////////////////////////////////////////////////////////////////////////// 11 | //scene 12 | /////////////////////////////////////////////////////////////////////////////////////////////////// 13 | 14 | class scene 15 | { 16 | public: 17 | 18 | scene(std::vector objs) 19 | { 20 | //construct distribution to sample points on light sources 21 | m_objs = distribution(std::move(objs), [](const object &obj){ return obj.light_power(); }); 22 | } 23 | 24 | //calculate intersection 25 | intersection calc_intersection(ray &r) const 26 | { 27 | intersection isect; 28 | for(const auto &obj : m_objs){ 29 | obj.calc_intersection(r, isect); 30 | } 31 | return isect; 32 | } 33 | 34 | //visibility test 35 | bool intersect(const ray &r) const 36 | { 37 | const ray r_(r.o(), r.d(), r.t() * (1 - 1e-3f)); 38 | for(const auto &obj : m_objs){ 39 | if(obj.intersect(r_)){ 40 | return true; 41 | } 42 | } 43 | return false; 44 | } 45 | 46 | //point sampling of light sources in the scene 47 | sample_point sample_light(random_number_generator &rng) const 48 | { 49 | //sample sphere proportional to areaxflux 50 | const auto s1 = m_objs.sample(rng); 51 | 52 | //uniformly sampling point on sphere 53 | const sample_point s2 = s1.p_elem->sample(rng); 54 | 55 | const float pdf = s1.pmf * s2.pdf(); 56 | return sample_point(s2.p(), s2.n(), &s2.material(), pdf); 57 | } 58 | 59 | //calculate pdf of intersection point x 60 | float pdf_light(const intersection &x) const 61 | { 62 | return luminance(x.material().Me()) / m_objs.normalization_constant(); 63 | } 64 | 65 | private: 66 | 67 | distribution m_objs; 68 | }; 69 | 70 | /////////////////////////////////////////////////////////////////////////////////////////////////// 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /src/inc/base/parallel.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef UTILITY_HPP 5 | #define UTILITY_HPP 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /////////////////////////////////////////////////////////////////////////////////////////////////// 13 | //spinlock 14 | /////////////////////////////////////////////////////////////////////////////////////////////////// 15 | 16 | class spinlock 17 | { 18 | public: 19 | 20 | void lock() 21 | { 22 | while(m_state.test_and_set(std::memory_order_acquire)){ 23 | } 24 | } 25 | 26 | void unlock() 27 | { 28 | m_state.clear(std::memory_order_release); 29 | } 30 | 31 | private: 32 | 33 | std::atomic_flag m_state = ATOMIC_FLAG_INIT; 34 | }; 35 | 36 | /////////////////////////////////////////////////////////////////////////////////////////////////// 37 | //function definitions 38 | /////////////////////////////////////////////////////////////////////////////////////////////////// 39 | 40 | 41 | //evaluate func( x, y ) in parallel with nt threads 42 | template inline void in_parallel(const int nx, const int ny, Func func, const size_t nt = std::thread::hardware_concurrency()) 43 | { 44 | std::atomic idx = 0; 45 | std::vector threads(nt); 46 | 47 | for(auto &thread : threads){ 48 | 49 | thread = std::thread([&](){ 50 | for(int i = idx.fetch_add(1); i < nx * ny; i = idx.fetch_add(1)){ 51 | const int y = i / nx; 52 | const int x = i - nx * y; 53 | func(x, y); 54 | } 55 | }); 56 | } 57 | for(auto &thread : threads){ 58 | thread.join(); 59 | } 60 | } 61 | 62 | //evaluate func(i) in parallel with nt threads 63 | template inline void in_parallel(const int nx, Func func, const size_t nt = std::thread::hardware_concurrency()) 64 | { 65 | std::atomic idx = 0; 66 | std::vector threads(nt); 67 | 68 | for(auto &thread : threads){ 69 | 70 | thread = std::thread([&](){ 71 | for(int i = idx.fetch_add(1); i < nx; i = idx.fetch_add(1)){ 72 | func(i); 73 | } 74 | }); 75 | } 76 | for(auto &thread : threads){ 77 | thread.join(); 78 | } 79 | } 80 | 81 | /////////////////////////////////////////////////////////////////////////////////////////////////// 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /src/inc/base/intersection.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef INTERSECTION_HPP 5 | #define INTERSECTION_HPP 6 | 7 | #include"math.hpp" 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////// 10 | //forward declaration 11 | /////////////////////////////////////////////////////////////////////////////////////////////////// 12 | 13 | class material; 14 | 15 | /////////////////////////////////////////////////////////////////////////////////////////////////// 16 | //intersection 17 | /////////////////////////////////////////////////////////////////////////////////////////////////// 18 | 19 | class intersection 20 | { 21 | public: 22 | 23 | intersection() : m_is_valid(), mp_mtl() 24 | { 25 | } 26 | 27 | //p: intersection point, n: normal at p, p_mtl: address of mtl for p 28 | intersection(const vec3 &p, const vec3 &n, const material *p_mtl) : m_p(p), m_n(n), m_is_valid(true), mp_mtl(p_mtl) 29 | { 30 | } 31 | 32 | //return position/normal 33 | const vec3 &p() const 34 | { 35 | return assert(is_valid()), m_p; 36 | } 37 | const vec3 &n() const 38 | { 39 | return assert(is_valid()), m_n; 40 | } 41 | 42 | //return material 43 | const material &material() const 44 | { 45 | return assert(is_valid() && (mp_mtl != nullptr)), *mp_mtl; 46 | } 47 | 48 | bool is_valid() const 49 | { 50 | return m_is_valid; 51 | } 52 | bool is_invalid() const 53 | { 54 | return !(is_valid()); 55 | } 56 | 57 | private: 58 | 59 | vec3 m_p; 60 | vec3 m_n; 61 | bool m_is_valid; 62 | const ::material *mp_mtl; 63 | }; 64 | 65 | /////////////////////////////////////////////////////////////////////////////////////////////////// 66 | //sample_point 67 | /////////////////////////////////////////////////////////////////////////////////////////////////// 68 | 69 | class sample_point : public intersection 70 | { 71 | public: 72 | 73 | sample_point() : m_pdf() 74 | { 75 | } 76 | 77 | //p: sampled position, n: normal at p, p_mtl: address of material at p, pdf: sampling pdf 78 | sample_point(const vec3 &p, const vec3 &n, const ::material *p_mtl, const float pdf) : intersection(p, n, p_mtl), m_pdf(pdf) 79 | { 80 | assert(pdf > 0); 81 | } 82 | 83 | float pdf() const 84 | { 85 | return m_pdf; 86 | } 87 | 88 | private: 89 | 90 | float m_pdf; 91 | }; 92 | 93 | /////////////////////////////////////////////////////////////////////////////////////////////////// 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /src/inc/base/distribution.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef DISTRIBUTION_HPP 5 | #define DISTRIBUTION_HPP 6 | 7 | #include 8 | #include 9 | 10 | #include"rng.hpp" 11 | 12 | /////////////////////////////////////////////////////////////////////////////////////////////////// 13 | //distribution 14 | /////////////////////////////////////////////////////////////////////////////////////////////////// 15 | 16 | template class distribution 17 | { 18 | public: 19 | 20 | //elems: set of elements to construct distribution, weight: function object that returns weight 21 | //distribution::sample samples element proportional to weight 22 | //distribution::normalization_constant returns sum of weight 23 | template distribution(std::vector elems, Weight weight) : m_cdf(elems.size() + 1) 24 | { 25 | double sum = 0; 26 | for(size_t i = 0, n = elems.size(); i < n; i++){ 27 | m_cdf[i] = float(sum); sum += weight(elems[i]); 28 | } 29 | 30 | const float inv_sum = float(1 / sum); 31 | for(size_t i = 0, n = elems.size(); i < n; i++){ 32 | m_cdf[i] *= inv_sum; 33 | } 34 | m_cdf.back() = 1; 35 | m_elems = std::move(elems); 36 | m_normalization_constant = float(sum); 37 | } 38 | distribution() : m_normalization_constant() 39 | { 40 | } 41 | 42 | 43 | //p_elem : address of sampled element, pmf: sampling probability 44 | struct sample_t{ 45 | const T *p_elem; float pmf; 46 | }; 47 | sample_t sample(random_number_generator &rng) const 48 | { 49 | const size_t idx = std::upper_bound(m_cdf.begin(), m_cdf.end(), rng.generate_uniform_real()) - m_cdf.begin() - 1; //二分探索 50 | return sample_t{ &m_elems[idx], m_cdf[idx + 1] - m_cdf[idx] }; 51 | } 52 | 53 | //return pmf to sample idx-th element 54 | float pmf(const size_t idx) const 55 | { 56 | return assert(idx < m_elems.size()), m_cdf[idx + 1] - m_cdf[idx]; 57 | } 58 | 59 | float normalization_constant() const 60 | { 61 | return m_normalization_constant; 62 | } 63 | 64 | typename std::vector::iterator begin() 65 | { 66 | return m_elems.begin(); 67 | } 68 | typename std::vector::iterator end() 69 | { 70 | return m_elems.end(); 71 | } 72 | typename std::vector::const_iterator begin() const 73 | { 74 | return m_elems.begin(); 75 | } 76 | typename std::vector::const_iterator end() const 77 | { 78 | return m_elems.end(); 79 | } 80 | 81 | private: 82 | 83 | std::vector m_elems; 84 | std::vector m_cdf; 85 | float m_normalization_constant; 86 | }; 87 | 88 | /////////////////////////////////////////////////////////////////////////////////////////////////// 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /src/inc/base/sphere.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef SPHERE_HPP 5 | #define SPHERE_HPP 6 | 7 | #include"ray.hpp" 8 | #include"rng.hpp" 9 | #include"intersection.hpp" 10 | 11 | /////////////////////////////////////////////////////////////////////////////////////////////////// 12 | //sphere 13 | /////////////////////////////////////////////////////////////////////////////////////////////////// 14 | 15 | class sphere 16 | { 17 | public: 18 | 19 | //constructor c : center of sphere, r : radius of sphere 20 | sphere(const vec3 &c, const float r) : m_c(c), m_r(r) 21 | { 22 | } 23 | 24 | //calculate intersection point (isect) between ray (o,d) and sphere 25 | bool calc_intersection(const vec3 &o, const vec3 &d, float &t_max, const float t_min, intersection &isect) const 26 | { 27 | if(intersect(o, d, t_max, t_min) == false){ 28 | return false; 29 | } 30 | 31 | const vec3 p = o + d * t_max; 32 | 33 | //direct normal toward origin o 34 | vec3 n = normalize(p - m_c); 35 | if(dot(n, d) > 0){ 36 | n = -n; 37 | } 38 | isect = intersection(p, n, nullptr); 39 | return true; 40 | } 41 | 42 | //ray sphere intersection test 43 | //if ray intersects sphere, distance from origin o to intersection point is stored in t_max 44 | bool intersect(const vec3 &o, const vec3 &d, float &t_max, const float t_min) const 45 | { 46 | const vec3 co = o - m_c; 47 | 48 | const float A = dot(d, d); 49 | const float B = dot(d, co); 50 | const float C = dot(co, co) - m_r * m_r; 51 | 52 | const float D = B * B - A * C; 53 | if(D <= 0){ 54 | return false; 55 | } 56 | 57 | const float sqrt_D = sqrt(D); 58 | const float inv_A = 1 / A; 59 | const float t1 = (-B - sqrt_D) * inv_A; 60 | const float t2 = (-B + sqrt_D) * inv_A; 61 | 62 | float t; 63 | if(t1 > t_min){ 64 | t = t1; 65 | }else if(t2 > t_min){ 66 | t = t2; 67 | }else{ 68 | return false; 69 | } 70 | if(t >= t_max){ 71 | return false; 72 | }else{ 73 | t_max = t; 74 | return true; 75 | } 76 | } 77 | 78 | //uniform sampling of sphere 79 | sample_point sample(random_number_generator &rng) const 80 | { 81 | const float u1 = rng.generate_uniform_real(); 82 | const float u2 = rng.generate_uniform_real(); 83 | 84 | const float ph = u1 * 2 * PI(); 85 | const float ct = u2 * 2 - 1; 86 | const float st = sqrt(1 - ct * ct); 87 | const float cp = cos(ph); 88 | const float sp = sin(ph); 89 | const vec3 n(st * cp, st * sp, ct); 90 | return sample_point(n * m_r + m_c, n, nullptr, 1 / area()); 91 | } 92 | 93 | //calculate surface area of sphere 94 | float area() const 95 | { 96 | return 4 * PI() * m_r * m_r; 97 | } 98 | 99 | private: 100 | 101 | vec3 m_c; 102 | float m_r; 103 | }; 104 | 105 | /////////////////////////////////////////////////////////////////////////////////////////////////// 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * a simple implementation of 3 | * "Resampling-aware Weighting Functions for BPT Using Multiple Light Sub-paths" 4 | * by K. Nabata et al. (ACM TOG Vol. 39, No. 2, Article No. 15, pp. 1-11, 2020) 5 | */ 6 | 7 | #include"inc/sample/our.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | /////////////////////////////////////////////////////////////////////////////////////////////////// 18 | 19 | int main(int argc, char **argv) 20 | { 21 | //scene setup 22 | const scene scene(std::vector{ 23 | object(sphere(vec3(1 - 1e+3f, 0, 0), 1e+3f), material(col3(0.14f, 0.45f, 0.091f), false)), //+X 24 | object(sphere(vec3(1e+3f - 1, 0, 0), 1e+3f), material(col3(0.63f, 0.065f, 0.05f), false)), //-X 25 | object(sphere(vec3(0, 1 - 1e+3f, 0), 1e+3f), material(col3(0.725f, 0.71f, 0.68f), false)), //+Y 26 | object(sphere(vec3(0, 1e+3f - 1, 0), 1e+3f), material(col3(0.725f, 0.71f, 0.68f), false)), //-Y 27 | object(sphere(vec3(0, 0, 1e+3f - 1), 1e+3f), material(col3(0.725f, 0.71f, 0.68f), false)), //-Z 28 | 29 | object(sphere(vec3(0, 0.9f, 0), 0.1f), material(col3(170, 120, 40), true)), //Light 30 | /* 31 | object(sphere(vec3(0.89f, -0.89f, -0.89f), 0.1f), material(col3(170, 120, 40) * 10, true)), //Light 32 | object(sphere(vec3(0.89f, -0.89f + 0.31f, -0.89f), 0.2f), material(col3(0.1f), false)), 33 | object(sphere(vec3(0.89f - 0.31f, -0.89f, -0.89f), 0.2f), material(col3(0.1f), false)), 34 | object(sphere(vec3(0.89f, -0.89f, -0.89f + 0.31f), 0.2f), material(col3(0.1f), false)), 35 | */ 36 | }); 37 | 38 | //camera setup 39 | const float fovy = 40; 40 | const camera camera(vec3(0, 0, 1 / tan(conv_deg_to_rad(fovy / 2)) + 1), vec3(0, 0, 0), 512, 512, fovy, 0.0); 41 | 42 | //parameter setup 43 | const size_t M = 200; //the number of pre-sampled light sub-paths 44 | our::renderer renderer(scene, camera, M); 45 | 46 | //buffer for storing rendering results 47 | const int w = camera.res_x(); 48 | const int h = camera.res_y(); 49 | imaged sum(w, h); 50 | 51 | //rendering algorithm shown in Algorithm 1 on Page 6 52 | const size_t max_iterations = 256; 53 | for(size_t n = 0; n < max_iterations; n++){ 54 | 55 | std::cout << "iteration = " << n << std::endl; 56 | 57 | const imagef result = renderer.render(scene, camera); 58 | 59 | for(int i = 0, npixel = 3 * w * h; i < npixel; i++){ 60 | sum(0,0)[i] += result(0,0)[i]; 61 | } 62 | } 63 | 64 | //save image as test.bmp 65 | image result(w, h); 66 | for(int i = 0, n = 3 * w * h; i < n; i++){ 67 | result(0,0)[i] = (unsigned char) (clamp(pow(float(sum(0,0)[i] / max_iterations ), 1.0f / 2.2f), 0, 1) * 255 ); //gamma_correction 68 | } 69 | save_as_bmp(result, "test.bmp"); 70 | return 0; 71 | } 72 | 73 | /////////////////////////////////////////////////////////////////////////////////////////////////// 74 | -------------------------------------------------------------------------------- /src/inc/sample/our.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef OUR_HPP 5 | #define OUR_HPP 6 | 7 | #include"our/path.hpp" 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////// 10 | 11 | namespace our{ 12 | 13 | /////////////////////////////////////////////////////////////////////////////////////////////////// 14 | //renderer 15 | //To simplify the implementation, we do not use the strategies (s>=2, t=0) 16 | ////////////////////////////////////////////////////////////////////////////////////////////////// 17 | 18 | class renderer 19 | { 20 | public: 21 | 22 | //constructor ( M : number of pre-sampled light sub-paths, nt : number of threads ) 23 | renderer(const scene &scene, const camera &camera, const size_t M, const size_t nt = std::thread::hardware_concurrency()); 24 | 25 | //rendering 26 | imagef render(const scene &scene, const camera &camera); 27 | 28 | private: 29 | 30 | //calculate radiance for pixel (x,y) 31 | col3 radiance(const int x, const int y, const scene &scene, const camera &camera, random_number_generator &rng); 32 | 33 | //calculate contributions of strategies (s=0,t>=2) (i.e., unidirectional path tracing from eye) for Line 10 of Algorithm1 34 | col3 calculate_0t(const scene &scene, const light_path &y, const camera_path &z); 35 | 36 | //calculate resampling estimators (i.e., strategy (s>=1, t>=2)) in Eq. (6) (Lines 11 to 23 of Algorithm1) 37 | col3 calculate_st(const scene &scene, const camera_path &z, random_number_generator &rng); 38 | 39 | //calculate contributions of strategies (s>=1,t=1) (i.e., light tracing) for Line 10 of Algorithm1 40 | void calculate_s1(const scene &scene, const camera &camera, const light_path &y, const camera_path &z, random_number_generator &rng); 41 | 42 | private: 43 | 44 | size_t m_M; 45 | size_t m_nt; 46 | size_t m_ns1; //number of samples for strategy (s>=1,t=1), i.e., widthxheight of the image 47 | float m_Qp; //normalization factor for virtual cache point (uniform distribution) in Sec. 5.2 48 | double m_sum; //sum of Qp for each iteration 49 | double m_ite; //number of iterations 50 | imagef m_buf_s1; //buffer to store contributions of strategy (s>=1,t=1) (i.e., light tracing) 51 | kd_tree m_caches; //cache points. we store cache points in the previous iteration to calculate the normalization factor Q 52 | std::unique_ptr m_locks; //spinlock for exclusive access to m_buf_s1 53 | std::vector m_candidates; //pre-sampled light sub-paths ¥hat{Y} for resampling 54 | std::vector m_light_paths; //light sub-paths for strategies handled by BPT 55 | }; 56 | 57 | /////////////////////////////////////////////////////////////////////////////////////////////////// 58 | 59 | } //namespace our 60 | 61 | /////////////////////////////////////////////////////////////////////////////////////////////////// 62 | 63 | #include"our/renderer-impl.hpp" 64 | 65 | /////////////////////////////////////////////////////////////////////////////////////////////////// 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/inc/sample/our/path/cache-impl.hpp: -------------------------------------------------------------------------------- 1 | 2 | /////////////////////////////////////////////////////////////////////////////////////////////////// 3 | 4 | namespace our{ 5 | 6 | /////////////////////////////////////////////////////////////////////////////////////////////////// 7 | //cache 8 | /////////////////////////////////////////////////////////////////////////////////////////////////// 9 | 10 | //constructor (v: eye sub-path vertex, first_iteration: flag (true for 1st iteration, false otherwise) 11 | inline cache::cache(const camera_path_vertex &v, const bool first_iteration) : camera_path_vertex(v) 12 | { 13 | if(first_iteration){ 14 | m_Q = -1;//for first iteration, normalization factor Q will be estimated in calc_distribution 15 | } 16 | else{ 17 | //estimate of Q is approximated using Q estimated in previous iteration 18 | m_Q = 0; 19 | for(size_t i = 0; i < Nc; i++){ 20 | m_Q += v.neighbor_cache(i).m_Z; //m_Z is estimate of Q using ¥bar{Y}_{n-1} stored at neighbor cache points 21 | } 22 | m_Q /= Nc; 23 | } 24 | } 25 | 26 | /////////////////////////////////////////////////////////////////////////////////////////////////// 27 | 28 | //construct resampling pmf 29 | inline void cache::calc_distribution(const scene &scene, const std::vector &candidates, const size_t M) 30 | { 31 | //construct resampling pmf (q*/p) (Line 5 in Algorithm1) 32 | auto weight = [&](const candidate &c){ 33 | return luminance(c.vertex().Le_throughput() * calc_FGV(scene, c.vertex().intersection(), c.vertex().brdf())); 34 | }; 35 | distribution::operator=( 36 | distribution(candidates, weight) 37 | ); 38 | 39 | //estimate Q using M pre-sampled light sub-paths in current iteration 40 | //m_Z is used in the next iteration (Line 6 in Algorithm1) 41 | m_Z = normalization_constant() / M; //normalization_constant=sum(q*/p) 42 | 43 | //for first iteration ¥hat{Y}_1 is used 44 | if(m_Q == -1){ 45 | m_Q = m_Z; 46 | } 47 | } 48 | 49 | /////////////////////////////////////////////////////////////////////////////////////////////////// 50 | 51 | //calculate F(brdf)*G(geo. term)*V(visibility) at cache point 52 | inline col3 cache::calc_FGV(const scene &scene, const ::intersection &x, const ::brdf &brdf) const 53 | { 54 | const auto &c_isect = camera_path_vertex::intersection(); 55 | 56 | const vec3 tmp_wo = c_isect.p() - x.p(); 57 | const float dist2 = squared_norm(tmp_wo); 58 | const float dist = sqrt(dist2); 59 | const direction wo(tmp_wo / dist, x.n()); 60 | if(wo.is_invalid() || wo.in_lower_hemisphere()){ 61 | return col3(); 62 | } 63 | 64 | const direction wi(-wo, c_isect.n()); 65 | if(wi.is_invalid() || wi.in_lower_hemisphere()){ 66 | return col3(); 67 | } 68 | 69 | //visibility test for V 70 | if(scene.intersect(ray(c_isect.p(), wi, dist)) == false){ 71 | //clamp G term to avoid unstable estimation of Q 72 | //(for glossy BRDFs, it would be better to clamp F*G instead of G only) 73 | return brdf.f(wo) * std::min(wi.abs_cos() * wo.abs_cos() / dist2, G_max); 74 | } 75 | return col3(); 76 | } 77 | 78 | /////////////////////////////////////////////////////////////////////////////////////////////////// 79 | 80 | } //namespace our 81 | 82 | /////////////////////////////////////////////////////////////////////////////////////////////////// 83 | -------------------------------------------------------------------------------- /src/inc/base/image.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef IMAGE_HPP 5 | #define IMAGE_HPP 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | /////////////////////////////////////////////////////////////////////////////////////////////////// 12 | //forward declaration 13 | /////////////////////////////////////////////////////////////////////////////////////////////////// 14 | 15 | template class Image; 16 | using image = Image; 17 | using imagef = Image; 18 | using imaged = Image; 19 | 20 | /////////////////////////////////////////////////////////////////////////////////////////////////// 21 | //Image 22 | /////////////////////////////////////////////////////////////////////////////////////////////////// 23 | 24 | template class Image 25 | { 26 | public: 27 | 28 | Image() : m_width(), m_height(), m_data() 29 | { 30 | } 31 | Image(const int width, const int height) : m_width(width), m_height(height), m_data(3 * width * height) 32 | { 33 | } 34 | 35 | int width() const 36 | { 37 | return m_width; 38 | } 39 | int height() const 40 | { 41 | return m_height; 42 | } 43 | 44 | T *operator()(const int x, const int y) 45 | { 46 | return &m_data[3 * (x + m_width * y)]; 47 | } 48 | const T *operator()(const int x, const int y) const 49 | { 50 | return &m_data[3 * (x + m_width * y)]; 51 | } 52 | 53 | private: 54 | 55 | int m_width; 56 | int m_height; 57 | std::vector m_data; 58 | }; 59 | 60 | 61 | 62 | //save as bitmap 63 | inline void save_as_bmp(const image &img, const std::string &filename) 64 | { 65 | const int width = img.width(); 66 | const int height = img.height(); 67 | 68 | std::ofstream ofs(filename, std::ios::binary); 69 | 70 | const int row_bytes = ((width * 3 + 3) >> 2) << 2; 71 | 72 | const short bfType = 0x4d42; 73 | ofs.write((char*)&bfType, 2); 74 | const int bfSize = 14 + 40 + row_bytes * height; 75 | ofs.write((char*)&bfSize, 4); 76 | const short bfReserved1 = 0; 77 | ofs.write((char*)&bfReserved1, 2); 78 | const short bfReserved2 = 0; 79 | ofs.write((char*)&bfReserved2, 2); 80 | const int bfOffBits = 14 + 40; 81 | ofs.write((char*)&bfOffBits, 4); 82 | 83 | const int biSize = 40; 84 | ofs.write((char*)&biSize, 4); 85 | const int biWidth = width; 86 | ofs.write((char*)&biWidth, 4); 87 | const int biHeight = height; 88 | ofs.write((char*)&biHeight, 4); 89 | const short biPlanes = 1; 90 | ofs.write((char*)&biPlanes, 2); 91 | const short biBitCount = 24; 92 | ofs.write((char*)&biBitCount, 2); 93 | const int biCompression = 0; 94 | ofs.write((char*)&biCompression, 4); 95 | const int biSizeImage = 0; 96 | ofs.write((char*)&biSizeImage, 4); 97 | const int biXPelsPerMeter = 0; 98 | ofs.write((char*)&biXPelsPerMeter, 4); 99 | const int biYPelsPerMeter = 0; 100 | ofs.write((char*)&biYPelsPerMeter, 4); 101 | const int biClrUsed = 0; 102 | ofs.write((char*)&biClrUsed, 4); 103 | const int biClrImportant = 0; 104 | ofs.write((char*)&biClrImportant, 4); 105 | 106 | std::vector scanline( 107 | row_bytes 108 | ); 109 | for(int y = 0; y < height; y++){ 110 | for(int x = 0; x < width; x++){ 111 | scanline[3 * x + 0] = img(x, y)[2]; //r 112 | scanline[3 * x + 1] = img(x, y)[1]; //g 113 | scanline[3 * x + 2] = img(x, y)[0]; //b 114 | } 115 | ofs.write((char*)scanline.data(), row_bytes); 116 | } 117 | } 118 | 119 | /////////////////////////////////////////////////////////////////////////////////////////////////// 120 | 121 | #endif 122 | -------------------------------------------------------------------------------- /src/inc/base/material.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef MATERIAL_HPP 5 | #define MATERIAL_HPP 6 | 7 | #include"rng.hpp" 8 | #include"math.hpp" 9 | #include"direction.hpp" 10 | #include"intersection.hpp" 11 | 12 | //brdf & material classes 13 | //this implementation handles only diffuse BRDF and diffuse light 14 | 15 | /////////////////////////////////////////////////////////////////////////////////////////////////// 16 | //brdf_sample 17 | /////////////////////////////////////////////////////////////////////////////////////////////////// 18 | 19 | class brdf_sample 20 | { 21 | public: 22 | 23 | brdf_sample() : m_pdf() 24 | { 25 | } 26 | 27 | brdf_sample(const direction &w, const col3 &f, const float pdf) : m_w(w), m_f(f), m_pdf(pdf) 28 | { 29 | assert(pdf > 0); 30 | } 31 | 32 | const direction &w() const 33 | { 34 | return assert(is_valid()), m_w; 35 | } 36 | 37 | const col3 &f() const 38 | { 39 | return assert(is_valid()), m_f; 40 | } 41 | 42 | float pdf() const 43 | { 44 | return assert(is_valid()), m_pdf; 45 | } 46 | 47 | bool is_valid() const 48 | { 49 | return (m_pdf > 0); 50 | } 51 | bool is_invalid() const 52 | { 53 | return !(m_pdf > 0); 54 | } 55 | 56 | private: 57 | 58 | direction m_w; 59 | col3 m_f; 60 | float m_pdf; 61 | }; 62 | 63 | /////////////////////////////////////////////////////////////////////////////////////////////////// 64 | //brdf 65 | /////////////////////////////////////////////////////////////////////////////////////////////////// 66 | 67 | class brdf 68 | { 69 | public: 70 | 71 | brdf(const intersection &x, const direction &w, const col3 &kd) : m_f(kd / PI()), m_n(x.n()) 72 | { 73 | if(abs(m_n.x) < abs(m_n.y)){ 74 | m_t = normalize(vec3(0, m_n.z, -m_n.y)); 75 | }else{ 76 | m_t = normalize(vec3(-m_n.z, 0, m_n.x)); 77 | } 78 | m_b = cross(m_n, m_t); 79 | } 80 | brdf() = default; 81 | 82 | //return brdf 83 | //w: incident direction for path tracing, outgoing direction for light tracing 84 | col3 f(const direction &w) const 85 | { 86 | return assert(w.in_upper_hemisphere()), m_f; 87 | } 88 | 89 | //sample direction 90 | //sample incident direction for path tracing, outgoing direction for light tracing 91 | brdf_sample sample(random_number_generator &rng) const 92 | { 93 | const float u1 = rng.generate_uniform_real(); 94 | const float u2 = rng.generate_uniform_real(); 95 | const float ph = (2 * PI()) * u1; 96 | const float ct = sqrt(1 - u2); 97 | const float st = sqrt(u2); 98 | const float cp = cos(ph); 99 | const float sp = sin(ph); 100 | 101 | const direction w( 102 | m_t * (st * cp) + m_b * (st * sp) + m_n * (ct), m_n 103 | ); 104 | return brdf_sample(w, f(w), pdf(w)); 105 | } 106 | 107 | float pdf(const direction &w) const 108 | { 109 | return assert(w.in_upper_hemisphere()), w.abs_cos() / PI(); 110 | } 111 | 112 | private: 113 | 114 | col3 m_f; 115 | vec3 m_t; 116 | vec3 m_b; 117 | vec3 m_n; 118 | }; 119 | 120 | /////////////////////////////////////////////////////////////////////////////////////////////////// 121 | //material 122 | /////////////////////////////////////////////////////////////////////////////////////////////////// 123 | 124 | class material 125 | { 126 | public: 127 | 128 | material(const col3 &col, const bool is_emissive) : m_col(col), m_is_emissive(is_emissive) 129 | { 130 | } 131 | 132 | brdf make_brdf(const intersection &x, const direction &w) const 133 | { 134 | return is_emissive() ? brdf(x, w, col3(1)) : brdf(x, w, m_col); 135 | } 136 | 137 | col3 Le(const intersection &x, const direction &w) const 138 | { 139 | return assert(is_emissive()), m_col * brdf(x, w, col3(1)).f(w); 140 | } 141 | col3 Me() const 142 | { 143 | return assert(is_emissive()), m_col; 144 | } 145 | 146 | bool is_emissive() const 147 | { 148 | return m_is_emissive; 149 | } 150 | 151 | private: 152 | 153 | col3 m_col; 154 | bool m_is_emissive; 155 | }; 156 | 157 | /////////////////////////////////////////////////////////////////////////////////////////////////// 158 | 159 | #endif 160 | -------------------------------------------------------------------------------- /src/inc/base/math/vec4.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef VEC4_HPP 5 | #define VEC4_HPP 6 | 7 | #include"vec3.hpp" 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////// 10 | //vec4 (four-dimensional vector class) 11 | /////////////////////////////////////////////////////////////////////////////////////////////////// 12 | 13 | class vec4 14 | { 15 | public: 16 | 17 | //constructors 18 | vec4() : x(), y(), z(), w() 19 | { 20 | } 21 | vec4(const float s) : x(s), y(s), z(s), w(s) 22 | { 23 | } 24 | vec4(const vec3 &v, const float w) : x(v.x), y(v.y), z(v.z), w(w) 25 | { 26 | } 27 | vec4(const float x, const float y, const float z, const float w) : x(x), y(y), z(z), w(w) 28 | { 29 | } 30 | 31 | //assignment operators 32 | vec4 &operator+=(const vec4 &v) 33 | { 34 | x += v.x; y += v.y; z += v.z; w += v.w; return *this; 35 | } 36 | vec4 &operator-=(const vec4 &v) 37 | { 38 | x -= v.x; y -= v.y; z -= v.z; w -= v.w; return *this; 39 | } 40 | vec4 &operator*=(const vec4 &v) 41 | { 42 | x *= v.x; y *= v.y; z *= v.z; w *= v.w; return *this; 43 | } 44 | vec4 &operator/=(const vec4 &v) 45 | { 46 | x /= v.x; y /= v.y; z /= v.z; w /= v.w; return *this; 47 | } 48 | vec4 &operator*=(const float s) 49 | { 50 | x *= s; y *= s; z *= s; w *= s; return *this; 51 | } 52 | vec4 &operator/=(const float s) 53 | { 54 | x /= s; y /= s; z /= s; w /= s; return *this; 55 | } 56 | 57 | //binary operators 58 | vec4 operator+(const vec4 &v) const 59 | { 60 | return vec4(x + v.x, y + v.y, z + v.z, w + v.w); 61 | } 62 | vec4 operator-(const vec4 &v) const 63 | { 64 | return vec4(x - v.x, y - v.y, z - v.z, w - v.w); 65 | } 66 | vec4 operator*(const vec4 &v) const 67 | { 68 | return vec4(x * v.x, y * v.y, z * v.z, w * v.w); 69 | } 70 | vec4 operator/(const vec4 &v) const 71 | { 72 | return vec4(x / v.x, y / v.y, z / v.z, w / v.w); 73 | } 74 | vec4 operator*(const float s) const 75 | { 76 | return vec4(x * s, y * s, z * s, w * s); 77 | } 78 | vec4 operator/(const float s) const 79 | { 80 | return vec4(x / s, y / s, z / s, w / s); 81 | } 82 | friend vec4 operator*(const float s, const vec4 &v) 83 | { 84 | return v * s; 85 | } 86 | 87 | //unitary operator 88 | vec4 operator+() const 89 | { 90 | return *this; 91 | } 92 | vec4 operator-() const 93 | { 94 | return vec4(-x, -y, -z, -w); 95 | } 96 | 97 | //accessor 98 | float &operator[](const size_t i) 99 | { 100 | return vals[i]; 101 | } 102 | const float &operator[](const size_t i) const 103 | { 104 | return vals[i]; 105 | } 106 | 107 | //cast to three-dimensional vector class vec3 108 | explicit operator vec3() const 109 | { 110 | return vec3(x, y, z); 111 | } 112 | 113 | public: 114 | 115 | union{ 116 | struct{ 117 | float x, y, z, w; 118 | }; 119 | float vals[4]; 120 | }; 121 | }; 122 | 123 | /////////////////////////////////////////////////////////////////////////////////////////////////// 124 | //function definitions 125 | /////////////////////////////////////////////////////////////////////////////////////////////////// 126 | 127 | //dot product (inner product) 128 | inline float dot(const vec4 &v1, const vec4 &v2) 129 | { 130 | return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z + v1.w * v2.w; 131 | } 132 | 133 | /////////////////////////////////////////////////////////////////////////////////////////////////// 134 | 135 | //squared L2 norm 136 | inline float squared_norm(const vec4 &v) 137 | { 138 | return dot(v, v); 139 | } 140 | 141 | /////////////////////////////////////////////////////////////////////////////////////////////////// 142 | 143 | //L2 norm 144 | inline float norm(const vec4 &v) 145 | { 146 | return sqrt(squared_norm(v)); 147 | } 148 | 149 | /////////////////////////////////////////////////////////////////////////////////////////////////// 150 | 151 | //normalization 152 | inline vec4 normalize(const vec4 &v) 153 | { 154 | return v / norm(v); 155 | } 156 | 157 | /////////////////////////////////////////////////////////////////////////////////////////////////// 158 | 159 | #endif 160 | -------------------------------------------------------------------------------- /src/inc/base/camera.hpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #pragma once 5 | 6 | #ifndef CAMERA_HPP 7 | #define CAMERA_HPP 8 | 9 | #include"ray.hpp" 10 | #include"rng.hpp" 11 | 12 | /////////////////////////////////////////////////////////////////////////////////////////////////// 13 | //camera 14 | /////////////////////////////////////////////////////////////////////////////////////////////////// 15 | 16 | class camera 17 | { 18 | public: 19 | 20 | camera(const vec3 &eye, const vec3 ¢er, const int res_x, const int res_y, const float fovy, const float lens_radius = 1e-1f) : m_p(eye), m_res_x(res_x), m_res_y(res_y), m_fovy(fovy), m_lens_radius(lens_radius) 21 | { 22 | m_n = center - eye; 23 | m_focus = norm(m_n); 24 | m_n /= m_focus; 25 | 26 | const float theta = asin(m_n.y); 27 | const float phi = atan2(-m_n.x, -m_n.z); 28 | const float st = sin(theta); 29 | const float ct = cos(theta); 30 | const float sp = sin(phi); 31 | const float cp = cos(phi); 32 | m_b = vec3(st * sp, ct, st * cp); 33 | m_t = normalize(cross(m_n, m_b)); 34 | 35 | const float aspect = res_x / float(res_y); 36 | m_screen_size_y = 2 * m_focus * tan(conv_deg_to_rad(fovy * 0.5f)); 37 | m_screen_size_x = m_screen_size_y * aspect; 38 | m_screen_min = center - m_t * (m_screen_size_x * 0.5f) - m_b * (m_screen_size_y * 0.5f); 39 | 40 | m_stow = inv_look_at(eye, center, m_b) * inv_perspective(fovy, aspect, m_focus, m_focus * 1.1f) * inv_viewport(0, 0, res_x, res_y); 41 | 42 | if(lens_radius > 0){ 43 | m_pdf_lens = 1 / (PI() * lens_radius * lens_radius); 44 | }else{ 45 | m_pdf_lens = 1; 46 | } 47 | 48 | m_pdf_pixel = res_x * res_y * m_focus * m_focus / (m_screen_size_x * m_screen_size_y); 49 | } 50 | 51 | ray sample(const int x, const int y, random_number_generator &rng) const 52 | { 53 | const float u1 = rng.generate_uniform_real(); 54 | const float u2 = rng.generate_uniform_real(); 55 | const float u3 = rng.generate_uniform_real(); 56 | const float u4 = rng.generate_uniform_real(); 57 | 58 | const float r = m_lens_radius * sqrt(u1); 59 | const float th = 2 * PI() * u2; 60 | const float st = sin(th); 61 | const float ct = cos(th); 62 | const vec3 org = m_t * (r * ct) + m_b * (r * st) + m_p; 63 | 64 | const vec3 dst = mat_mul_vec_div_w(m_stow, vec4(x + u3, y + u4, 0, 1)); 65 | const vec3 dir = normalize(dst - org); 66 | return ray(org, dir); 67 | } 68 | 69 | struct intersection{ 70 | int x, y; bool is_valid; 71 | }; 72 | intersection calc_intersection(const vec3 &lens_p, const direction &lens_wi) const 73 | { 74 | const float t = m_focus / lens_wi.cos(); 75 | const vec3 screen_p = lens_p + vec3(lens_wi) * t; 76 | const int pixel_x = int(dot(screen_p - m_screen_min, m_t) * m_res_x / m_screen_size_x); 77 | const int pixel_y = int(dot(screen_p - m_screen_min, m_b) * m_res_y / m_screen_size_y); 78 | 79 | if((0 <= pixel_x) && (pixel_x < m_res_x)){ 80 | if((0 <= pixel_y) && (pixel_y < m_res_y)){ 81 | return intersection{pixel_x, pixel_y, true}; 82 | } 83 | } 84 | return intersection{-1, -1, false}; 85 | } 86 | 87 | float We(const direction &w) const 88 | { 89 | const float cos2 = pow(w.abs_cos(), 2); 90 | return m_pdf_lens * m_pdf_pixel / (cos2 * cos2); 91 | } 92 | 93 | float pdf_o() const 94 | { 95 | return m_pdf_lens; 96 | } 97 | 98 | float pdf_d(const direction &w) const 99 | { 100 | return m_pdf_pixel / (w.cos() * w.cos() * w.cos()); 101 | } 102 | 103 | const vec3 &p() const 104 | { 105 | return m_p; 106 | } 107 | const vec3 &d() const 108 | { 109 | return m_n; 110 | } 111 | 112 | int res_x() const 113 | { 114 | return m_res_x; 115 | } 116 | int res_y() const 117 | { 118 | return m_res_y; 119 | } 120 | 121 | float fovy() const 122 | { 123 | return m_fovy; 124 | } 125 | 126 | float lens_radius() const 127 | { 128 | return m_lens_radius; 129 | } 130 | 131 | private: 132 | 133 | vec3 m_p; 134 | vec3 m_t; 135 | vec3 m_b; 136 | vec3 m_n; 137 | mat4 m_stow; 138 | vec3 m_screen_min; 139 | int m_res_x; 140 | int m_res_y; 141 | float m_fovy; 142 | float m_focus; 143 | float m_pdf_lens; 144 | float m_pdf_pixel; 145 | float m_lens_radius; 146 | float m_screen_size_x; 147 | float m_screen_size_y; 148 | }; 149 | 150 | /////////////////////////////////////////////////////////////////////////////////////////////////// 151 | 152 | #endif 153 | -------------------------------------------------------------------------------- /src/inc/base/math/vec3.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef VEC3_HPP 5 | #define VEC3_HPP 6 | 7 | #include 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////// 10 | //forward declaration 11 | /////////////////////////////////////////////////////////////////////////////////////////////////// 12 | 13 | class vec3; 14 | using col3 = vec3; 15 | 16 | /////////////////////////////////////////////////////////////////////////////////////////////////// 17 | //vec3 18 | /////////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | class vec3 21 | { 22 | public: 23 | 24 | //constructors 25 | vec3() : x(), y(), z() 26 | { 27 | } 28 | vec3(const float s) : x(s), y(s), z(s) 29 | { 30 | } 31 | vec3(const float x, const float y, const float z) : x(x), y(y), z(z) 32 | { 33 | } 34 | 35 | //assignment operators 36 | vec3 &operator+=(const vec3 &v) 37 | { 38 | x += v.x; y += v.y; z += v.z; return *this; 39 | } 40 | vec3 &operator-=(const vec3 &v) 41 | { 42 | x -= v.x; y -= v.y; z -= v.z; return *this; 43 | } 44 | vec3 &operator*=(const vec3 &v) 45 | { 46 | x *= v.x; y *= v.y; z *= v.z; return *this; 47 | } 48 | vec3 &operator/=(const vec3 &v) 49 | { 50 | x /= v.x; y /= v.y; z /= v.z; return *this; 51 | } 52 | vec3 &operator*=(const float s) 53 | { 54 | x *= s; y *= s; z *= s; return *this; 55 | } 56 | vec3 &operator/=(const float s) 57 | { 58 | x /= s; y /= s; z /= s; return *this; 59 | } 60 | 61 | //binary operators 62 | vec3 operator+(const vec3 &v) const 63 | { 64 | return vec3(x + v.x, y + v.y, z + v.z); 65 | } 66 | vec3 operator-(const vec3 &v) const 67 | { 68 | return vec3(x - v.x, y - v.y, z - v.z); 69 | } 70 | vec3 operator*(const vec3 &v) const 71 | { 72 | return vec3(x * v.x, y * v.y, z * v.z); 73 | } 74 | vec3 operator/(const vec3 &v) const 75 | { 76 | return vec3(x / v.x, y / v.y, z / v.z); 77 | } 78 | vec3 operator*(const float s) const 79 | { 80 | return vec3(x * s, y * s, z * s); 81 | } 82 | vec3 operator/(const float s) const 83 | { 84 | return vec3(x / s, y / s, z / s); 85 | } 86 | friend vec3 operator*(const float s, const vec3 &v) 87 | { 88 | return v * s; 89 | } 90 | 91 | //unitary operators 92 | vec3 operator+() const 93 | { 94 | return *this; 95 | } 96 | vec3 operator-() const 97 | { 98 | return vec3(-x, -y, -z); 99 | } 100 | 101 | //accessor 102 | float &operator[](const size_t i) 103 | { 104 | return vals[i]; 105 | } 106 | const float &operator[](const size_t i) const 107 | { 108 | return vals[i]; 109 | } 110 | 111 | public: 112 | 113 | union{ 114 | struct{ 115 | float x, y, z; 116 | }; 117 | float vals[3]; 118 | }; 119 | }; 120 | 121 | /////////////////////////////////////////////////////////////////////////////////////////////////// 122 | //function definitions 123 | /////////////////////////////////////////////////////////////////////////////////////////////////// 124 | 125 | //dot product (inner product) 126 | inline float dot(const vec3 &v1, const vec3 &v2) 127 | { 128 | return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; 129 | } 130 | 131 | /////////////////////////////////////////////////////////////////////////////////////////////////// 132 | 133 | //cross product (outer product) 134 | inline vec3 cross(const vec3 &v1, const vec3 &v2) 135 | { 136 | return vec3(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x); 137 | } 138 | 139 | /////////////////////////////////////////////////////////////////////////////////////////////////// 140 | 141 | //squared L2 norm 142 | inline float squared_norm(const vec3 &v) 143 | { 144 | return dot(v, v); 145 | } 146 | 147 | /////////////////////////////////////////////////////////////////////////////////////////////////// 148 | 149 | //L2 norm 150 | inline float norm(const vec3 &v) 151 | { 152 | return sqrt(squared_norm(v)); 153 | } 154 | 155 | /////////////////////////////////////////////////////////////////////////////////////////////////// 156 | 157 | //normalization 158 | inline vec3 normalize(const vec3 &v) 159 | { 160 | return v / norm(v); 161 | } 162 | 163 | /////////////////////////////////////////////////////////////////////////////////////////////////// 164 | 165 | //convert RGB color into luminance 166 | inline float luminance(const col3 &c) 167 | { 168 | return 0.2126f * c[0] + 0.7152f * c[1] + 0.0722f * c[2]; 169 | } 170 | 171 | /////////////////////////////////////////////////////////////////////////////////////////////////// 172 | 173 | #endif 174 | -------------------------------------------------------------------------------- /src/inc/base/kd_tree.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef KD_TREE_HPP 5 | #define KD_TREE_HPP 6 | 7 | #include 8 | #include 9 | #include"math.hpp" 10 | 11 | /////////////////////////////////////////////////////////////////////////////////////////////////// 12 | //neighbor 13 | /////////////////////////////////////////////////////////////////////////////////////////////////// 14 | 15 | template class neighbor 16 | { 17 | public: 18 | 19 | neighbor(const T &elem, const float d2) : mp_elem(&elem), m_d2(d2) 20 | { 21 | } 22 | 23 | float d2() const 24 | { 25 | return m_d2; 26 | } 27 | 28 | const T &operator*() const 29 | { 30 | return *mp_elem; 31 | } 32 | const T *operator->() const 33 | { 34 | return mp_elem; 35 | } 36 | 37 | private: 38 | 39 | const T *mp_elem; 40 | float m_d2; 41 | }; 42 | 43 | /////////////////////////////////////////////////////////////////////////////////////////////////// 44 | //kd_tree 45 | /////////////////////////////////////////////////////////////////////////////////////////////////// 46 | 47 | template class kd_tree 48 | { 49 | public: 50 | 51 | struct node{ 52 | node() = default; 53 | node(node&&) = default; 54 | node(const node&) = delete; 55 | node &operator=(node&&) = default; 56 | node &operator=(const node&) = delete; 57 | node(const vec3 &p, const int k, T &&elem) : p(p), k(k){ 58 | new (&storage) T(std::move(elem)); 59 | } 60 | ~node(){ 61 | reinterpret_cast(storage).~T(); 62 | } 63 | operator const T&() const{ 64 | return reinterpret_cast(storage); 65 | } 66 | vec3 p; int k; std::aligned_storage_t storage; 67 | }; 68 | 69 | //elems: set of elements, point: function object that returns position 70 | template kd_tree(std::vector elems, Point point) : m_nodes(elems.size()) 71 | { 72 | auto implement = [&](const size_t idx, auto first, auto last, const int depth, auto *This) -> void 73 | { 74 | const size_t num = last - first; 75 | if(num == 1){ 76 | m_nodes[idx] = node(point(*first), -1, std::move(*first)); 77 | } else{ 78 | const int k = depth % 3; 79 | 80 | const size_t subtree_height = size_t(ceil(log2(num + 1))) - 1; 81 | auto mid = first + std::min((size_t(1) << subtree_height) - 1, num - (size_t(1) << (subtree_height - 1))); 82 | 83 | std::nth_element(first, mid, last, [&, k](const T &a, const T &b){ 84 | return (point(a)[k] < point(b)[k]); 85 | }); 86 | 87 | m_nodes[idx] = node(point(*mid), k, std::move(*mid)); 88 | 89 | { 90 | (*This)(2 * idx + 1, first, mid, depth + 1, This); 91 | } 92 | if(++mid != last){ 93 | (*This)(2 * idx + 2, mid, last, depth + 1, This); 94 | } 95 | } 96 | }; 97 | implement(0, elems.begin(), elems.end(), 0, &implement); 98 | } 99 | kd_tree() = default; 100 | 101 | //p: query point, r: query radius, n: number of elements, neighbors: store neighbor elements 102 | void find_nearest(const vec3 &p, const float r, const size_t n, std::vector> &neighbors) const 103 | { 104 | float r2 = r * r; 105 | auto implement = [&, p, n, this](const size_t idx, auto *This) -> void 106 | { 107 | if(idx >= m_nodes.size()){ 108 | return; 109 | } 110 | const node &node = m_nodes[idx]; 111 | 112 | const vec3 diff( 113 | p - node.p 114 | ); 115 | if(2 * idx + 1 < m_nodes.size()){ 116 | const float diff1_k = diff[node.k]; 117 | const float diff2_k = diff[node.k] * diff[node.k]; 118 | 119 | if(diff1_k < 0){ 120 | (*This)(2 * idx + 1, This); if(diff2_k < r2){ (*This)(2 * idx + 2, This); } 121 | } else{ 122 | (*This)(2 * idx + 2, This); if(diff2_k < r2){ (*This)(2 * idx + 1, This); } 123 | } 124 | } 125 | 126 | const float d2 = squared_norm(diff); 127 | 128 | if(d2 < r2){ 129 | 130 | neighbors.emplace_back(node, d2); 131 | 132 | auto pred = [](const auto &a, const auto &b){ 133 | return (a.d2() < b.d2()); 134 | }; 135 | if(neighbors.size() > n){ 136 | std::push_heap(neighbors.begin(), neighbors.end(), pred); 137 | std:: pop_heap(neighbors.begin(), neighbors.end(), pred); neighbors.pop_back(); 138 | r2 = neighbors.front().d2(); 139 | } 140 | else if(neighbors.size() == n){ 141 | std::make_heap(neighbors.begin(), neighbors.end(), pred); 142 | r2 = neighbors.front().d2(); 143 | } 144 | } 145 | }; 146 | neighbors.clear(); 147 | implement(0, &implement); 148 | 149 | if(neighbors.size() < n){ 150 | std::make_heap(neighbors.begin(), neighbors.end(), [](const auto &a, const auto &b){ return (a.d2() < b.d2()); }); 151 | } 152 | } 153 | 154 | typename std::vector::const_iterator begin() const 155 | { 156 | return m_nodes.begin(); 157 | } 158 | typename std::vector::const_iterator end() const 159 | { 160 | return m_nodes.end(); 161 | } 162 | 163 | private: 164 | 165 | std::vector m_nodes; 166 | }; 167 | 168 | /////////////////////////////////////////////////////////////////////////////////////////////////// 169 | 170 | #endif 171 | -------------------------------------------------------------------------------- /src/inc/sample/our/path_vertex.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef OUR_PATH_VERTEX_HPP 5 | #define OUR_PATH_VERTEX_HPP 6 | 7 | #include 8 | #include"../../base.hpp" 9 | 10 | /////////////////////////////////////////////////////////////////////////////////////////////////// 11 | 12 | namespace our{ 13 | 14 | /////////////////////////////////////////////////////////////////////////////////////////////////// 15 | //forward declaration 16 | /////////////////////////////////////////////////////////////////////////////////////////////////// 17 | 18 | class cache; 19 | 20 | /////////////////////////////////////////////////////////////////////////////////////////////////// 21 | //constant parameters 22 | /////////////////////////////////////////////////////////////////////////////////////////////////// 23 | 24 | //number of nearest cache points (Nc in Sec 5.2) 25 | static const size_t Nc = 3; 26 | 27 | /////////////////////////////////////////////////////////////////////////////////////////////////// 28 | //light_path_vertex 29 | /////////////////////////////////////////////////////////////////////////////////////////////////// 30 | 31 | class light_path_vertex 32 | { 33 | public: 34 | 35 | //isect: intersection point, brdf: BRDF at isect, wi/wo: incident/outgoing directions, Le_throughput: emittance Le*throughput_weight 36 | light_path_vertex(const intersection &isect, const brdf &brdf, const direction &wi, const direction &wo, const col3 &Le_throughput, const float pdf) : m_brdf(brdf), m_isect(isect), m_wi(wi), m_wo(wo), m_Le_throughput(Le_throughput), m_pdf_fwd(pdf) 37 | { 38 | for(size_t i = 0; i < Nc; i++){ 39 | m_cache_ptrs[i] = nullptr; m_Le_throughput_FGVc[i] = -1; 40 | } 41 | m_pdf_bwd = -1; 42 | m_pdf_bwd_rr = -1; 43 | } 44 | 45 | //set cache point c 46 | void set_neighbor_cache(const size_t i, const cache &c) 47 | { 48 | m_cache_ptrs[i] = &c; 49 | } 50 | 51 | //return i-th nearest cache point 52 | const cache &neighbor_cache(const size_t i) const 53 | { 54 | return assert(m_cache_ptrs[i] != nullptr), *m_cache_ptrs[i]; 55 | } 56 | 57 | //set q*/p at i-th cache point (q* in Eq. (15)) 58 | void set_Le_throughput_FGVc(const size_t i, const col3 &Le_throughput_FGVc) 59 | { 60 | m_Le_throughput_FGVc[i] = luminance(Le_throughput_FGVc); 61 | } 62 | 63 | //return q*/p at i-th cache point 64 | float Le_throughput_FGVc(const size_t i) const 65 | { 66 | return assert(m_Le_throughput_FGVc[i] != -1), m_Le_throughput_FGVc[i]; 67 | } 68 | 69 | void set_pdf_bwd(const float pdf_bwd) 70 | { 71 | m_pdf_bwd = pdf_bwd; 72 | } 73 | void set_pdf_bwd_rr(const float pdf_bwd_rr) 74 | { 75 | m_pdf_bwd_rr = pdf_bwd_rr; 76 | } 77 | 78 | //return Le*throughput 79 | const col3 &Le_throughput() const 80 | { 81 | return m_Le_throughput; 82 | } 83 | 84 | //return pdf in forward direction (from light source) 85 | float pdf_fwd() const 86 | { 87 | return m_pdf_fwd; 88 | } 89 | //return pdf in backward direction (from eye) 90 | float pdf_bwd() const 91 | { 92 | return assert(m_pdf_bwd != -1), m_pdf_bwd; 93 | } 94 | float pdf_bwd_rr() const 95 | { 96 | return assert(m_pdf_bwd_rr != -1), m_pdf_bwd_rr; 97 | } 98 | 99 | const direction &wi() const 100 | { 101 | return m_wi; 102 | } 103 | const direction &wo() const 104 | { 105 | return m_wo; 106 | } 107 | 108 | const brdf &brdf() const 109 | { 110 | return m_brdf; 111 | } 112 | 113 | const intersection &intersection() const 114 | { 115 | return m_isect; 116 | } 117 | 118 | private: 119 | 120 | ::brdf m_brdf; 121 | ::intersection m_isect; 122 | direction m_wi; 123 | direction m_wo; 124 | col3 m_Le_throughput; 125 | float m_Le_throughput_FGVc[Nc]; 126 | float m_pdf_fwd; 127 | float m_pdf_bwd; 128 | float m_pdf_bwd_rr; 129 | const cache *m_cache_ptrs[Nc]; 130 | }; 131 | 132 | /////////////////////////////////////////////////////////////////////////////////////////////////// 133 | //camera_path_vertex 134 | /////////////////////////////////////////////////////////////////////////////////////////////////// 135 | 136 | class camera_path_vertex 137 | { 138 | public: 139 | 140 | //isect: intersection point, brdf: brdf at isect, wo&wi outgoing&incident directions, throughput_We: throughput * importance / PDF, 141 | camera_path_vertex(const intersection &isect, const brdf &brdf, const direction &wo, const direction &wi, const col3 &throughput_We, const float pdf) : m_brdf(brdf), m_isect(isect), m_wo(wo), m_wi(wi), m_throughput_We(throughput_We), m_pdf_fwd(pdf) 142 | { 143 | //initialize m_cache_ptrs & m_FGVc 144 | for(size_t i = 0; i < Nc; i++){ 145 | m_cache_ptrs[i] = nullptr; m_FGVc[i][0] = -1; 146 | } 147 | m_FG_bwd[0] = -1; 148 | m_pdf_bwd = -1; 149 | m_pdf_bwd_rr = -1; //pdf including russian roulette probability 150 | } 151 | 152 | //set cache point c 153 | void set_neighbor_cache(const size_t i, const cache &c) 154 | { 155 | m_cache_ptrs[i] = &c; 156 | } 157 | 158 | //return i-th nearest cache point 159 | const cache &neighbor_cache(const size_t i) const 160 | { 161 | return assert(m_cache_ptrs[i] != nullptr), *m_cache_ptrs[i]; 162 | } 163 | 164 | //return F(brdf) x G(geo term) x V(visibility) stored at cache points 165 | const std::array &FGVc() const 166 | { 167 | return m_FGVc; 168 | } 169 | std::array &FGVc() 170 | { 171 | return m_FGVc; 172 | } 173 | 174 | void set_FG_bwd(const col3 &FG_bwd) 175 | { 176 | m_FG_bwd = FG_bwd; 177 | } 178 | 179 | const col3 &FG_bwd() const 180 | { 181 | return assert(m_FG_bwd[0] != -1), m_FG_bwd; 182 | } 183 | 184 | void set_pdf_bwd(const float pdf_bwd) 185 | { 186 | m_pdf_bwd = pdf_bwd; 187 | } 188 | void set_pdf_bwd_rr(const float pdf_bwd_rr) 189 | { 190 | m_pdf_bwd_rr = pdf_bwd_rr; 191 | } 192 | 193 | //return throughput x importance(W_e) 194 | const col3 &throughput_We() const 195 | { 196 | return m_throughput_We; 197 | } 198 | 199 | //return pdfs 200 | float pdf_fwd() const 201 | { 202 | return m_pdf_fwd; 203 | } 204 | float pdf_bwd() const 205 | { 206 | return assert(m_pdf_bwd != -1), m_pdf_bwd; 207 | } 208 | float pdf_bwd_rr() const 209 | { 210 | return assert(m_pdf_bwd_rr != -1), m_pdf_bwd_rr; 211 | } 212 | 213 | const direction &wi() const 214 | { 215 | return m_wi; 216 | } 217 | const direction &wo() const 218 | { 219 | return m_wo; 220 | } 221 | 222 | const brdf &brdf() const 223 | { 224 | return m_brdf; 225 | } 226 | 227 | const intersection &intersection() const 228 | { 229 | return m_isect; 230 | } 231 | 232 | private: 233 | 234 | ::brdf m_brdf; 235 | ::intersection m_isect; 236 | direction m_wo; 237 | direction m_wi; 238 | col3 m_FG_bwd; 239 | col3 m_throughput_We; 240 | float m_pdf_fwd; 241 | float m_pdf_bwd; 242 | float m_pdf_bwd_rr; 243 | const cache *m_cache_ptrs[Nc]; 244 | std::array m_FGVc; 245 | }; 246 | 247 | /////////////////////////////////////////////////////////////////////////////////////////////////// 248 | 249 | } //namespace our 250 | 251 | /////////////////////////////////////////////////////////////////////////////////////////////////// 252 | 253 | #endif 254 | -------------------------------------------------------------------------------- /src/inc/base/math/mat4.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef MAT4_HPP 5 | #define MAT4_HPP 6 | 7 | #include"vec4.hpp" 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////// 10 | //mat4 11 | /*///////////////////////////////////////////////////////////////////////////////////////////////// 12 | column-major 4x4 matrix class 13 | /////////////////////////////////////////////////////////////////////////////////////////////////*/ 14 | 15 | class mat4 16 | { 17 | public: 18 | 19 | //constructors 20 | mat4() : m_c1(), m_c2(), m_c3(), m_c4() 21 | { 22 | } 23 | mat4(const vec4 &c1, const vec4 &c2, const vec4 &c3, const vec4 &c4) : m_c1(c1), m_c2(c2), m_c3(c3), m_c4(c4) 24 | { 25 | } 26 | mat4( 27 | const float _11, const float _12, const float _13, const float _14, 28 | const float _21, const float _22, const float _23, const float _24, 29 | const float _31, const float _32, const float _33, const float _34, 30 | const float _41, const float _42, const float _43, const float _44) : m_c1(_11, _21, _31, _41), m_c2(_12, _22, _32, _42), m_c3(_13, _23, _33, _43), m_c4(_14, _24, _34, _44) 31 | { 32 | } 33 | 34 | //assignment operators 35 | mat4 &operator+=(const mat4 &m) 36 | { 37 | m_c1 += m.m_c1; m_c2 += m.m_c2; m_c3 += m.m_c3; m_c4 += m.m_c4; return *this; 38 | } 39 | mat4 &operator-=(const mat4 &m) 40 | { 41 | m_c1 -= m.m_c1; m_c2 -= m.m_c2; m_c3 -= m.m_c3; m_c4 -= m.m_c4; return *this; 42 | } 43 | mat4 &operator*=(const mat4 &m) 44 | { 45 | const vec4 c1 = (*this) * m.m_c1; 46 | const vec4 c2 = (*this) * m.m_c2; 47 | const vec4 c3 = (*this) * m.m_c3; 48 | const vec4 c4 = (*this) * m.m_c4; 49 | m_c1 = c1; m_c2 = c2; m_c3 = c3; m_c4 = c4; return *this; 50 | } 51 | mat4 &operator*=(const float s) 52 | { 53 | m_c1 *= s; m_c2 *= s; m_c3 *= s; m_c4 *= s; return *this; 54 | } 55 | mat4 &operator/=(const float s) 56 | { 57 | m_c1 /= s; m_c2 /= s; m_c3 /= s; m_c4 /= s; return *this; 58 | } 59 | 60 | //binary operators 61 | mat4 operator+(const mat4 &m) const 62 | { 63 | return mat4(m_c1 + m.m_c1, m_c2 + m.m_c2, m_c3 + m.m_c3, m_c4 + m.m_c4); 64 | } 65 | mat4 operator-(const mat4 &m) const 66 | { 67 | return mat4(m_c1 - m.m_c1, m_c2 - m.m_c2, m_c3 - m.m_c3, m_c4 - m.m_c4); 68 | } 69 | mat4 operator*(const mat4 &m) const 70 | { 71 | return mat4((*this) * m.m_c1, (*this) * m.m_c2, (*this) * m.m_c3, (*this) * m.m_c4); 72 | } 73 | vec4 operator*(const vec4 &v) const 74 | { 75 | return vec4( 76 | m_c1.x * v.x + m_c2.x * v.y + m_c3.x * v.z + m_c4.x * v.w, 77 | m_c1.y * v.x + m_c2.y * v.y + m_c3.y * v.z + m_c4.y * v.w, 78 | m_c1.z * v.x + m_c2.z * v.y + m_c3.z * v.z + m_c4.z * v.w, 79 | m_c1.w * v.x + m_c2.w * v.y + m_c3.w * v.z + m_c4.w * v.w 80 | ); 81 | } 82 | mat4 operator*(const float s) const 83 | { 84 | return mat4(m_c1 * s, m_c2 * s, m_c3 * s, m_c4 * s); 85 | } 86 | mat4 operator/(const float s) const 87 | { 88 | return mat4(m_c1 / s, m_c2 / s, m_c3 / s, m_c4 / s); 89 | } 90 | friend mat4 operator*(const float s, const mat4 &m) 91 | { 92 | return m * s; 93 | } 94 | 95 | //unitary operators 96 | mat4 operator+() const 97 | { 98 | return *this; 99 | } 100 | mat4 operator-() const 101 | { 102 | return mat4(-m_c1, -m_c2, -m_c3, -m_c4); 103 | } 104 | 105 | private: 106 | 107 | vec4 m_c1; 108 | vec4 m_c2; 109 | vec4 m_c3; 110 | vec4 m_c4; 111 | }; 112 | 113 | /////////////////////////////////////////////////////////////////////////////////////////////////// 114 | //function definition 115 | /////////////////////////////////////////////////////////////////////////////////////////////////// 116 | 117 | 118 | inline vec3 mat_mul_vec_div_w(const mat4 &m, const vec4 &v) 119 | { 120 | const vec4 tmp = m * v; 121 | return vec3(tmp) / tmp.w; 122 | } 123 | 124 | /////////////////////////////////////////////////////////////////////////////////////////////////// 125 | 126 | //calculate view matrix 127 | inline mat4 look_at(const vec3 &eye, const vec3 ¢er, const vec3 &up) 128 | { 129 | const vec3 axis_z = normalize(eye - center); 130 | const vec3 axis_x = normalize(cross(up, axis_z)); 131 | const vec3 axis_y = cross(axis_z, axis_x); 132 | 133 | return mat4( 134 | axis_x.x, axis_x.y, axis_x.z, -dot(eye, axis_x), 135 | axis_y.x, axis_y.y, axis_y.z, -dot(eye, axis_y), 136 | axis_z.x, axis_z.y, axis_z.z, -dot(eye, axis_z), 0, 0, 0, 1 137 | ); 138 | } 139 | 140 | 141 | //calculate inverse matrix of view matrix 142 | inline mat4 inv_look_at(const vec3 &eye, const vec3 ¢er, const vec3 &up) 143 | { 144 | const vec3 axis_z = normalize(eye - center); 145 | const vec3 axis_x = normalize(cross(up, axis_z)); 146 | const vec3 axis_y = cross(axis_z, axis_x); 147 | 148 | return mat4( 149 | axis_x.x, axis_y.x, axis_z.x, eye.x, 150 | axis_x.y, axis_y.y, axis_z.y, eye.y, 151 | axis_x.z, axis_y.z, axis_z.z, eye.z, 0, 0, 0, 1 152 | ); 153 | } 154 | 155 | /////////////////////////////////////////////////////////////////////////////////////////////////// 156 | 157 | //calculate perspective projection matrix 158 | inline mat4 perspective(const float fovy, const float aspect, const float z_near, const float z_far) 159 | { 160 | const float pi = 2 * acos(0.0f); 161 | const float rad = fovy * (pi / 180); 162 | const float cot = 1 / tan(rad * 0.5f); 163 | const float inv_f_n = 1 / (z_far - z_near); 164 | 165 | return mat4( 166 | cot / aspect, 0, 0, 0, 167 | 0, cot, 0, 0, 168 | 0, 0, -(z_far + z_near) * inv_f_n, -(2 * z_far * z_near) * inv_f_n, 169 | 0, 0, -1, 0 170 | ); 171 | } 172 | 173 | 174 | //calculate inverse matrix of perspective-projection matrix 175 | inline mat4 inv_perspective(const float fovy, const float aspect, const float z_near, const float z_far) 176 | { 177 | const float pi = 2 * acos(0.0f); 178 | const float rad = fovy * (pi / 180); 179 | const float tan_ = tan(rad * 0.5f); 180 | const float mP_33 = -(z_far + z_near) / (z_far - z_near); 181 | const float inv_mP_34 = -(z_far - z_near) / (2 * z_far * z_near); 182 | 183 | return mat4( 184 | aspect * tan_, 0, 0, 0, 185 | 0, tan_, 0, 0, 186 | 0, 0, 0, -1, 187 | 0, 0, inv_mP_34, mP_33 * inv_mP_34 188 | ); 189 | } 190 | 191 | /////////////////////////////////////////////////////////////////////////////////////////////////// 192 | 193 | 194 | //calculate viewport transformation matrix 195 | inline mat4 viewport(const int x, const int y, const int width, const int height) 196 | { 197 | const float sx = 0.5f * width; 198 | const float sy = 0.5f * height; 199 | const float sz = 0.5f; 200 | 201 | return mat4( 202 | sx, 0, 0, sx + x, 203 | 0, sy, 0, sy + y, 204 | 0, 0, sz, sz + 0, 205 | 0, 0, 0, 1 206 | ); 207 | } 208 | 209 | //calculate inverse matrix of viewport transformation matrix 210 | inline mat4 inv_viewport(const int x, const int y, const int width, const int height) 211 | { 212 | const float sx = 2.0f / width; 213 | const float sy = 2.0f / height; 214 | const float sz = 2.0f; 215 | 216 | return mat4( 217 | sx, 0, 0, -x * sx - 1, 218 | 0, sy, 0, -y * sy - 1, 219 | 0, 0, sz, - 1, 220 | 0, 0, 0, 1 221 | ); 222 | } 223 | 224 | /////////////////////////////////////////////////////////////////////////////////////////////////// 225 | 226 | #endif 227 | -------------------------------------------------------------------------------- /src/inc/sample/our/path.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef OUR_PATH_HPP 5 | #define OUR_PATH_HPP 6 | 7 | #include"path_vertex.hpp" 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////// 10 | 11 | namespace our{ 12 | 13 | /////////////////////////////////////////////////////////////////////////////////////////////////// 14 | //constant parameters 15 | /////////////////////////////////////////////////////////////////////////////////////////////////// 16 | 17 | //parameter for russian roulette 18 | //generation of (rr_threshold+1)-th vertex of sub-path may be terminated. 19 | const size_t rr_threshold = 5; 20 | 21 | //clamping parameter for geometry term G in cache point 22 | const float G_max = 1e6f; 23 | 24 | //clamping parameter epsilon in Sec. 5.1 25 | const float mis_threshold = 1e-3f; 26 | 27 | /////////////////////////////////////////////////////////////////////////////////////////////////// 28 | //forward declaration 29 | /////////////////////////////////////////////////////////////////////////////////////////////////// 30 | 31 | class light_path; 32 | class camera_path; 33 | 34 | /////////////////////////////////////////////////////////////////////////////////////////////////// 35 | //light_path 36 | /////////////////////////////////////////////////////////////////////////////////////////////////// 37 | 38 | class light_path 39 | { 40 | public: 41 | 42 | void construct(const scene &scene, random_number_generator &rng, const kd_tree &caches); 43 | 44 | //zi : z(i), zip1: z(i+1), FGVc: array to store F(brdf)*GV at neighbor cache points of z(i) 45 | static std::tuple pdfs_FG(const scene &scene, const camera_path_vertex &zi, const camera_path_vertex &zip1, std::array &FGVc); 46 | 47 | //return sampling pdf of z(i) from z(i+1) (n: z(i) is n-th vertex from light source) 48 | static float pdf(const camera_path_vertex &zi, const camera_path_vertex &zip1, const size_t n); 49 | 50 | //ztm1: z(t-1), ysm1: y(s-1), n: z(t-1) is n-th vertex from light source, zy: direction from z(t-1) to y(s-1), yz: direction from y(s-1) to z(t-1) 51 | static std::tuple pdf_FG(const camera_path_vertex &ztm1, const light_path_vertex &ysm1, const size_t n, const direction &zy, const direction &yz); 52 | 53 | //ztm2: z(t-2), ztm1: z(t-1), n: z(t-2) is n-th vertex from light source, zy: direction from z(t-1) to y(s-1), FGVc: array to store FGV 54 | static std::tuple pdf_FG(const scene &scene, const camera_path_vertex &ztm2, const camera_path_vertex &ztm1, const size_t n, const direction &zy, std::array &FGVc); 55 | 56 | //return MIS partial weight (yz : direction from y(s-1) to z(t-1), zy: direction from z(t-1) to y(s-1), Qp : normalization factor for virtual cache point) 57 | static float mis_partial_weight(const light_path &y, const size_t s, const camera_path &z, const size_t t, const direction &yz, const direction &zy, const float M, const float Qp); 58 | 59 | //return number of vertices 60 | size_t num_vertices() const 61 | { 62 | return m_vertices.size() - 1; //decrement to exclude dummy vertex 63 | } 64 | 65 | //return path vertex 66 | light_path_vertex &operator()(const size_t i) 67 | { 68 | return m_vertices[i + 1]; //increment to exclude dummy vertex 69 | } 70 | const light_path_vertex &operator()(const size_t i) const 71 | { 72 | return m_vertices[i + 1]; 73 | } 74 | 75 | private: 76 | 77 | std::vector m_vertices; 78 | }; 79 | 80 | /////////////////////////////////////////////////////////////////////////////////////////////////// 81 | //camera_path 82 | /////////////////////////////////////////////////////////////////////////////////////////////////// 83 | 84 | class camera_path 85 | { 86 | public: 87 | 88 | //x,y: pixel coordinate 89 | void construct(const scene &scene, const camera &camera, const int x, const int y, random_number_generator &rng); 90 | 91 | //x,y: pixel coordinate, caches: cache points 92 | void construct(const scene &scene, const camera &camera, const int x, const int y, random_number_generator &rng, const kd_tree &caches); 93 | 94 | //return sampling pdfs (with RR and without RR) of y(i) from y(i+1) 95 | static std::tuple pdfs(const light_path_vertex &yi, const light_path_vertex &yip1); 96 | 97 | //return sampling pdf of y(i) from y(i+1) (n: y(i) is n-th vertex from eye) 98 | static float pdf(const light_path_vertex &yi, const light_path_vertex &yip1, const size_t n); 99 | 100 | //return sampling pdf of y(s-1) from z(t-1) (n: y(s-1) is n-th vertex from eye) 101 | static float pdf(const light_path_vertex &ysm1, const camera_path_vertex &ztm1, const size_t n, const direction &yz, const direction &zy); 102 | 103 | //return sampling pdf of y(i+1) from y(i) (n: y(s-2) is n-th vertex from eye, yz: direction from y(s-1) to z(t-1)) 104 | static float pdf(const light_path_vertex &ysm2, const light_path_vertex &ysm1, const size_t n, const direction &yz); 105 | 106 | //return MIS partial weight (yz/zy directions from y(s-1)/z(t-1) to z(t-1)/y(s-1), Qp: normalization factor for virtual cache point) 107 | static float mis_partial_weight(const scene &scene, const light_path &y, const size_t s, const camera_path &z, const size_t t, const direction &yz, const direction &zy, const float M, const float Qp); 108 | 109 | size_t num_vertices() const 110 | { 111 | return m_vertices.size(); 112 | } 113 | 114 | //return i-th vertex 115 | camera_path_vertex &operator()(const size_t i) 116 | { 117 | return m_vertices[i]; 118 | } 119 | const camera_path_vertex &operator()(const size_t i) const 120 | { 121 | return m_vertices[i]; 122 | } 123 | 124 | private: 125 | 126 | size_t m_ns1; //number of samples for strategies (s>=1,t=1) (i.e., widthxheight) 127 | std::vector m_vertices; 128 | }; 129 | 130 | /////////////////////////////////////////////////////////////////////////////////////////////////// 131 | //candidate (each pre-sampled light sub-path in ¥hat{Y}) 132 | /////////////////////////////////////////////////////////////////////////////////////////////////// 133 | 134 | class candidate 135 | { 136 | public: 137 | 138 | //i: index of vertex 139 | candidate(const light_path &path, const size_t i) : mp_path(&path), m_i(i) 140 | { 141 | } 142 | candidate() : mp_path() 143 | { 144 | } 145 | 146 | size_t s() const 147 | { 148 | return assert(mp_path != nullptr), m_i + 1; 149 | } 150 | const light_path &path() const 151 | { 152 | return assert(mp_path != nullptr), *mp_path; 153 | } 154 | const light_path_vertex &vertex() const 155 | { 156 | return assert(mp_path != nullptr), (*mp_path)(m_i); 157 | } 158 | 159 | private: 160 | 161 | const light_path *mp_path; 162 | size_t m_i; 163 | }; 164 | 165 | /////////////////////////////////////////////////////////////////////////////////////////////////// 166 | //cache 167 | /////////////////////////////////////////////////////////////////////////////////////////////////// 168 | 169 | class cache : public distribution, protected camera_path_vertex 170 | { 171 | public: 172 | 173 | //v: eye sub-path vertex, first_iteration: flag to detect whether first iteration or not 174 | cache(const camera_path_vertex &v, const bool first_iteration); 175 | 176 | //construct resampling pmf (candidates: pre-sampled light sub-paths) 177 | void calc_distribution(const scene &scene, const std::vector &candidates, const size_t M); 178 | 179 | //calculate F(brdf)*G(geo term)*V(visibility) at cache point 180 | col3 calc_FGV(const scene &scene, const ::intersection &x, const ::brdf &brdf) const; 181 | 182 | //return estimate of Q (normalization factor of target distribution) 183 | float Q() const 184 | { 185 | return m_Q; 186 | } 187 | 188 | using camera_path_vertex::intersection; 189 | 190 | private: 191 | 192 | float m_Z; //normalization factor estimated using light sub-paths in current iteration 193 | float m_Q; //normalization factor estimated using light sub-paths in previous iteration 194 | }; 195 | 196 | inline float rr_probability(const col3 &f, const float cos, const float pdf) 197 | { 198 | return std::min(luminance(f) * cos / pdf, 1.0f); 199 | } 200 | 201 | /////////////////////////////////////////////////////////////////////////////////////////////////// 202 | 203 | } //namespace our 204 | 205 | /////////////////////////////////////////////////////////////////////////////////////////////////// 206 | 207 | #include"path/cache-impl.hpp" 208 | #include"path/light_path-impl.hpp" 209 | #include"path/camera_path-impl.hpp" 210 | 211 | /////////////////////////////////////////////////////////////////////////////////////////////////// 212 | 213 | #endif 214 | -------------------------------------------------------------------------------- /src/inc/sample/our/path/camera_path-impl.hpp: -------------------------------------------------------------------------------- 1 | 2 | /////////////////////////////////////////////////////////////////////////////////////////////////// 3 | 4 | namespace our{ 5 | 6 | /////////////////////////////////////////////////////////////////////////////////////////////////// 7 | //camera_path 8 | /////////////////////////////////////////////////////////////////////////////////////////////////// 9 | 10 | //construct eye sub-paths 11 | inline void camera_path::construct(const scene &scene, const camera &camera, const int x, const int y, random_number_generator &rng) 12 | { 13 | m_vertices.clear(); 14 | 15 | //number of samples for strategies (s>=1,t=1) (used in MIS weights) 16 | m_ns1 = camera.res_x() * camera.res_y(); 17 | 18 | ray r = camera.sample(x, y, rng); 19 | 20 | //generate path vertex on lens, store camera to material 21 | m_vertices.emplace_back(intersection(r.o(), camera.d(), reinterpret_cast(&camera)), brdf(), direction(), direction(), col3(1), camera.pdf_o()); 22 | 23 | float pdf = camera.pdf_d(direction(r.d(), camera.d())); 24 | 25 | //initialize throughput*We 26 | col3 throughput_We(1); 27 | 28 | //generate path vertices 29 | while(true){ 30 | 31 | const intersection isect = scene.calc_intersection(r); 32 | if(isect.is_invalid()){ 33 | break; 34 | } 35 | 36 | const direction wo(-r.d(), isect.n()); 37 | if(wo.is_invalid()){ 38 | break; 39 | } 40 | 41 | pdf *= wo.abs_cos() / (r.t() * r.t()); 42 | 43 | //if isect is on light source, add isect and terminate tracing 44 | if(isect.material().is_emissive()){ 45 | m_vertices.emplace_back(isect, brdf(), wo, direction(isect.n()), throughput_We, pdf); 46 | break; 47 | } 48 | 49 | const brdf brdf = isect.material().make_brdf(isect, wo); 50 | const brdf_sample sample = brdf.sample(rng); 51 | 52 | //add path vertex 53 | if(sample.is_invalid()){ 54 | m_vertices.emplace_back(isect, brdf, wo, direction(), throughput_We, pdf); 55 | break; 56 | }else{ 57 | m_vertices.emplace_back(isect, brdf, wo, sample.w(), throughput_We, pdf); 58 | } 59 | 60 | //russian roulette 61 | if(num_vertices() >= rr_threshold){ 62 | 63 | const float q = rr_probability(sample.f(), sample.w().abs_cos(), sample.pdf()); 64 | if(rng.generate_uniform_real() < q){ 65 | pdf = sample.pdf() * q; 66 | }else{ 67 | break; 68 | } 69 | }else{ 70 | pdf = sample.pdf(); 71 | } 72 | 73 | //update ray & throughput weight 74 | r = ray(isect.p(), sample.w()); 75 | throughput_We *= sample.f() * sample.w().abs_cos() / pdf; 76 | } 77 | } 78 | 79 | //construct eye sub-path 80 | inline void camera_path::construct(const scene &scene, const camera &camera, const int x, const int y, random_number_generator &rng, const kd_tree &caches) 81 | { 82 | //construct path 83 | construct(scene, camera, x, y, rng); 84 | 85 | //precompute variables used in MIS weights 86 | { 87 | //search nearest cache points 88 | thread_local std::vector> neighbors; 89 | for(size_t i = 1, n = num_vertices(); i < n; i++){ 90 | 91 | auto &zi = operator()(i); 92 | caches.find_nearest(zi.intersection().p(), FLT_MAX, Nc, neighbors); 93 | 94 | for(size_t j = 0; j < Nc; j++){ 95 | zi.set_neighbor_cache(j, *neighbors[j]); 96 | } 97 | } 98 | 99 | //calculate backward pdfs and FGV at neighbor cache points 100 | for(size_t i = 1, n = num_vertices(); i + 2 < n; i++){ 101 | 102 | auto &zi = operator()(i); 103 | auto &zip1 = operator()(i + 1); 104 | auto pdfs_FG = light_path::pdfs_FG(scene, zi, zip1, zi.FGVc()); 105 | zi.set_pdf_bwd(std::get<0>(pdfs_FG)); 106 | zi.set_pdf_bwd_rr(std::get<1>(pdfs_FG)); 107 | zi.set_FG_bwd(std::get<2>(pdfs_FG)); 108 | } 109 | } 110 | } 111 | 112 | /////////////////////////////////////////////////////////////////////////////////////////////////// 113 | 114 | //calculate MIS partial weight 115 | inline float camera_path::mis_partial_weight(const scene &scene, const light_path &y, const size_t s, const camera_path &z, const size_t t, const direction &yz, const direction &zy, const float M, const float Qp) 116 | { 117 | float w = 0; 118 | { 119 | auto recurse = [&](const size_t i, const col3 &Le_throughput, const float pdf_L_zi, auto *This) -> void 120 | { 121 | //calculate FGV 122 | std::array FGVc; 123 | if(i > 1){ 124 | 125 | col3 FG_zim1; 126 | float pdf_L_zim1; 127 | if(i == t - 1){ 128 | const auto pdf_FG = light_path::pdf_FG(scene, z(t - 2), z(t - 1), s + (t - (i - 1)), zy, FGVc); 129 | pdf_L_zim1 = std::get<0>(pdf_FG); 130 | FG_zim1 = std::get<1>(pdf_FG); 131 | 132 | }else{ 133 | FGVc = z(i - 1).FGVc(); 134 | FG_zim1 = z(i - 1).FG_bwd(); 135 | pdf_L_zim1 = light_path::pdf(z(i - 1), z(i), s + (t - (i - 1))); 136 | } 137 | (*This)(i - 1, Le_throughput * FG_zim1 / pdf_L_zim1, pdf_L_zim1, This); 138 | } 139 | 140 | if(i == 1){ 141 | w += z.m_ns1; 142 | }else{ 143 | for(size_t j = 0; j < Nc; j++){ 144 | 145 | const float Q = z(i - 1).neighbor_cache(j).Q(); 146 | const float Le_throughput_FGVc = luminance(Le_throughput * FGVc[j]); 147 | 148 | if(Le_throughput_FGVc > 0){ 149 | w += (1 / float(Nc + 1)) * M / ((M - 1) * std::max(mis_threshold, Q / Le_throughput_FGVc) + 1); 150 | } 151 | } 152 | w += (1 / float(Nc + 1)) * M / ((M - 1) * Qp + 1); 153 | } 154 | w *= pdf_L_zi / z(i).pdf_fwd(); 155 | }; 156 | 157 | const col3 Le_throughput = ( 158 | (s > 0) ? y(s - 1).Le_throughput() : col3(1) 159 | ); 160 | const auto pdf_FG = light_path::pdf_FG(z(t - 1), y(s - 1), s + 1, zy, yz); 161 | const auto pdf = std::get<0>(pdf_FG); 162 | const auto &FG = std::get<1>(pdf_FG); 163 | recurse(t - 1, Le_throughput * FG / pdf, pdf, &recurse); 164 | } 165 | return w; 166 | } 167 | 168 | /////////////////////////////////////////////////////////////////////////////////////////////////// 169 | 170 | 171 | //sampling pdfs (without/with RR) of y(i) from y(i+1) 172 | inline std::tuple camera_path::pdfs(const light_path_vertex &yi, const light_path_vertex &yip1) 173 | { 174 | auto &yi_isect = yi.intersection(); 175 | auto &yip1_isect = yip1.intersection(); 176 | 177 | //BRDF at y(i+1) 178 | const auto brdf = yip1_isect.material().make_brdf(yip1_isect, yip1.wo()); 179 | 180 | //pdf of solid angle measure 181 | const float pdf_w = brdf.pdf(yip1.wi()); 182 | const float pdf_w_rr = pdf_w * rr_probability(brdf.f(yip1.wi()), yip1.wi().abs_cos(), pdf_w); 183 | 184 | //convert to area measure 185 | const float J = yi.wo().abs_cos() / squared_norm(yi_isect.p() - yip1_isect.p()); 186 | const float pdf_A = pdf_w * J; 187 | const float pdf_A_rr = pdf_w_rr * J; 188 | return std::make_tuple(pdf_A, pdf_A_rr); 189 | } 190 | 191 | /////////////////////////////////////////////////////////////////////////////////////////////////// 192 | 193 | //sampling pdf of y(i) (yi) from y(i+1) (yip1) (n: y(i) is n-th vertex from eye) 194 | inline float camera_path::pdf(const light_path_vertex &yi, const light_path_vertex &yip1, const size_t n) 195 | { 196 | return (void)yip1, (n > rr_threshold) ? yi.pdf_bwd_rr() : yi.pdf_bwd(); 197 | } 198 | 199 | /////////////////////////////////////////////////////////////////////////////////////////////////// 200 | 201 | //sampling pdf of y(s-1) (ysm1) from z(t-1) (ztm1), (yz: direction from y(s-1) to z(t-1), zy: direction from z(t-1) to y(s-1)) 202 | inline float camera_path::pdf(const light_path_vertex &ysm1, const camera_path_vertex &ztm1, const size_t n, const direction &yz, const direction &zy) 203 | { 204 | float pdf_w; 205 | 206 | if(n==1){ 207 | pdf_w = reinterpret_cast(ztm1.intersection().material()).pdf_d(zy); 208 | } 209 | else{ //z(t-1) on surfaces 210 | pdf_w = ztm1.brdf().pdf(zy); 211 | if(n > rr_threshold){ 212 | pdf_w *= rr_probability(ztm1.brdf().f(zy), zy.abs_cos(), pdf_w); 213 | } 214 | } 215 | 216 | //convert to area measure 217 | return pdf_w * yz.abs_cos() / squared_norm(ztm1.intersection().p() - ysm1.intersection().p()); 218 | } 219 | 220 | /////////////////////////////////////////////////////////////////////////////////////////////////// 221 | 222 | //sampling pdf of y(s-2) (ysm2) from y(s-1) (ysm1) 223 | inline float camera_path::pdf(const light_path_vertex &ysm2, const light_path_vertex &ysm1, const size_t n, const direction &yz) 224 | { 225 | //BRDF at y(s-1) 226 | const auto &ysm1_isect = ysm1.intersection(); 227 | const auto brdf = ysm1_isect.material().make_brdf(ysm1_isect, yz); 228 | 229 | //solid angle pdf 230 | float pdf_w = brdf.pdf(ysm1.wi()); 231 | if(n > rr_threshold){ 232 | pdf_w *= rr_probability(brdf.f(ysm1.wi()), ysm1.wi().abs_cos(), pdf_w); 233 | } 234 | 235 | //convert to area measure 236 | return pdf_w * ysm2.wo().abs_cos() / squared_norm(ysm2.intersection().p() - ysm1_isect.p()); 237 | } 238 | 239 | /////////////////////////////////////////////////////////////////////////////////////////////////// 240 | 241 | } //namespace our 242 | 243 | /////////////////////////////////////////////////////////////////////////////////////////////////// 244 | -------------------------------------------------------------------------------- /src/inc/sample/our/path/light_path-impl.hpp: -------------------------------------------------------------------------------- 1 | 2 | /////////////////////////////////////////////////////////////////////////////////////////////////// 3 | 4 | namespace our{ 5 | 6 | /////////////////////////////////////////////////////////////////////////////////////////////////// 7 | //light_path 8 | /////////////////////////////////////////////////////////////////////////////////////////////////// 9 | 10 | //construct light sub-path 11 | inline void light_path::construct(const scene &scene, random_number_generator &rng, const kd_tree &caches) 12 | { 13 | m_vertices.clear(); 14 | 15 | //initialize m_vertices using "dummy" path vertex to avoid out of range access when MIS is calculated, ptr to scene is stored in material 16 | m_vertices.emplace_back(intersection(vec3(), vec3(), reinterpret_cast(&scene)), brdf(), direction(), direction(), col3(), 0.0f); 17 | 18 | //sample point on light source 19 | const sample_point lsample = scene.sample_light(rng); 20 | if(lsample.is_invalid()){ 21 | return; 22 | } 23 | 24 | //sample outgoing direction 25 | const brdf lbrdf = lsample.material().make_brdf(lsample, direction(lsample.n())); 26 | const brdf_sample bsample = lbrdf.sample(rng); 27 | 28 | //add path vertex 29 | col3 Le_throughput = lsample.material().Me() / lsample.pdf(); 30 | if(bsample.is_invalid()){ 31 | m_vertices.emplace_back(lsample, lbrdf, direction(lsample.n()), direction(), Le_throughput, lsample.pdf()); 32 | return; 33 | }else{ 34 | m_vertices.emplace_back(lsample, lbrdf, direction(lsample.n()), bsample.w(), Le_throughput, lsample.pdf()); 35 | } 36 | 37 | //generate path 38 | float pdf = bsample.pdf(); 39 | ray r(lsample.p(), bsample.w()); 40 | while(true){ 41 | 42 | //intersection test 43 | const intersection isect = scene.calc_intersection(r); 44 | if(isect.is_invalid()){ 45 | break; 46 | } 47 | 48 | const direction wi(-r.d(), isect.n()); 49 | if(wi.is_invalid()){ 50 | break; 51 | } 52 | 53 | //convert to area measure 54 | pdf *= wi.abs_cos() / (r.t() * r.t()); 55 | 56 | //if isect is on light source terminate tracing 57 | if(isect.material().is_emissive()){ 58 | break; 59 | } 60 | 61 | //sample direction 62 | const brdf brdf = isect.material().make_brdf(isect, wi); 63 | const brdf_sample sample = brdf.sample(rng); 64 | 65 | //add path vertex 66 | if(sample.is_invalid()){ 67 | m_vertices.emplace_back(isect, brdf, wi, direction(), Le_throughput, pdf); 68 | break; 69 | }else{ 70 | m_vertices.emplace_back(isect, brdf, wi, sample.w(), Le_throughput, pdf); 71 | } 72 | 73 | //russian roulette 74 | if(num_vertices() >= rr_threshold){ 75 | 76 | const float q = rr_probability(sample.f(), sample.w().abs_cos(), sample.pdf()); 77 | if(rng.generate_uniform_real() < q){ 78 | pdf = sample.pdf() * q; 79 | }else{ 80 | break; 81 | } 82 | }else{ 83 | pdf = sample.pdf(); 84 | } 85 | 86 | //update ray/throughput weight 87 | r = ray(isect.p(), sample.w()); 88 | Le_throughput *= sample.f() * sample.w().abs_cos() / pdf; 89 | } 90 | 91 | //precompute variables for MIS weights 92 | { 93 | //search neighbor cache points 94 | thread_local std::vector> neighbors; 95 | for(size_t i = 1, n = num_vertices(); i < n; i++){ 96 | 97 | auto &yi = operator()(i); 98 | caches.find_nearest(yi.intersection().p(), FLT_MAX, Nc, neighbors); 99 | 100 | for(size_t j = 0; j < Nc; j++){ 101 | yi.set_neighbor_cache(j, *neighbors[j]); 102 | } 103 | } 104 | 105 | //calculate backward pdfs from eye 106 | for(size_t i = 0, n = num_vertices(); i + 2 < n; i++){ 107 | auto &yi = operator()(i); 108 | auto &yip1 = operator()(i + 1); 109 | auto pdfs = camera_path::pdfs(yi, yip1); 110 | yi.set_pdf_bwd(std::get<0>(pdfs)); //RRなしのPDF 111 | yi.set_pdf_bwd_rr(std::get<1>(pdfs)); //RRありのPDF 112 | } 113 | 114 | //set q*/p 115 | for(size_t i = 1, n = num_vertices(); i < n; i++){ 116 | 117 | auto &yi = operator()(i); 118 | auto &yim1 = operator()(i - 1); 119 | 120 | for(size_t j = 0; j < Nc; j++){ 121 | yi.set_Le_throughput_FGVc(j, yim1.Le_throughput() * yi.neighbor_cache(j).calc_FGV(scene, yim1.intersection(), yim1.brdf())); 122 | } 123 | } 124 | } 125 | } 126 | 127 | ///////////////////////////////////////////////////////////////////////////////////////////////////} 128 | 129 | //calculate MIS partial weight (yz: direction from y(s-1) to z(t-1), zy: direction from z(t-1) to y(s-1), Qp: normalization factor for virtual cache point) 130 | inline float light_path::mis_partial_weight(const light_path &y, const size_t s, const camera_path &z, const size_t t, const direction &yz, const direction &zy, const float M, const float Qp) 131 | { 132 | float w = 0; 133 | for(size_t i = 0; i < s; i++){ 134 | 135 | if(i == 0){ 136 | w += 1; 137 | }else{ 138 | for(size_t j = 0; j < Nc; j++){ 139 | 140 | const float Q = y(i).neighbor_cache(j).Q(); 141 | const float Le_throughput_FGVc = y(i).Le_throughput_FGVc(j); 142 | 143 | if(Le_throughput_FGVc > 0){ 144 | w += (1 / float(Nc + 1)) * M / ((M - 1) * std::max(mis_threshold, Q / Le_throughput_FGVc) + 1); 145 | } 146 | } 147 | w += (1 / float(Nc + 1)) * M / ((M - 1) * Qp + 1); 148 | } 149 | if(i == s - 1){ 150 | w *= camera_path::pdf(y(s - 1), z(t - 1), (s - i) + t, yz, zy); 151 | }else if(i == s - 2){ 152 | w *= camera_path::pdf(y(s - 2), y(s - 1), (s - i) + t, yz); 153 | }else{ 154 | w *= camera_path::pdf(y(i), y(i + 1), (s - i) + t); 155 | } 156 | w /= y(i).pdf_fwd(); 157 | } 158 | return w; 159 | } 160 | 161 | /////////////////////////////////////////////////////////////////////////////////////////////////// 162 | 163 | //return pdfs (without/with RR) and FG, and calculate FGV at neighbor cache points of z(i) 164 | //zi: z(i), zip1: z(i+1), FGVc: array to store FGVs 165 | inline std::tuple light_path::pdfs_FG(const scene &scene, const camera_path_vertex &zi, const camera_path_vertex &zip1, std::array &FGVc) 166 | { 167 | auto &zi_isect = zi.intersection(); 168 | auto &zip1_isect = zip1.intersection(); 169 | 170 | //BRDF at z(i+1) 171 | const auto brdf = zip1_isect.material().make_brdf(zip1_isect, zip1.wi()); 172 | 173 | //solid angle pdf 174 | const float pdf_w = brdf.pdf(zip1.wo()); 175 | const float pdf_w_rr = pdf_w * rr_probability(brdf.f(zip1.wo()), zip1.wo().abs_cos(), pdf_w); 176 | 177 | //convert to area measure 178 | const float J = zi.wi().abs_cos() / squared_norm(zi_isect.p() - zip1_isect.p()); 179 | const float pdf_A = pdf_w * J; 180 | const float pdf_A_rr = pdf_w_rr * J; 181 | 182 | //calculate FG 183 | const col3 FG = brdf.f(zip1.wo()) * (zip1.wo().abs_cos() * J); 184 | 185 | //calculate FGV at neighbor cache points of z(i) 186 | for(size_t i = 0; i < Nc; i++){ 187 | FGVc[i] = zi.neighbor_cache(i).calc_FGV(scene, zip1_isect, brdf); 188 | } 189 | return std::make_tuple(pdf_A, pdf_A_rr, FG); 190 | } 191 | 192 | /////////////////////////////////////////////////////////////////////////////////////////////////// 193 | 194 | //return sampling pdf of z(i) from z(i+1) (n: z(i) is n-th vertex from light source) 195 | inline float light_path::pdf(const camera_path_vertex &zi, const camera_path_vertex &zip1, const size_t n) 196 | { 197 | return (void)zip1, (n > rr_threshold) ? zi.pdf_bwd_rr() : zi.pdf_bwd(); 198 | } 199 | 200 | /////////////////////////////////////////////////////////////////////////////////////////////////// 201 | 202 | //return pdf and FG and calculate FGV at cache points neighbor to z(t-2) 203 | //ztm2: z(t-2), ztm1: z(t-1), n: z(i) is n-th vertex from light source, zy: direction from z(t-1) to y(s-1), FGVc: array to store FGV 204 | inline std::tuple light_path::pdf_FG(const scene &scene, const camera_path_vertex &ztm2, const camera_path_vertex &ztm1, const size_t n, const direction &zy, std::array &FGVc) 205 | { 206 | //BRDF at z(t-1) 207 | const auto &ztm1_isect = ztm1.intersection(); 208 | const auto brdf = ztm1_isect.material().make_brdf(ztm1_isect, zy); 209 | 210 | //solid angle pdf 211 | float pdf_w = brdf.pdf(ztm1.wo()); 212 | if(n > rr_threshold){ 213 | pdf_w *= rr_probability(brdf.f(ztm1.wo()), ztm1.wo().abs_cos(), pdf_w); 214 | } 215 | 216 | //convert to area measure 217 | const float J = ztm2.wi().abs_cos() / squared_norm(ztm2.intersection().p() - ztm1_isect.p()); 218 | const float pdf_A = pdf_w * J; 219 | 220 | //calculate FG 221 | const col3 FG = brdf.f(ztm1.wo()) * (ztm1.wo().abs_cos() * J); 222 | 223 | //calculate FGV at cache points neighbor to z(t-2) 224 | for(size_t i = 0; i < Nc; i++){ 225 | FGVc[i] = ztm2.neighbor_cache(i).calc_FGV(scene, ztm1_isect, brdf); 226 | } 227 | return std::make_tuple(pdf_A, FG); 228 | } 229 | 230 | /////////////////////////////////////////////////////////////////////////////////////////////////// 231 | 232 | //return pdf and FG 233 | //ztm1: z(t-1), ysm1: y(s-1), n: z(i) is n-th vertex from light source, zy: direction from z(t-1) to y(s-1), yz: direction from y(s-1) to z(t-1) 234 | inline std::tuple light_path::pdf_FG(const camera_path_vertex &ztm1, const light_path_vertex &ysm1, const size_t n, const direction &zy, const direction &yz) 235 | { 236 | const auto &ztm1_isect = ztm1.intersection(); 237 | const auto &ysm1_isect = ysm1.intersection(); 238 | 239 | col3 FG; 240 | float pdf_A; 241 | 242 | if(ztm1_isect.material().is_emissive()){ //z(t-1) is on light source 243 | //intersection::material in y(-1) stores ptr to scene 244 | pdf_A = reinterpret_cast(ysm1_isect.material()).pdf_light(ztm1_isect); 245 | FG = ztm1_isect.material().Me(); 246 | } 247 | //z(t-1) is on (non-emissive) surface 248 | else{ 249 | //calculate solid angle pdf 250 | float pdf_w = ysm1.brdf().pdf(yz); 251 | if(n > rr_threshold){ 252 | pdf_w *= rr_probability(ysm1.brdf().f(yz), yz.abs_cos(), pdf_w); 253 | } 254 | 255 | //convert to area measure 256 | const float J = zy.abs_cos() / squared_norm(ztm1_isect.p() - ysm1_isect.p()); 257 | pdf_A = pdf_w * J; 258 | 259 | //calculate FG 260 | FG = ysm1.brdf().f(yz) * yz.abs_cos() * J; 261 | } 262 | return std::make_tuple(pdf_A, FG); 263 | } 264 | 265 | /////////////////////////////////////////////////////////////////////////////////////////////////// 266 | 267 | } //namespace our 268 | 269 | /////////////////////////////////////////////////////////////////////////////////////////////////// 270 | -------------------------------------------------------------------------------- /src/inc/sample/our/renderer-impl.hpp: -------------------------------------------------------------------------------- 1 | 2 | /////////////////////////////////////////////////////////////////////////////////////////////////// 3 | 4 | namespace our{ 5 | 6 | /////////////////////////////////////////////////////////////////////////////////////////////////// 7 | //renderer 8 | /////////////////////////////////////////////////////////////////////////////////////////////////// 9 | 10 | 11 | //constructor (M : number of pre-sampled light sub-paths, nt : number of threads) 12 | inline renderer::renderer(const scene &scene, const camera &camera, const size_t M, const size_t nt) : m_M(M), m_nt(nt), m_sum(), m_ite() 13 | { 14 | //number of samples for strategies (s>=1, t=1) 15 | m_ns1 = camera.res_x() * camera.res_y(); 16 | 17 | //buffer to store contributions of strategies (s>=1, t=1) 18 | m_buf_s1 = imagef(camera.res_x(), camera.res_y()); 19 | 20 | //spinlock 21 | m_locks = std::make_unique(camera.res_x() * camera.res_y()); 22 | } 23 | 24 | /////////////////////////////////////////////////////////////////////////////////////////////////// 25 | 26 | //rendering 27 | inline imagef renderer::render(const scene &scene, const camera &camera) 28 | { 29 | m_ite += 1; 30 | 31 | const int w = camera.res_x(); 32 | const int h = camera.res_y(); 33 | imagef screen(w, h); 34 | 35 | //generate cache points (Line 3 of Algorithm1) 36 | { 37 | std::mutex mtx; 38 | std::vector caches; 39 | auto locked_add = [&](const camera_path_vertex &v){ 40 | std::lock_guard lock(mtx); caches.emplace_back(v, m_ite == 1); 41 | }; 42 | 43 | //camera setup for generating cache points 44 | //generate eye sub-paths from camera_for_gen_caches (with approximately wxhx0.4% pixels) 45 | const float num = w * h * 0.004f; 46 | const int res_x = int(ceil(sqrt(num * camera.res_x() / float(camera.res_y())))); 47 | const int res_y = int(ceil(sqrt(num * camera.res_y() / float(camera.res_x())))); 48 | const ::camera camera_for_gen_caches(camera.p(), camera.p() + camera.d(), res_x, res_y, camera.fovy(), camera.lens_radius()); 49 | 50 | //cache points are generated by tracing eye sub-paths. Each vertex of the eye sub-paths are used as the cache point 51 | std::atomic_size_t idx(0); 52 | in_parallel(res_x, res_y, [&](const int x, const int y) 53 | { 54 | thread_local random_number_generator rng(std::random_device{}()); 55 | thread_local camera_path z; 56 | 57 | if(m_ite == 1){ 58 | //for 1st iteration, estimate normalization factor Q using pre-sampled light sub-paths of 1st iteration 59 | z.construct(scene, camera_for_gen_caches, x, y, rng); 60 | }else{ 61 | //estimate normalization factor Q using cache points at previous iteration (m_caches) 62 | z.construct(scene, camera_for_gen_caches, x, y, rng, m_caches); 63 | } 64 | for(size_t j = 1, n = z.num_vertices(); j < n; j++){ 65 | locked_add(std::move(std::move(z(j)))); //generation of cache points for current iteration 66 | } 67 | }, m_nt); 68 | 69 | //construct kd-tree to search cache points 70 | m_caches = kd_tree(std::move(caches), [](const cache &c) -> const vec3&{ 71 | return c.intersection().p(); 72 | }); 73 | } 74 | 75 | //generate light sub-paths 76 | //we prepare wxh light sub-paths and each light sub-path is used for strategies other than resampling strategies. 77 | m_light_paths.resize(w * h); 78 | in_parallel(w * h, [&](const int idx) 79 | { 80 | thread_local random_number_generator rng(std::random_device{}()); 81 | m_light_paths[idx].construct(scene, rng, m_caches); 82 | }, m_nt); 83 | 84 | //generate ¥hat{Y}_n in Line 2 of Algorithm1 85 | { 86 | size_t V = 0; 87 | for(size_t i = 0; i < m_M; i++){ 88 | V += m_light_paths[i].num_vertices(); 89 | } 90 | m_candidates.resize(V); 91 | 92 | V = 0; 93 | for(size_t i = 0; i < m_M; i++){ 94 | for(size_t j = 0, n = m_light_paths[i].num_vertices(); j < n; j++){ 95 | m_candidates[V++] = candidate(m_light_paths[i], j); 96 | } 97 | } 98 | } 99 | 100 | //construct resampling pmfs at cache points 101 | in_parallel(int(m_caches.end() - m_caches.begin()), [&](const int idx) 102 | { 103 | const cache &c = *(m_caches.begin() + idx); 104 | const_cast(c).calc_distribution(scene, m_candidates, m_M); 105 | }, m_nt); 106 | 107 | //calculate normalization factor for virtual cache point 108 | { 109 | m_sum += m_candidates.size() / double(m_M); 110 | m_Qp = float(m_sum / m_ite); 111 | } 112 | 113 | //initialize buffer that stores contributions of strategies (s>=1,t=1) of light tracing 114 | memset(m_buf_s1(0,0), 0, sizeof(float) * 3 * w * h); 115 | 116 | in_parallel(w, h, [&](const int x, const int y) 117 | { 118 | thread_local random_number_generator rng(std::random_device{}()); 119 | 120 | const col3 col = radiance(x, y, scene, camera, rng); 121 | if(!(isnan(col[0] + col[1] + col[2]))){ 122 | screen(x, y)[0] = col[0]; 123 | screen(x, y)[1] = col[1]; 124 | screen(x, y)[2] = col[2]; 125 | } 126 | }, m_nt); 127 | 128 | //add contributions of strategies (s>=1,t=1) 129 | const float inv_ns1 = 1 / float(m_ns1); 130 | for(int y = 0; y < h; y++){ 131 | for(int x = 0; x < w; x++){ 132 | screen(x, y)[0] += m_buf_s1(x, y)[0] * inv_ns1; 133 | screen(x, y)[1] += m_buf_s1(x, y)[1] * inv_ns1; 134 | screen(x, y)[2] += m_buf_s1(x, y)[2] * inv_ns1; 135 | } 136 | } 137 | return screen; 138 | } 139 | 140 | /////////////////////////////////////////////////////////////////////////////////////////////////// 141 | 142 | //radiance calculation (x,y: pixel coordinate) 143 | inline col3 renderer::radiance(const int x, const int y, const scene &scene, const camera &camera, random_number_generator &rng) 144 | { 145 | thread_local camera_path camera_path; 146 | 147 | //generate eye sub-path 148 | camera_path.construct(scene, camera, x, y, rng, m_caches); 149 | const light_path &light_path = m_light_paths[x + camera.res_x() * y]; 150 | 151 | //calculate contributions of strategies (s>=1,t=1) and store them in m_buf_s1 152 | calculate_s1(scene, camera, light_path, camera_path, rng); 153 | 154 | //calculate contributions of resampling strategies (s>=1,t>=2) and strategies (s=0,t>=2) 155 | return calculate_0t(scene, light_path, camera_path) + calculate_st(scene, camera_path, rng); 156 | } 157 | 158 | /////////////////////////////////////////////////////////////////////////////////////////////////// 159 | 160 | //calculate contributions of strategies (s=0,t>=2) (unidirectional path tracing) 161 | inline col3 renderer::calculate_0t(const scene &scene, const light_path &y, const camera_path &z) 162 | { 163 | const size_t t = z.num_vertices(); 164 | if(t >= 2){ 165 | 166 | const auto &ztm1 = z(t - 1); 167 | const auto &ztm1_isect = z(t - 1).intersection(); 168 | 169 | //if z(t-1) is on light source 170 | if(ztm1_isect.material().is_emissive()){ 171 | 172 | const col3 Le = ztm1_isect.material().Le(ztm1_isect, ztm1.wo()); 173 | 174 | const float mis_weight = 1 / ( 175 | 0 + 1 + camera_path::mis_partial_weight(scene, y, 0, z, t, direction(), ztm1.wi(), m_M, m_Qp) 176 | ); 177 | return Le * ztm1.throughput_We() * mis_weight; 178 | } 179 | } 180 | return col3(); 181 | } 182 | 183 | /////////////////////////////////////////////////////////////////////////////////////////////////// 184 | 185 | //calculate contributions of strategies (s>=1, t=1) 186 | inline void renderer::calculate_s1(const scene &scene, const camera &camera, const light_path &y, const camera_path &z, random_number_generator &rng) 187 | { 188 | for(size_t s = 1, nL = y.num_vertices(); s <= nL; s++){ 189 | 190 | const auto &z0 = z(0); 191 | const auto &ysm1 = y(s - 1); 192 | const auto &z0_isect = z(0).intersection(); 193 | const auto &ysm1_isect = y(s - 1).intersection(); 194 | 195 | const vec3 tmp_zy = ysm1_isect.p() - z0_isect.p(); 196 | const float dist2 = squared_norm(tmp_zy); 197 | const float dist = sqrt(dist2); 198 | const direction zy(tmp_zy / dist, z0_isect.n()); 199 | if(zy.is_invalid() || zy.in_lower_hemisphere()){ 200 | continue; 201 | } 202 | 203 | const direction yz(-zy, ysm1_isect.n()); 204 | if(yz.is_invalid() || yz.in_lower_hemisphere()){ 205 | continue; 206 | } 207 | 208 | //calculate intersection on screen 209 | const auto screen_pos = camera.calc_intersection(z0_isect.p(), zy); 210 | if(screen_pos.is_valid){ 211 | 212 | //visibility test 213 | if(scene.intersect(ray(z0_isect.p(), zy, dist)) == false){ 214 | 215 | const col3 fyz = ysm1.brdf().f(yz); 216 | const float We = camera.We(zy); 217 | const float G = yz.abs_cos() * zy.abs_cos() / dist2; 218 | 219 | const float mis_weight = m_ns1 / ( 220 | light_path::mis_partial_weight(y, s, z, 1, yz, zy, m_M, m_Qp) + m_ns1 + 0 221 | ); 222 | const col3 contrib = ysm1.Le_throughput() * fyz * (We * G / z0.pdf_fwd() * mis_weight); 223 | 224 | //update m_buf_s1 using spinlock 225 | std::lock_guard lock(m_locks[screen_pos.x + camera.res_x() * screen_pos.y]); 226 | m_buf_s1(screen_pos.x, screen_pos.y)[0] += contrib[0]; 227 | m_buf_s1(screen_pos.x, screen_pos.y)[1] += contrib[1]; 228 | m_buf_s1(screen_pos.x, screen_pos.y)[2] += contrib[2]; 229 | } 230 | } 231 | } 232 | } 233 | 234 | /////////////////////////////////////////////////////////////////////////////////////////////////// 235 | 236 | //calculate contributions for resampling estimators 237 | inline col3 renderer::calculate_st(const scene &scene, const camera_path &z, random_number_generator &rng) 238 | { 239 | const size_t nE = z.num_vertices(); 240 | 241 | col3 L; 242 | for(size_t t = 2; t <= nE; t++){ 243 | 244 | const auto &ztm1 = z(t - 1); 245 | const auto &ztm1_isect = z(t - 1).intersection(); 246 | 247 | if(ztm1_isect.material().is_emissive()){ 248 | continue; 249 | } 250 | 251 | //sample cache point uniformly (i.e, P_c(i)=1/(Nc+1) in Sec. 5.2) 252 | size_t cache_idx = Nc; 253 | float pmf = 1 / float(Nc + 1); 254 | { 255 | float u = rng.generate_uniform_real(); 256 | for(size_t i = 0; i < Nc; i++){ 257 | if(u < 1 / float(Nc + 1)){ 258 | cache_idx = i; break; 259 | } 260 | u -= 1 / float(Nc + 1); 261 | } 262 | } 263 | if((cache_idx != Nc) && (ztm1.neighbor_cache(cache_idx).normalization_constant() == 0)){ 264 | continue; 265 | } 266 | 267 | //resample light sub-path (Line13 in Algorithm1) 268 | size_t sample_idx; 269 | const candidate *p_candidate; 270 | if(cache_idx != Nc){ 271 | const auto sample = ztm1.neighbor_cache(cache_idx).sample(rng); 272 | sample_idx = sample.p_elem - &*ztm1.neighbor_cache(cache_idx).begin(); 273 | p_candidate = sample.p_elem; 274 | pmf *= sample.pmf; 275 | }else{ 276 | //use virtual cache point 277 | sample_idx = rng.generate_uniform_int(0, m_candidates.size() - 1); 278 | p_candidate = &m_candidates[sample_idx]; 279 | pmf *= 1 / float(m_candidates.size()); 280 | } 281 | const auto &y = p_candidate->path(); 282 | const auto s = p_candidate->s(); 283 | const auto &ysm1 = y(s - 1); 284 | const auto &ysm1_isect = y(s - 1).intersection(); 285 | 286 | const vec3 tmp_yz = ztm1_isect.p() - ysm1_isect.p(); 287 | const float dist2 = squared_norm(tmp_yz); 288 | const float dist = sqrt(dist2); 289 | const direction yz(tmp_yz / dist, ysm1_isect.n()); 290 | if(yz.is_invalid() || yz.in_lower_hemisphere()){ 291 | continue; 292 | } 293 | 294 | const direction zy(-yz, ztm1_isect.n()); 295 | if(zy.is_invalid() || zy.in_lower_hemisphere()){ 296 | continue; 297 | } 298 | 299 | //visibility test between y(s-1) & z(t-1) 300 | if(scene.intersect(ray(ysm1_isect.p(), yz, dist)) == false){ 301 | 302 | const col3 fyz = ysm1.brdf().f(yz); 303 | const col3 fzy = ztm1.brdf().f(zy); 304 | const float G = yz.abs_cos() * zy.abs_cos() / dist2; 305 | 306 | //calculate resampling-aware weighting function 307 | float mis_weight; 308 | { 309 | float val, sum_val = 0; 310 | for(size_t i = 0; i < Nc; i++){ 311 | //normalization factor Q at nearest cache point 312 | const float Q = ztm1.neighbor_cache(i).Q(); 313 | 314 | //calculate q*/p 315 | const float Le_throughput_FGVc = ztm1.neighbor_cache(i).pmf(sample_idx) * ztm1.neighbor_cache(i).normalization_constant(); 316 | 317 | if(Le_throughput_FGVc > 0){ 318 | const float tmp_val = (1 / float(Nc + 1)) * m_M / ( 319 | (m_M - 1) * std::max(mis_threshold, Q / Le_throughput_FGVc) + 1 320 | ); 321 | if(cache_idx == i){ 322 | val = tmp_val; 323 | } 324 | sum_val += tmp_val; 325 | } 326 | } 327 | const float tmp_val = (1 / float(Nc + 1)) * m_M / ( 328 | (m_M - 1) * m_Qp + 1 329 | ); 330 | if(cache_idx == Nc){ 331 | val = tmp_val; 332 | } 333 | sum_val += tmp_val; 334 | 335 | mis_weight = val / ( 336 | light_path::mis_partial_weight(y, s, z, t, yz, zy, m_M, m_Qp) + sum_val + camera_path::mis_partial_weight(scene, y, s, z, t, yz, zy, m_M, m_Qp) 337 | ); 338 | } 339 | 340 | L += ysm1.Le_throughput() * fyz * fzy * ztm1.throughput_We() * (G / (pmf * m_M) * mis_weight); 341 | } 342 | } 343 | return L; 344 | } 345 | 346 | /////////////////////////////////////////////////////////////////////////////////////////////////// 347 | 348 | } //namespace our 349 | 350 | /////////////////////////////////////////////////////////////////////////////////////////////////// 351 | --------------------------------------------------------------------------------