├── README.md ├── pathcut ├── SG.h ├── interval.h ├── newton.cpp ├── newton.h ├── pathcut.h ├── pathcut_wrapper.h ├── ptree.h └── visualizer.cpp └── pathcut_guided_path.cpp /README.md: -------------------------------------------------------------------------------- 1 | # caustics-pathcut 2 | Core code of the paper "Unbiased Caustics Rendering Guided by Representative Specular Paths". 3 | 4 | * pathcut_guided_path.cpp: our integrator modified from the original mitsuba path tracing integrator. 5 | * pathcut/: codes of our relaxed path cuts algorithm, SG approximation and parallax compensation. 6 | -------------------------------------------------------------------------------- /pathcut/SG.h: -------------------------------------------------------------------------------- 1 | // #pragma GCC optimize("O0") 2 | #pragma once 3 | #include 4 | #include 5 | 6 | using namespace mitsuba; 7 | struct SG { 8 | static SG product(const SG &g1, const SG &g2) { 9 | float lambda; 10 | Vector3 i_mean = weightedSumP(g1, g2, lambda); 11 | Spectrum c = g1.c * g2.c * std::max(math::fastexp(lambda - g1.lambda - g2.lambda), 0.f); 12 | return SG(i_mean, lambda, c); 13 | } 14 | static Vector3 weightedSumP(const SG &g1, const SG &g2, float &length) { 15 | Vector3 unnormalized = g1.lambda * g1.p + g2.lambda * g2.p; 16 | length = unnormalized.length(); 17 | return normalize(unnormalized); 18 | } 19 | static SG sgLight(float radius, const Spectrum &intensity, const Vector3 &dir_unnromalized) { 20 | float length = dir_unnromalized.length(); 21 | return SG(normalize(dir_unnromalized), 4 * pow(length / radius, 2), 2 * intensity); 22 | } 23 | static SG brdfSlice(const Normal &n, const Vector3 &o, float roughness, const BSDF *bsdf) { 24 | float cosThetaO = dot(o, n); 25 | float m2 = roughness * roughness; 26 | SG ndf(n, 2.f / m2, Spectrum(1.f) / (M_PI * m2)); 27 | float lambda = ndf.lambda / (4.f * cosThetaO); 28 | Vector3 p = reflect(o, n); 29 | if (bsdf->isConductor()) { 30 | Spectrum F = fresnelConductorApprox(cosThetaO, bsdf->getConductorEta(), bsdf->getConductorK()); 31 | float G1 = 1.0f / (cosThetaO + sqrt(m2 + (1 - m2) * cosThetaO * cosThetaO)); 32 | return SG(p, lambda, F * G1 * G1 * ndf.c); 33 | } else { 34 | float eta = bsdf->getProperties().getFloat("intIOR"); 35 | Spectrum d = bsdf->getProperties().getSpectrum("diffuseReflectance"); 36 | Spectrum F(fresnelDielectricExt(cosThetaO, eta)); 37 | float G1 = 1.0f / (cosThetaO + sqrt(m2 + (1 - m2) * cosThetaO * cosThetaO)); 38 | return SG(p, lambda, F * G1 * G1 * ndf.c * d); 39 | } 40 | } 41 | static SG btdfSlice(const SG &ndf, const Spectrum &mo, const Vector3 &o, float _eta) { 42 | // eta: intIOR/extIOR 43 | float cosThetaO = dot(o, ndf.p); 44 | Vector3 p = refract(o, ndf.p, _eta, cosThetaO); 45 | float eta = cosThetaO < 0 ? _eta : (1.f / _eta); 46 | float sqrtDenom = cosThetaO + eta * cosThetaO; 47 | float dwh_dwo = (eta * eta * cosThetaO) / (sqrtDenom * sqrtDenom); 48 | float lambda = ndf.lambda * dwh_dwo; 49 | return SG(p, lambda, mo * ndf.c); 50 | } 51 | static Spectrum convolve(const SG &g1, const SG &g2) { 52 | float dm = (g1.lambda * g1.p + g2.lambda * g2.p).length(); 53 | Spectrum expo = math::fastexp(dm - g1.lambda - g2.lambda) * g1.c * g2.c; 54 | float other = 1.0f - math::fastexp(-2.0f * dm); 55 | return (2.0f * M_PI * expo * other) / dm; 56 | } 57 | static SG convolveApproximation(const SG &gli, const SG &gs, const Normal &n) { 58 | float lambda3; 59 | Vector3 i_mean = weightedSumP(gli, gs, lambda3); 60 | Spectrum c = 2 * M_PI * gli.c * gs.c * std::fabs(dot(i_mean, n)) / lambda3; 61 | float lambda = gli.lambda * gs.lambda / (gli.lambda + gs.lambda); 62 | return SG(reflect(gli.p, n), lambda, c); 63 | } 64 | static SG ndf(float roughness, const Normal &n) { 65 | float m2 = roughness * roughness; 66 | return SG(n, 2.f / m2, Spectrum(1.f) / (M_PI * m2)); 67 | } 68 | static SG region(const Point &pos, const Point ¢er, const Vector3 &normal, float area) { 69 | Vector3 dir_unnormalized = center - pos; 70 | Vector3 p = normalize(dir_unnormalized); 71 | float omega = area * fabs(dot(p, normal)) / pow(dir_unnormalized.length(), 2); 72 | return SG(p, 4 * M_PI / omega, Spectrum(2)); 73 | } 74 | static Vector3 frameToWorld(const Vector3 n, const Vector3 dir) { 75 | Vector3 s, t; 76 | if (std::abs(n.x) > std::abs(n.y)) { 77 | mitsuba::Float invLen = 1.0f / std::sqrt(n.x * n.x + n.z * n.z); 78 | t = Vector3(n.z * invLen, 0.0f, -n.x * invLen); 79 | } else { 80 | mitsuba::Float invLen = 1.0f / std::sqrt(n.y * n.y + n.z * n.z); 81 | t = Vector3(0.0f, n.z * invLen, -n.y * invLen); 82 | } 83 | s = cross(t, n); 84 | return s * dir.x + t * dir.y + n * dir.z; 85 | } 86 | 87 | SG() {} 88 | SG(const Vector3 &p, float lambda, const Spectrum &c) : p(p), c(c), lambda(lambda) { 89 | } 90 | void computeNormalization() { 91 | eMin2Lambda = math::fastexp(-2 * lambda); 92 | norm = lambda / (2 * M_PI * (1 - eMin2Lambda)); 93 | } 94 | Spectrum evaluate(const Vector3 &x) const { 95 | return c * math::fastexp(lambda * (dot(x, p) - 1)); 96 | } 97 | Spectrum evaluate(const Vector3 ¢er, const Vector3 &x) const { 98 | return c * math::fastexp(lambda * (dot(x, center) - 1)); 99 | } 100 | Spectrum integral() const { 101 | return 2 * M_PI * c * (1 - eMin2Lambda) / lambda; 102 | } 103 | 104 | float pdf(const Vector3 &x) const { 105 | return math::fastexp(lambda * (dot(x, p) - 1)) * norm; 106 | } 107 | 108 | float pdf(const Vector3 ¢er, const Vector3 &x) const { 109 | float dotVal = dot(x, center); 110 | return math::fastexp(lambda * (dotVal - 1.f)) * norm; 111 | } 112 | 113 | Vector3 sample(const Point2 sample) const { 114 | float cosTheta = 1 + (std::log(sample.x + eMin2Lambda * (1 - sample.x))) / lambda; 115 | const float sinTheta = (1.0f - cosTheta * cosTheta <= 0.0f) ? 0.0f : std::sqrt(1.f - cosTheta * cosTheta); 116 | const float phi = 2.f * M_PI * sample[1]; 117 | 118 | float sinPhi, cosPhi; 119 | sincosf(phi, &sinPhi, &cosPhi); 120 | Vector3 dir(sinTheta * cosPhi, sinTheta * sinPhi, cosTheta); 121 | dir = frameToWorld(p, dir); 122 | return dir; 123 | } 124 | 125 | Vector3 sample(const Vector3 ¢er, const Point2 sample) const { 126 | const float cosTheta = 1.f + std::log1p(eMin2Lambda * sample.x - sample.x) / lambda; 127 | // float cosTheta = 1 + (std::log(sample.x + eMin2Lambda * (1 - sample.x))) / lambda; 128 | const float sinTheta = (1.0f - cosTheta * cosTheta <= 0.0f) ? 0.0f : std::sqrt(1.f - cosTheta * cosTheta); 129 | const float phi = 2.f * M_PI * sample[1]; 130 | 131 | float sinPhi, cosPhi; 132 | sincosf(phi, &sinPhi, &cosPhi); 133 | Vector3 dir(sinTheta * cosPhi, sinTheta * sinPhi, cosTheta); 134 | dir = frameToWorld(center, dir); 135 | return dir; 136 | } 137 | 138 | Vector3 p; 139 | Spectrum c; 140 | float lambda; 141 | float eMin2Lambda; 142 | float norm; 143 | }; 144 | -------------------------------------------------------------------------------- /pathcut/interval.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #if !defined(__INTERVAL_H_) 3 | #define __INTERVAL_H_ 4 | 5 | #include 6 | #include 7 | using mitsuba::AABB; 8 | using mitsuba::Point; 9 | using mitsuba::Vector2; 10 | using mitsuba::Vector3; 11 | using std::max; 12 | using std::min; 13 | 14 | class Interval1D { 15 | public: 16 | Interval1D() : vMin(1e10f), vMax(1e-10f){}; 17 | Interval1D(float _min, float _max) : vMin(_min), vMax(_max) {} 18 | Interval1D(float _min) : vMin(_min), vMax(_min) {} 19 | //[x_1, x_2] + [y_1, y_2] = [x_1+y_1, x_2+y_2] 20 | /// Add two vectors and return the result 21 | 22 | inline Interval1D operator+(const Interval1D &b) const { 23 | return Interval1D(vMin + b.vMin, vMax + b.vMax); 24 | } 25 | 26 | //[x_1, x_2] - [y_1, y_2] = [x_1-y_2, x_2-y_1] 27 | inline Interval1D operator-(const Interval1D &b) const { 28 | return Interval1D(vMin - b.vMax, vMax - b.vMin); 29 | } 30 | inline Interval1D operator-(const float &b) const { 31 | return Interval1D(vMin - b, vMax - b); 32 | } 33 | 34 | //[x_1, x_2] \cdot [y_1, y_2] = [\min(x_1 y_1,x_1 y_2,x_2 y_1,x_2 y_2), \max(x_1 y_1,x_1 y_2,x_2 y_1,x_2 y_2)] 35 | inline Interval1D operator*(const Interval1D &b) const { 36 | return Interval1D(min(min(vMin * b.vMin, vMin * b.vMax), min(vMax * b.vMin, vMax * b.vMax)), 37 | max(max(vMin * b.vMin, vMin * b.vMax), max(vMax * b.vMin, vMax * b.vMax))); 38 | } 39 | 40 | inline Interval1D operator*(float b) const { 41 | return Interval1D(min(vMin * b, vMax * b), max(vMin * b, vMax * b)); 42 | } 43 | 44 | inline Interval1D inverse(const Interval1D &b) const { 45 | if (b.vMin > 0 || b.vMax < 0) //0 is not in the range 46 | { 47 | return Interval1D(1.0 / b.vMax, 1.0 / b.vMin); 48 | } else { 49 | return Interval1D(-1e10, 1e10); 50 | } 51 | } 52 | 53 | inline Interval1D operator/(const Interval1D &b) const { 54 | Interval1D inverseB = inverse(b); 55 | Interval1D result(vMin, vMax); 56 | return result * inverseB; 57 | } 58 | 59 | inline Interval1D sqrt() const { 60 | return Interval1D(std::sqrt(vMin), std::sqrt(vMax)); 61 | } 62 | 63 | inline Interval1D square() const { 64 | float vMin_2 = vMin * vMin; 65 | float vMax_2 = vMax * vMax; 66 | float maxSquare = max(vMin_2, vMax_2); 67 | 68 | if (vMin < 0 && vMax > 0) { 69 | return Interval1D(1e-7, maxSquare); 70 | } else { 71 | return Interval1D(min(vMin_2, vMax_2), maxSquare); 72 | } 73 | } 74 | /// Return a negated version of the vector 75 | inline Interval1D operator-() const { 76 | return Interval1D(-vMax, -vMin); 77 | } 78 | inline float length() const { 79 | return vMax - vMin; 80 | } 81 | 82 | inline Interval1D clampZero() { 83 | return Interval1D(std::max(vMin, 0.0f), std::max(vMax, 0.0f)); 84 | } 85 | 86 | inline Interval1D intersect(const Interval1D &b) const { 87 | return Interval1D(std::max(vMin, b.vMin), std::min(vMax, b.vMax)); 88 | } 89 | 90 | inline Interval1D acos() { 91 | return Interval1D(std::acos(vMax), std::acos(vMin)); 92 | } 93 | 94 | inline bool coverZero() { 95 | if (vMin > 0 || vMax < 0) { 96 | return false; 97 | } 98 | return true; 99 | } 100 | inline bool subset(const Interval1D &b) const { 101 | return vMin >= b.vMin && vMax <= b.vMax; 102 | } 103 | 104 | inline bool empty() const { 105 | return vMin > vMax; 106 | } 107 | 108 | inline float mid() const { 109 | return (vMin + vMax) / 2; 110 | } 111 | inline void expand(float val) { 112 | vMin = min(vMin, val); 113 | vMax = max(vMax, val); 114 | } 115 | float vMin; 116 | float vMax; 117 | }; 118 | 119 | inline Interval1D minmax(const Interval1D &a, const Interval1D &b) { 120 | return Interval1D(min(a.vMin, b.vMin), max(a.vMax, b.vMax)); 121 | } 122 | inline Interval1D minmax(const Interval1D &a, const float &b) { 123 | return Interval1D(min(a.vMin, b), max(a.vMax, b)); 124 | } 125 | 126 | inline Interval1D minus1D(const float &a, const Interval1D &b) { 127 | return Interval1D(a - b.vMax, a - b.vMin); 128 | } 129 | 130 | class Interval3D { 131 | public: 132 | Interval3D() { 133 | for (int i = 0; i < 3; i++) { 134 | value[i] = Interval1D(1e10f, -1e10f); 135 | } 136 | } 137 | Interval3D(const Point &_min, const Point &_max) { 138 | for (int i = 0; i < 3; i++) { 139 | value[i] = Interval1D(_min[i], _max[i]); 140 | } 141 | } 142 | Interval3D(const Point &_value) { 143 | for (int i = 0; i < 3; i++) { 144 | value[i] = Interval1D(_value[i], _value[i]); 145 | } 146 | } 147 | Interval3D(float pos) { 148 | for (int i = 0; i < 3; i++) { 149 | value[i] = Interval1D(pos, pos); 150 | } 151 | } 152 | Interval3D(const Vector3 &_value) { 153 | for (int i = 0; i < 3; i++) { 154 | value[i] = Interval1D(_value[i], _value[i]); 155 | } 156 | } 157 | Interval3D(const Interval1D v[]) { 158 | for (int i = 0; i < 3; i++) { 159 | value[i] = v[i]; 160 | } 161 | } 162 | 163 | inline Interval1D dot(const Interval3D &b) { 164 | return value[0] * b.value[0] + value[1] * b.value[1] + value[2] * b.value[2]; 165 | } 166 | 167 | inline Interval1D dot(const Interval3D &b) const { 168 | return value[0] * b.value[0] + value[1] * b.value[1] + value[2] * b.value[2]; 169 | } 170 | 171 | inline Interval3D operator+(const Interval3D &b) const { 172 | Interval1D result[3]; 173 | for (int i = 0; i < 3; i++) { 174 | result[i] = value[i] + b.value[i]; 175 | } 176 | return Interval3D(result); 177 | } 178 | 179 | inline Interval3D operator-(const Interval3D &b) const { 180 | Interval1D result[3]; 181 | for (int i = 0; i < 3; i++) { 182 | result[i] = value[i] - b.value[i]; 183 | } 184 | return Interval3D(result); 185 | } 186 | 187 | inline Interval3D operator*(float b) const { 188 | Interval1D result[3]; 189 | for (int i = 0; i < 3; i++) { 190 | result[i] = value[i] * b; 191 | } 192 | return Interval3D(result); 193 | } 194 | 195 | inline Interval3D operator*(const Interval1D &b) const { 196 | Interval1D result[3]; 197 | for (int i = 0; i < 3; i++) { 198 | result[i] = value[i] * b; 199 | } 200 | return Interval3D(result); 201 | } 202 | 203 | inline Interval3D operator/(const Interval1D &b) const { 204 | Interval1D temp = b.inverse(b); 205 | Interval1D result[3]; 206 | for (int i = 0; i < 3; i++) { 207 | result[i] = value[i] * temp; 208 | } 209 | return Interval3D(result); 210 | } 211 | 212 | inline Interval3D operator-(const Vector3 &b) const { 213 | Interval1D result[3]; 214 | for (int i = 0; i < 3; i++) { 215 | result[i] = value[i] - b[i]; 216 | } 217 | return Interval3D(result); 218 | } 219 | 220 | inline Interval1D squaredNorm() const { 221 | Interval1D result = (value[0].square() + value[1].square() + value[2].square()); 222 | return result; 223 | } 224 | 225 | inline Interval1D norm() { 226 | Interval1D result = (value[0].square() + value[1].square() + value[2].square()); 227 | result.vMin = sqrt(result.vMin); 228 | result.vMax = sqrt(result.vMax); 229 | return result; 230 | } 231 | 232 | inline Interval1D norm3() { 233 | Interval1D result = (value[0].square() + value[1].square() + value[2].square()); 234 | result.vMin = pow(sqrt(result.vMin), 3.0f); 235 | result.vMax = pow(sqrt(result.vMax), 3.0f); 236 | 237 | return result; 238 | } 239 | 240 | inline Interval3D normalized() const { 241 | //get max & min lengths of normals in normal box 242 | Interval1D lengthSquare = squaredNorm(); 243 | if (lengthSquare.vMin < 0) 244 | return Interval3D(Point(-1), Point(1)); 245 | 246 | Interval1D length = lengthSquare.sqrt(); 247 | Interval1D invLength = length.inverse(length); 248 | Interval1D result[3]; 249 | for (int i = 0; i < 3; i++) { 250 | result[i] = value[i] * invLength; 251 | result[i].vMin = std::max(result[i].vMin, -1.0f); 252 | result[i].vMax = std::min(result[i].vMax, 1.0f); 253 | } 254 | 255 | return Interval3D(result); 256 | } 257 | 258 | inline void expand(const Vector3 &dir) { 259 | for (int i = 0; i < 3; i++) { 260 | value[i].vMin = min(value[i].vMin, dir[i]); 261 | value[i].vMax = max(value[i].vMax, dir[i]); 262 | } 263 | } 264 | inline bool contain(const Interval3D &box) const { 265 | for (int i = 0; i < 3; i++) { 266 | if (!box.value[i].subset(value[i])) { 267 | return false; 268 | } 269 | } 270 | return true; 271 | } 272 | inline bool cover(const Point &p) const { 273 | for (int i = 0; i < 3; i++) { 274 | if (value[i].vMin > p[i] || value[i].vMax < p[i]) { 275 | return false; 276 | } 277 | } 278 | return true; 279 | } 280 | inline bool coverZero() const { 281 | for (int i = 0; i < 3; i++) { 282 | if (value[i].vMin > 0 || value[i].vMax < 0) { 283 | return false; 284 | } 285 | } 286 | return true; 287 | } 288 | inline bool smallerThan(float threshold) const { 289 | for (int i = 0; i < 3; i++) { 290 | if (value[i].vMin > threshold || value[i].vMax < -threshold) { 291 | return false; 292 | } 293 | } 294 | return true; 295 | } 296 | inline bool valid() const { 297 | for (int i = 0; i < 3; i++) { 298 | if (value[i].vMin > value[i].vMax) { 299 | return false; 300 | } 301 | } 302 | return true; 303 | } 304 | 305 | inline float area() const { 306 | //surface area 307 | float area = value[0].length() * value[1].length() + 308 | value[0].length() * value[2].length() + 309 | value[1].length() * value[2].length(); 310 | area *= 2; 311 | return area; 312 | } 313 | 314 | AABB getAABB() const { 315 | Point min(value[0].vMin, value[1].vMin, value[2].vMin); 316 | Point max(value[0].vMax, value[1].vMax, value[2].vMax); 317 | return AABB(min, max); 318 | } 319 | 320 | inline Vector3 extents() const { 321 | return Vector3(value[0].length(), value[1].length(), value[2].length()); 322 | } 323 | 324 | inline float volumeSphere() { 325 | float maxDist = -1; 326 | for (int i = 0; i < 3; i++) { 327 | float temp = value[i].vMax - value[i].vMin; 328 | maxDist = max(maxDist, temp); 329 | } 330 | return 4 / 3.0f * M_PI * pow(maxDist * 0.5, 3.0); 331 | } 332 | 333 | inline Interval3D operator-() const { 334 | Interval1D result[3]; 335 | for (int i = 0; i < 3; i++) { 336 | result[i] = -value[i]; 337 | } 338 | 339 | return Interval3D(result); 340 | } 341 | 342 | inline Interval3D intersect(const Interval3D &b) const { 343 | Interval1D result[3]; 344 | for (int i = 0; i < 3; i++) { 345 | result[i] = value[i].intersect(b.value[i]); 346 | } 347 | return Interval3D(result); 348 | } 349 | 350 | inline bool isEmpty() { 351 | for (int i = 0; i < 3; i++) { 352 | if (value[i].vMin > value[i].vMax) 353 | return true; 354 | } 355 | return false; 356 | } 357 | 358 | inline Point center(float &radius) const { 359 | Point result; 360 | float r = 0.0f; 361 | for (int i = 0; i < 3; i++) { 362 | result[i] = (value[i].vMin + value[i].vMax) * 0.5; 363 | float temp = value[i].vMin - result[i]; 364 | r += temp * temp; 365 | } 366 | radius = sqrtf(r); 367 | return result; 368 | } 369 | inline Point center() const { 370 | Point result; 371 | for (int i = 0; i < 3; i++) { 372 | result[i] = (value[i].vMin + value[i].vMax) * 0.5f; 373 | } 374 | return result; 375 | } 376 | inline Vector3 centerVector() const { 377 | Vector3 result; 378 | for (int i = 0; i < 3; i++) { 379 | result[i] = (value[i].vMin + value[i].vMax) * 0.5; 380 | } 381 | return result; 382 | } 383 | inline Vector3 clamp(const Vector3 &vec) { 384 | Vector3 result; 385 | for (int i = 0; i < 3; i++) { 386 | result[i] = max(min(result[i], value[i].vMax), value[i].vMin); 387 | } 388 | return result; 389 | } 390 | 391 | Interval1D value[3]; 392 | }; 393 | 394 | inline Interval3D minmax(const Interval3D &a, const Interval3D &b) { 395 | Interval1D result[3]; 396 | for (int i = 0; i < 3; i++) { 397 | result[i] = minmax(a.value[i], b.value[i]); 398 | } 399 | return Interval3D(result); 400 | } 401 | 402 | inline Interval3D minmax(const Interval3D &a, const Point &b) { 403 | Interval1D result[3]; 404 | for (int i = 0; i < 3; i++) { 405 | result[i] = minmax(a.value[i], b[i]); 406 | } 407 | return Interval3D(result); 408 | } 409 | inline Interval3D minmax(const Interval3D &a, const Vector3 &b) { 410 | Interval1D result[3]; 411 | for (int i = 0; i < 3; i++) { 412 | result[i] = minmax(a.value[i], b[i]); 413 | } 414 | return Interval3D(result); 415 | } 416 | 417 | inline Interval3D reflect(const Interval3D &wi, const Interval3D &n) { 418 | return n * (wi.dot(n) * 2) - wi; 419 | } 420 | 421 | inline Interval3D minus3D(const Vector3 &a, const Interval3D &b) { 422 | Interval1D result[3]; 423 | for (int i = 0; i < 3; i++) { 424 | result[i] = minus1D(a[i], b.value[i]); 425 | } 426 | return Interval3D(result); 427 | } 428 | 429 | inline Interval3D mul(const Interval1D &a, const Vector3 &b) { 430 | Interval1D result[3]; 431 | for (int i = 0; i < 3; i++) { 432 | result[i] = a * b[i]; 433 | } 434 | return Interval3D(result); 435 | } 436 | 437 | inline Interval3D minus(const Vector3 &a, const Interval3D &b) { 438 | Interval1D result[3]; 439 | for (int i = 0; i < 3; i++) { 440 | result[i] = Interval1D(a[i]) - b.value[i]; 441 | } 442 | return Interval3D(result); 443 | } 444 | 445 | #endif 446 | -------------------------------------------------------------------------------- /pathcut/newton.cpp: -------------------------------------------------------------------------------- 1 | #include "newton.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using autodiff::ArrayXreal; 9 | using autodiff::real; 10 | using autodiff::Vector3real; 11 | using autodiff::VectorXreal; 12 | 13 | #define MAX_STEP 20 14 | 15 | float solveOneBounce(VertInfo &reflVert, const std::array &triInfo, 16 | const std::array &lightPos_, const std::array &camPos_, float errorThreshold) { 17 | float minError = INFINITY; 18 | Vector3real lightPos(lightPos_[0], lightPos_[1], lightPos_[2]); 19 | Vector3real camPos(camPos_[0], camPos_[1], camPos_[2]); 20 | Vector3real pos; 21 | Vector3real normal; 22 | 23 | auto f = [lightPos, camPos, triInfo, &pos, &normal](const VectorXreal &x) { 24 | auto alpha = x[0]; 25 | auto beta = x[1]; 26 | auto gamma = 1 - alpha - beta; 27 | 28 | pos[0] = triInfo[0].pos[0] * alpha + triInfo[1].pos[0] * beta + triInfo[2].pos[0] * gamma; 29 | pos[1] = triInfo[0].pos[1] * alpha + triInfo[1].pos[1] * beta + triInfo[2].pos[1] * gamma; 30 | pos[2] = triInfo[0].pos[2] * alpha + triInfo[1].pos[2] * beta + triInfo[2].pos[2] * gamma; 31 | normal[0] = triInfo[0].normal[0] * alpha + triInfo[1].normal[0] * beta + triInfo[2].normal[0] * gamma; 32 | normal[1] = triInfo[0].normal[1] * alpha + triInfo[1].normal[1] * beta + triInfo[2].normal[1] * gamma; 33 | normal[2] = triInfo[0].normal[2] * alpha + triInfo[1].normal[2] * beta + triInfo[2].normal[2] * gamma; 34 | normal.normalize(); 35 | 36 | Vector3real wi, wo; 37 | wi = (lightPos - pos).normalized(); 38 | wo = (camPos - pos).normalized(); 39 | Vector3real h = (wi + wo).normalized(); 40 | Vector3real result = normal - h; 41 | return result; 42 | }; 43 | 44 | VectorXreal x(2); 45 | x.fill(1.0 / 3.0); 46 | for (int iter = 0; iter < MAX_STEP; iter++) { 47 | VectorXreal F; 48 | 49 | auto J = autodiff::jacobian(f, autodiff::wrt(x), autodiff::at(x), F); 50 | float error = F.cwiseAbs().sum().val(); 51 | // error ? 52 | if (error < minError) { 53 | minError = error; 54 | reflVert.pos[0] = pos[0].val(); 55 | reflVert.pos[1] = pos[1].val(); 56 | reflVert.pos[2] = pos[2].val(); 57 | reflVert.normal[0] = normal[0].val(); 58 | reflVert.normal[1] = normal[1].val(); 59 | reflVert.normal[2] = normal[2].val(); 60 | if (error < errorThreshold) { 61 | return error; 62 | } 63 | } 64 | 65 | auto JT = J.transpose(); 66 | auto invJ = (JT * J).inverse() * JT; 67 | auto x_delta = invJ * F; 68 | auto x_delta2 = x_delta / 4.0; 69 | x = x - x_delta2; 70 | float bound = 0.2; 71 | if (x[0] < 0 - bound || x[0] > 1 + bound || x[1] < 0 - bound || x[1] > 1 + bound || x[0] + x[1] < 0 - bound || x[0] + x[1] > 1 + bound) { 72 | auto edge = [=](int idx1, int idx2) { 73 | return Vector3real(triInfo[idx1].pos[0] - triInfo[idx2].pos[0], triInfo[idx1].pos[1] - triInfo[idx2].pos[1], triInfo[idx1].pos[2] - triInfo[idx2].pos[2]); 74 | }; 75 | auto pos = [=](int idx) { 76 | return Vector3real(triInfo[idx].pos[0], triInfo[idx].pos[1], triInfo[idx].pos[2]); 77 | }; 78 | Vector3real p; 79 | for (int dim = 0; dim < 3; dim++) { 80 | p[dim] = x[0] * triInfo[0].pos[dim] + x[1] * triInfo[1].pos[dim] + (1 - x[0] - x[1]) * triInfo[2].pos[dim]; 81 | } 82 | if (x[0] < 0) { 83 | auto e = edge(2, 1); 84 | real t = (p - pos(1)).dot(e) / e.squaredNorm(); 85 | t = std::min(1.0, std::max(t.val(), 0.0)); 86 | x[0] = 0; 87 | x[1] = 1 - t; 88 | } else if (x[1] < 0) { 89 | auto e = edge(0, 2); 90 | real t = (p - pos(2)).dot(e) / e.squaredNorm(); 91 | t = std::min(1.0, std::max(t.val(), 0.0)); 92 | x[0] = t; 93 | x[1] = 0; 94 | } else if (x[0] + x[1] > 1) { 95 | auto e = edge(1, 0); 96 | real t = (p - pos(0)).dot(e) / e.squaredNorm(); 97 | t = std::min(1.0, std::max(t.val(), 0.0)); 98 | x[0] = 1 - t; 99 | x[1] = t; 100 | } 101 | } 102 | 103 | // auto maxCoeff = x_delta.maxCoeff(); 104 | // auto minCoeff = x_delta.minCoeff(); 105 | // x = x - x_delta / std::max(1.0, std::max(fabs(minCoeff.val()), maxCoeff.val())); 106 | // std::cout << "x_delta: \n" 107 | // << x_delta << "\n"; 108 | // std::cout << "x_delta2: \n" 109 | // << x_delta2 << "\n"; 110 | // std::cout << "F: \n" 111 | // << F << "\n"; 112 | // std::cout << "error: \n" 113 | // << error << "\n"; 114 | // std::cout << "vertPos: \n" 115 | // << pos << "\n"; 116 | // std::cout << "vertN: \n" 117 | // << normal << "\n"; 118 | // std::cin.get(); 119 | } 120 | return minError; 121 | } 122 | 123 | float solveTwoBounce(VertInfo &reflVert1, VertInfo &reflVert2, const std::array &triInfo1, const std::array &triInfo2, 124 | const std::array &lightPos_, const std::array &camPos_, float errorThreshold) { 125 | float minError = INFINITY; 126 | Vector3real lightPos(lightPos_[0], lightPos_[1], lightPos_[2]); 127 | Vector3real camPos(camPos_[0], camPos_[1], camPos_[2]); 128 | Vector3real pos1, pos2; 129 | Vector3real n1, n2; 130 | auto f = [lightPos, camPos, triInfo1, triInfo2, &pos1, &n1, &pos2, &n2](const VectorXreal &x) { 131 | // first point 132 | auto alpha1 = x[0]; 133 | auto beta1 = x[1]; 134 | auto gamma1 = 1 - alpha1 - beta1; 135 | pos1[0] = triInfo1[0].pos[0] * alpha1 + triInfo1[1].pos[0] * beta1 + triInfo1[2].pos[0] * gamma1; 136 | pos1[1] = triInfo1[0].pos[1] * alpha1 + triInfo1[1].pos[1] * beta1 + triInfo1[2].pos[1] * gamma1; 137 | pos1[2] = triInfo1[0].pos[2] * alpha1 + triInfo1[1].pos[2] * beta1 + triInfo1[2].pos[2] * gamma1; 138 | n1[0] = triInfo1[0].normal[0] * alpha1 + triInfo1[1].normal[0] * beta1 + triInfo1[2].normal[0] * gamma1; 139 | n1[1] = triInfo1[0].normal[1] * alpha1 + triInfo1[1].normal[1] * beta1 + triInfo1[2].normal[1] * gamma1; 140 | n1[2] = triInfo1[0].normal[2] * alpha1 + triInfo1[1].normal[2] * beta1 + triInfo1[2].normal[2] * gamma1; 141 | n1.normalize(); 142 | // second point 143 | auto alpha2 = x[2]; 144 | auto beta2 = x[3]; 145 | auto gamma2 = 1 - alpha2 - beta2; 146 | pos2[0] = triInfo2[0].pos[0] * alpha2 + triInfo2[1].pos[0] * beta2 + triInfo2[2].pos[0] * gamma2; 147 | pos2[1] = triInfo2[0].pos[1] * alpha2 + triInfo2[1].pos[1] * beta2 + triInfo2[2].pos[1] * gamma2; 148 | pos2[2] = triInfo2[0].pos[2] * alpha2 + triInfo2[1].pos[2] * beta2 + triInfo2[2].pos[2] * gamma2; 149 | n2[0] = triInfo2[0].normal[0] * alpha2 + triInfo2[1].normal[0] * beta2 + triInfo2[2].normal[0] * gamma2; 150 | n2[1] = triInfo2[0].normal[1] * alpha2 + triInfo2[1].normal[1] * beta2 + triInfo2[2].normal[1] * gamma2; 151 | n2[2] = triInfo2[0].normal[2] * alpha2 + triInfo2[1].normal[2] * beta2 + triInfo2[2].normal[2] * gamma2; 152 | n2.normalize(); 153 | 154 | Vector3real wi1, wo1, h1, wi2, wo2, h2; 155 | wi1 = (lightPos - pos1).normalized(); 156 | wo1 = (pos2 - pos1).normalized(); 157 | h1 = (wi1 + wo1).normalized(); 158 | 159 | wi2 = (pos1 - pos2).normalized(); 160 | wo2 = (camPos - pos2).normalized(); 161 | h2 = (wi2 + wo2).normalized(); 162 | Vector3real d1 = n1 - h1; 163 | Vector3real d2 = n2 - h2; 164 | VectorXreal result(6); 165 | result[0] = d1[0]; 166 | result[1] = d1[1]; 167 | result[2] = d1[2]; 168 | result[3] = d2[0]; 169 | result[4] = d2[1]; 170 | result[5] = d2[2]; 171 | return result; 172 | }; 173 | 174 | VectorXreal x(4); 175 | x.fill(1.0 / 3.0); 176 | 177 | for (int iter = 0; iter < MAX_STEP; iter++) { 178 | 179 | VectorXreal F; 180 | 181 | auto J = autodiff::jacobian(f, autodiff::wrt(x), autodiff::at(x), F); 182 | float error = F.cwiseAbs().sum().val(); 183 | if (error < minError) { 184 | minError = error; 185 | reflVert1.pos[0] = pos1[0].val(); 186 | reflVert1.pos[1] = pos1[1].val(); 187 | reflVert1.pos[2] = pos1[2].val(); 188 | reflVert1.normal[0] = n1[0].val(); 189 | reflVert1.normal[1] = n1[1].val(); 190 | reflVert1.normal[2] = n1[2].val(); 191 | reflVert2.pos[0] = pos2[0].val(); 192 | reflVert2.pos[1] = pos2[1].val(); 193 | reflVert2.pos[2] = pos2[2].val(); 194 | reflVert2.normal[0] = n2[0].val(); 195 | reflVert2.normal[1] = n2[1].val(); 196 | reflVert2.normal[2] = n2[2].val(); 197 | if (error < errorThreshold) { 198 | return error; 199 | } 200 | } 201 | 202 | auto JT = J.transpose(); 203 | auto invJ = (JT * J).inverse() * JT; 204 | auto x_delta = invJ * F; 205 | auto x_delta2 = x_delta / 4.0; 206 | x = x - x_delta2; 207 | float bound = 0; 208 | if (x[0] < 0 - bound || x[0] > 1 + bound || x[1] < 0 - bound || x[1] > 1 + bound || x[0] + x[1] < 0 - bound || x[0] + x[1] > 1 + bound) { 209 | auto edge = [=](int idx1, int idx2) { 210 | return Vector3real(triInfo1[idx1].pos[0] - triInfo1[idx2].pos[0], triInfo1[idx1].pos[1] - triInfo1[idx2].pos[1], triInfo1[idx1].pos[2] - triInfo1[idx2].pos[2]); 211 | }; 212 | auto pos = [=](int idx) { 213 | return Vector3real(triInfo1[idx].pos[0], triInfo1[idx].pos[1], triInfo1[idx].pos[2]); 214 | }; 215 | Vector3real p; 216 | for (int dim = 0; dim < 3; dim++) { 217 | p[dim] = x[0] * triInfo1[0].pos[dim] + x[1] * triInfo1[1].pos[dim] + (1 - x[0] - x[1]) * triInfo1[2].pos[dim]; 218 | } 219 | if (x[0] < 0) { 220 | auto e = edge(2, 1); 221 | real t = (p - pos(1)).dot(e) / e.squaredNorm(); 222 | t = std::min(1.0, std::max(t.val(), 0.0)); 223 | x[0] = 0; 224 | x[1] = 1 - t; 225 | } else if (x[1] < 0) { 226 | auto e = edge(0, 2); 227 | real t = (p - pos(2)).dot(e) / e.squaredNorm(); 228 | t = std::min(1.0, std::max(t.val(), 0.0)); 229 | x[0] = t; 230 | x[1] = 0; 231 | } else if (x[0] + x[1] > 1) { 232 | auto e = edge(1, 0); 233 | real t = (p - pos(0)).dot(e) / e.squaredNorm(); 234 | t = std::min(1.0, std::max(t.val(), 0.0)); 235 | x[0] = 1 - t; 236 | x[1] = t; 237 | } 238 | iter = std::max(18, iter); 239 | } 240 | if (x[2] < 0 - bound || x[2] > 1 + bound || x[3] < 0 - bound || x[3] > 1 + bound || x[2] + x[3] < 0 - bound || x[2] + x[3] > 1 + bound) { 241 | auto edge = [=](int idx1, int idx2) { 242 | return Vector3real(triInfo2[idx1].pos[0] - triInfo2[idx2].pos[0], triInfo2[idx1].pos[1] - triInfo2[idx2].pos[1], triInfo2[idx1].pos[2] - triInfo2[idx2].pos[2]); 243 | }; 244 | auto pos = [=](int idx) { 245 | return Vector3real(triInfo2[idx].pos[0], triInfo2[idx].pos[1], triInfo2[idx].pos[2]); 246 | }; 247 | Vector3real p; 248 | for (int dim = 0; dim < 3; dim++) { 249 | p[dim] = x[2] * triInfo2[0].pos[dim] + x[3] * triInfo2[1].pos[dim] + (1 - x[2] - x[3]) * triInfo2[2].pos[dim]; 250 | } 251 | if (x[2] < 0) { 252 | auto e = edge(2, 1); 253 | real t = (p - pos(1)).dot(e) / e.squaredNorm(); 254 | t = std::min(1.0, std::max(t.val(), 0.0)); 255 | x[2] = 0; 256 | x[3] = 1 - t; 257 | } else if (x[3] < 0) { 258 | auto e = edge(0, 2); 259 | real t = (p - pos(2)).dot(e) / e.squaredNorm(); 260 | t = std::min(1.0, std::max(t.val(), 0.0)); 261 | x[2] = t; 262 | x[3] = 0; 263 | } else if (x[2] + x[3] > 1) { 264 | auto e = edge(1, 0); 265 | real t = (p - pos(0)).dot(e) / e.squaredNorm(); 266 | t = std::min(1.0, std::max(t.val(), 0.0)); 267 | x[2] = 1 - t; 268 | x[3] = t; 269 | } 270 | } 271 | } 272 | return minError; 273 | } -------------------------------------------------------------------------------- /pathcut/newton.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct VertInfo { 5 | std::array pos; 6 | std::array normal; 7 | }; 8 | 9 | //hard coded Newton solver for one-bounce and two-bounce reflections. 10 | float solveOneBounce(VertInfo &reflVert, const std::array &triInfo, 11 | const std::array &lightPos, const std::array &camPos, float errorThreshold); 12 | 13 | float solveTwoBounce(VertInfo &reflVert1, VertInfo &reflVert2, const std::array &triInfo1, const std::array &triInfo2, 14 | const std::array &lightPos_, const std::array &camPos_, float errorThreshold); -------------------------------------------------------------------------------- /pathcut/pathcut.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | #include 6 | 7 | #include "SG.h" 8 | #include "newton.h" 9 | #include "ptree.h" 10 | 11 | using namespace mitsuba; 12 | 13 | struct IntervalPath { 14 | struct Vert { 15 | const PNode *pnode; 16 | const TriMesh *mesh; 17 | int type; 18 | bool guided; 19 | }; 20 | std::vector bounces; 21 | Point lightPos; 22 | Point camPos; 23 | Vector camDir; 24 | Spectrum lightIntensity; 25 | Interval3D lightBound; 26 | Interval3D lightDirBound; 27 | int lightType; 28 | float lightCutoff; 29 | }; 30 | 31 | struct ParallaxStruct { 32 | Frame reflFrame; 33 | Point origin; 34 | float diU, hiU, diV, hiV; 35 | ParallaxStruct() : diU(INFINITY), hiU(0), diV(INFINITY), hiV(0) {} 36 | ParallaxStruct(const Point &reflPoint, const Point &lightPos, const Frame &tangentFrame, float focalLengthU, float focalLengthV, 37 | float &d_o, float &hoU, float &hoV) 38 | : reflFrame(tangentFrame), origin(reflPoint) { 39 | Vector lightRel = tangentFrame.toLocal(lightPos - reflPoint); 40 | hoU = lightRel.x; 41 | hoV = lightRel.y; 42 | d_o = lightRel.z; 43 | diU = 1 / (1 / focalLengthU - 1 / d_o), 44 | diV = 1 / (1 / focalLengthV - 1 / d_o), 45 | hiU = -diU / d_o * hoU, 46 | hiV = -diV / d_o * hoV; 47 | } 48 | void parallaxDir(const Point &pos, Vector &dir) const { 49 | if (std::isinf(diU) && std::isinf(diV)) { 50 | return; 51 | } 52 | Vector posRel = pos - origin; 53 | Vector posLocal = reflFrame.toLocal(posRel); 54 | float de = posLocal.z, heU = posLocal.x, heV = posLocal.y; 55 | float deltaU = (diU * heU - de * hiU) / (diU - de); 56 | float deltaV = (diV * heV - de * hiV) / (diV - de); 57 | Vector reflPoint = reflFrame.s * deltaU + reflFrame.t * deltaV; 58 | dir = normalize(reflPoint - posRel); 59 | } 60 | void imageDist(const Point &pos, float &distU, float &distV) const { 61 | if (std::isinf(diU) && std::isinf(diV)) { 62 | return; 63 | } 64 | Vector posRel = pos - origin; 65 | Vector posLocal = reflFrame.toLocal(posRel); 66 | float de = posLocal.z, heU = posLocal.x, heV = posLocal.y; 67 | distU = std::sqrt((de - diU) * (de - diU) + (heU - hiU) * (heU - hiU)) / std::sqrt(diU * diU + hiU * hiU); 68 | distV = std::sqrt((de - diV) * (de - diV) + (heV - hiV) * (heV - hiV)) / std::sqrt(diV * diV + hiV * hiV); 69 | } 70 | Point imageWorldPosU() const { 71 | Vector imageRel = diU * reflFrame.n + hiU * reflFrame.s; 72 | Point world = origin + imageRel; 73 | return world; 74 | } 75 | Point imageWorldPosV() const { 76 | Vector imageRel = diV * reflFrame.n + hiV * reflFrame.t; 77 | Point world = origin + imageRel; 78 | return world; 79 | } 80 | }; 81 | struct SGLight { 82 | SG sg; 83 | ParallaxStruct parallaxInfo; 84 | float weight; 85 | }; 86 | 87 | struct SGMixture { 88 | std::vector sgLights; 89 | 90 | Spectrum eval(Vector3 d) const { 91 | Spectrum result(0.f); 92 | for (size_t idx = 0; idx < sgLights.size(); idx++) { 93 | result += sgLights[idx].sg.evaluate(d); 94 | } 95 | return result; 96 | } 97 | Spectrum evalParallax(Point pos, Vector3 d) const { 98 | Spectrum result(0.f); 99 | for (const auto &vert : sgLights) { 100 | Vector lobeCenter = vert.sg.p; 101 | vert.parallaxInfo.parallaxDir(pos, lobeCenter); 102 | result += vert.sg.evaluate(lobeCenter, d); 103 | } 104 | return result; 105 | } 106 | Spectrum evalProduct(const Intersection &its, const Vector &dir) { 107 | const auto &bsdf = its.getBSDF(); 108 | Vector wi = its.toWorld(its.wi); 109 | SG brdfLobe = SG::brdfSlice(its.shFrame.n, wi, bsdf->getRoughness(its, 0), bsdf); 110 | 111 | Spectrum result; 112 | for (const auto &sgLight : sgLights) { 113 | SG incidentLobe = sgLight.sg; 114 | sgLight.parallaxInfo.parallaxDir(its.p, incidentLobe.p); 115 | SG product = SG::product(incidentLobe, brdfLobe); 116 | result += product.evaluate(dir); 117 | } 118 | return result; 119 | } 120 | 121 | int sgNum() const { 122 | return sgLights.size(); 123 | } 124 | 125 | void merge(float mergeAngleThreshold, float mergeDistThreshold) { 126 | if (mergeAngleThreshold == 0 && mergeDistThreshold == 0) { 127 | return; 128 | } 129 | std::vector mergedSGs; 130 | while (!sgLights.empty()) { 131 | size_t idx = 1; 132 | mergedSGs.push_back(sgLights[idx]); 133 | sgLights[1] = sgLights.back(); 134 | sgLights.pop_back(); 135 | while (idx < sgLights.size()) { 136 | auto &merged = mergedSGs.back(); 137 | const auto &vert = sgLights[idx]; 138 | 139 | float dist = (merged.parallaxInfo.origin - vert.parallaxInfo.origin).length(); 140 | 141 | if (dist < mergeDistThreshold) { 142 | merged.sg.c += vert.sg.c; 143 | merged.sg.lambda = std::min(merged.sg.lambda, vert.sg.lambda); 144 | sgLights[idx] = sgLights.back(); 145 | sgLights.pop_back(); 146 | } else { 147 | idx++; 148 | } 149 | } 150 | } 151 | sgLights.swap(mergedSGs); 152 | } 153 | void normalizeForSampling() { 154 | for (size_t i = 0; i < sgLights.size(); i++) { 155 | sgLights[i].sg.computeNormalization(); 156 | } 157 | } 158 | void computeWeights() __attribute__((optimize("O3"))) { //???? 159 | if (sgLights.empty()) { 160 | return; 161 | } 162 | float sgWeightSum = 0.f; 163 | for (size_t i = 1; i < sgLights.size(); i++) { 164 | float weight = sgLights[i].sg.integral().getLuminance(); 165 | sgLights[i].weight = weight; 166 | sgWeightSum += weight; 167 | } 168 | sgLights[0].weight = (sgWeightSum == 0.f ? 1 : sgWeightSum) / sgLights.size(); 169 | sgWeightSum += sgLights[0].weight; 170 | for (size_t i = 0; i < sgLights.size(); i++) { 171 | sgLights[i].weight /= sgWeightSum; 172 | } 173 | } 174 | 175 | Vector sampleWeighted(const Point &pos, Point2 sample) const { 176 | float partition = sample.x; 177 | float sum = 0; 178 | for (size_t i = 0; i < sgLights.size(); i++) { 179 | if (sum + sgLights[i].weight > partition) { 180 | sample.x = (partition - sum) / (sgLights[i].weight); 181 | Vector lobeCenter = sgLights[i].sg.p; 182 | sgLights[i].parallaxInfo.parallaxDir(pos, lobeCenter); 183 | Vector wo = sgLights[i].sg.sample(lobeCenter, sample); 184 | return wo; 185 | } 186 | sum = sum + sgLights[i].weight; 187 | } 188 | return Vector(0.f); 189 | } 190 | float pdfWeighted(const Point &pos, const Vector &dir) const { 191 | float pdf = 0.f; 192 | for (size_t i = 0; i < sgLights.size(); i++) { 193 | Vector lobeCenter = sgLights[i].sg.p; 194 | sgLights[i].parallaxInfo.parallaxDir(pos, lobeCenter); 195 | pdf += sgLights[i].weight * sgLights[i].sg.pdf(lobeCenter, dir); 196 | } 197 | return pdf; 198 | } 199 | Vector sampleProduct(const Intersection &its, Point2 sample) const { 200 | sample.x *= sgLights.size(); 201 | int idx = int(sample.x); 202 | sample.x -= idx; 203 | const auto &sampleSGLight = sgLights[idx]; 204 | 205 | SG incidentLobe = sampleSGLight.sg; 206 | sampleSGLight.parallaxInfo.parallaxDir(its.p, incidentLobe.p); 207 | 208 | const auto &bsdf = its.getBSDF(); 209 | Vector wi = its.toWorld(its.wi); 210 | SG brdfLobe = SG::brdfSlice(its.shFrame.n, wi, bsdf->getRoughness(its, 0), bsdf); 211 | SG product = SG::product(incidentLobe, brdfLobe); 212 | product.computeNormalization(); 213 | Vector dir = product.sample(sample); 214 | return dir; 215 | } 216 | float pdfProduct(const Intersection &its, const Vector &dir) const { 217 | const auto &bsdf = its.getBSDF(); 218 | Vector wi = its.toWorld(its.wi); 219 | SG brdfLobe = SG::brdfSlice(its.shFrame.n, wi, bsdf->getRoughness(its, 0), bsdf); 220 | 221 | float pdf = 0.f; 222 | for (const auto &sgLight : sgLights) { 223 | SG incidentLobe = sgLight.sg; 224 | sgLight.parallaxInfo.parallaxDir(its.p, incidentLobe.p); 225 | SG product = SG::product(incidentLobe, brdfLobe); 226 | product.computeNormalization(); 227 | pdf += product.pdf(dir); 228 | } 229 | return pdf / sgLights.size(); 230 | } 231 | }; 232 | 233 | 234 | struct GuidedShape { 235 | std::vector sgMixtures; //GMM cached in each triangle of the shape. 236 | }; 237 | 238 | 239 | class Precomputation { 240 | private: 241 | // std::vector m_sgLights; 242 | int m_nBounce; 243 | const Scene *m_scene; 244 | omp_lock_t m_lock; 245 | 246 | float m_pathcutCount; 247 | float m_validPathcutCount; 248 | float m_sgCount; 249 | 250 | bool m_validateCamera; 251 | bool m_parallax; 252 | bool m_newton; 253 | 254 | public: 255 | Precomputation(std::vector &sgShapes, const Scene *scene, const std::vector &ptrees, const Point &camPos, const Vector &camDir, int bounce, 256 | float mergeAngleThreshold, float mergeDistThreshold, 257 | bool sds = false, bool parallax = true, bool newton = true) { 258 | m_scene = scene; 259 | m_nBounce = bounce; 260 | m_validateCamera = !sds; 261 | m_parallax = parallax; 262 | m_newton = newton; 263 | 264 | m_pathcutCount = 0; 265 | m_validPathcutCount = 0; 266 | m_sgCount = 0; 267 | // find available pathcuts, specular paths and SGs. 268 | ref timer = new Timer(); 269 | computePathcuts(sgShapes, ptrees, camPos, camDir, mergeAngleThreshold, mergeDistThreshold); 270 | std::cout << "pathcut detph: " << m_nBounce << ", done in " << timer->getSeconds() << "s\n"; 271 | std::cout << "leaf pathcut found: " << m_pathcutCount << "\n"; 272 | std::cout << "valid pathcut: " << m_validPathcutCount << "\n"; 273 | std::cout << "valid SG: " << m_sgCount << "\n"; 274 | } 275 | 276 | private: 277 | //construct root path cut for differnt types of emitters. 278 | std::vector emitterPaths(const std::vector &ptrees, const Point &camPos, const Vector &camDir) const { 279 | std::vector roots; 280 | 281 | const auto &emitters = m_scene->getEmitters(); 282 | for (const auto &emitter : emitters) { 283 | if (!emitter->getProperties().getBoolean("causticEmitter", false)) { 284 | continue; 285 | } 286 | IntervalPath rootPath; 287 | rootPath.camPos = camPos; 288 | rootPath.camDir = camDir; 289 | if (emitter->getProperties().getString("lightType") == "point") { 290 | rootPath.lightPos = emitter->getProperties().getPoint("position"); 291 | rootPath.lightIntensity = emitter->getProperties().getSpectrum("intensity"); 292 | rootPath.lightType = 0; 293 | rootPath.lightBound = Interval3D(rootPath.lightPos); 294 | rootPath.lightDirBound = Interval3D(Point(-1.f), Point(1.f)); 295 | std::cout << "construct root path cut for point light.\n"; 296 | } else if (emitter->getProperties().getString("lightType") == "area") { 297 | // expand normal bound of area light 298 | auto mesh = const_cast(emitter->getShape())->createTriMesh(); 299 | const auto &normals = mesh->getVertexNormals(); 300 | auto vertexCount = mesh->getVertexCount(); 301 | for (size_t i = 0; i < vertexCount; i++) 302 | rootPath.lightDirBound.expand(normals[i]); 303 | // expand pos bound of area light 304 | const auto aabb = emitter->getShape()->getAABB(); 305 | rootPath.lightPos = aabb.getCenter(); 306 | rootPath.lightIntensity = emitter->getProperties().getSpectrum("radiance"); 307 | rootPath.lightType = 1; 308 | rootPath.lightBound = Interval3D(aabb.min, aabb.max); 309 | std::cout << "construct root path cut for area light.\n"; 310 | } else if (emitter->getProperties().getString("lightType") == "directional") { 311 | const auto dir = emitter->getProperties().getVector("direction"); 312 | rootPath.lightIntensity = emitter->getProperties().getSpectrum("irradiance"); 313 | rootPath.lightType = 2; 314 | rootPath.lightBound = Interval3D(0.f); 315 | rootPath.lightDirBound = Interval3D(dir); 316 | std::cout << "construct root path cut for directional light.\n"; 317 | } else if (emitter->getProperties().getString("lightType") == "spot") { 318 | Transform trafo = emitter->getWorldTransform()->eval(0.f); 319 | rootPath.lightPos = trafo(Point(0.f)); 320 | rootPath.lightIntensity = emitter->getProperties().getSpectrum("intensity"); 321 | rootPath.lightType = 3; 322 | rootPath.lightBound = Interval3D(rootPath.lightPos); 323 | rootPath.lightDirBound = Interval3D(trafo(Vector(0, 0, 1))); 324 | rootPath.lightCutoff = degToRad(emitter->getProperties().getFloat("cutoffAngle", 20)); 325 | std::cout << "construct root path cut for spot light.\n"; 326 | } else { 327 | std::cout << "unsupported light type.\n"; 328 | continue; 329 | } 330 | constructRoot(rootPath, ptrees, roots, 0); 331 | } 332 | return roots; 333 | } 334 | void computePathcuts(std::vector &sgShapes, const std::vector &ptrees, const Point &camPos, const Vector &camDir, float mergeAngleThreshold, float mergeDistThreshold) { 335 | // root path 336 | auto roots = emitterPaths(ptrees, camPos, camDir); 337 | std::cout << "root pathcuts: " << roots.size() << std::endl; 338 | 339 | omp_init_lock(&m_lock); 340 | for (size_t i = 0; i < roots.size(); i++) { 341 | if (validateReflection(roots[i])) { 342 | std::deque paths; 343 | paths.push_back(roots[i]); 344 | // subdivide several times 345 | while (paths.size() < 1000 && !paths.empty()) { 346 | const IntervalPath &path = paths.front(); 347 | if (validateReflection(path)) { 348 | float thickness; 349 | int idx = findThickest(path, thickness); 350 | if (idx == -1) 351 | break; 352 | subdivide(paths, path, idx); 353 | } 354 | paths.pop_front(); 355 | } 356 | #ifndef MYDEBUG 357 | #pragma omp parallel for schedule(dynamic) 358 | #endif 359 | for (size_t idx = 0; idx < paths.size(); idx++) { 360 | findPathcuts(sgShapes, paths[idx]); 361 | } 362 | 363 | int mergedSGCount = 0; 364 | 365 | for (auto &shape : sgShapes) { 366 | #ifndef MYDEBUG 367 | #pragma omp parallel for schedule(dynamic) 368 | #endif 369 | for (size_t idx = 0; idx < shape.sgMixtures.size(); idx++) { 370 | shape.sgMixtures[idx].merge(mergeAngleThreshold, mergeDistThreshold); 371 | omp_set_lock(&m_lock); 372 | mergedSGCount += shape.sgMixtures[idx].sgLights.size(); 373 | omp_unset_lock(&m_lock); 374 | } 375 | } 376 | std::cout << "merged sg count: " << mergedSGCount << "\n"; 377 | } 378 | } 379 | omp_destroy_lock(&m_lock); 380 | } 381 | void constructRoot(IntervalPath &rootPath, const std::vector &ptrees, std::vector &roots, int bounce) const { 382 | if (bounce == m_nBounce) { 383 | roots.push_back(rootPath); 384 | return; 385 | } 386 | for (size_t i = 0; i < ptrees.size(); i++) { 387 | PNode *node = ptrees[i].shapeRoot.get(); 388 | if (node == nullptr) { 389 | continue; 390 | } 391 | if (!rootPath.bounces.empty() && rootPath.bounces.back().pnode == node) { 392 | continue; 393 | } 394 | const auto &mesh = ptrees[i].trimesh; 395 | const auto &meshProps = mesh->getProperties(); 396 | if (bounce == m_nBounce - 1) { 397 | if (!(meshProps.getBoolean("reflector", false) || meshProps.getBoolean("refractor", false)) && meshProps.getBoolean("guided", false)) { 398 | rootPath.bounces.push_back({node, mesh, 0, true}); 399 | roots.push_back(rootPath); 400 | rootPath.bounces.pop_back(); 401 | } 402 | } else { 403 | if (meshProps.getBoolean("reflector", false)) { 404 | rootPath.bounces.push_back({node, mesh, 1, meshProps.getBoolean("guided", false)}); 405 | constructRoot(rootPath, ptrees, roots, bounce + 1); 406 | rootPath.bounces.pop_back(); 407 | } 408 | if (meshProps.getBoolean("refractor", false)) { 409 | rootPath.bounces.push_back({node, mesh, -1, meshProps.getBoolean("guided", false)}); 410 | constructRoot(rootPath, ptrees, roots, bounce + 1); 411 | rootPath.bounces.pop_back(); 412 | } 413 | } 414 | } 415 | } 416 | void findPathcuts(std::vector &sgShapes, const IntervalPath &root) { 417 | std::stack pathStack; 418 | pathStack.push(root); 419 | // subdivide several times 420 | while (!pathStack.empty()) { 421 | IntervalPath path = pathStack.top(); 422 | pathStack.pop(); 423 | 424 | if (validateReflection(path)) { 425 | float thickness; 426 | auto shouldSplitIdx = findThickest(path, thickness); 427 | // auto shouldSplitIdx = findShouldSplit(path, 0.5, posExtent, vecExtent); 428 | if (shouldSplitIdx == -1) { 429 | std::vector points; 430 | std::vector normals; 431 | omp_set_lock(&m_lock); 432 | m_pathcutCount++; 433 | omp_unset_lock(&m_lock); 434 | if (solvePathcut(path, points, normals)) { 435 | omp_set_lock(&m_lock); 436 | m_validPathcutCount++; 437 | omp_unset_lock(&m_lock); 438 | computeSGs(sgShapes, path, points, normals); 439 | } 440 | } else { 441 | subdivide(pathStack, path, shouldSplitIdx); 442 | } 443 | } 444 | } 445 | } 446 | bool validateRefraction(const IntervalPath &path) const { 447 | return false; 448 | } 449 | bool validateReflection(const IntervalPath &path) const { 450 | for (size_t i = 0; i < path.bounces.size() - 1; i++) { 451 | const auto &curr = path.bounces[i].pnode; 452 | if (curr->tri && curr == path.bounces[i + 1].pnode) 453 | return false; 454 | } 455 | 456 | Interval3D curr2pre; 457 | const auto &firstNode = path.bounces[0].pnode; 458 | // emitter to first 459 | curr2pre = path.lightBound - firstNode->posBox; 460 | if (path.lightType == 1) { // area 461 | if (path.lightDirBound.dot(-curr2pre).vMax < 0.f) { 462 | return false; 463 | } 464 | } else if (path.lightType == 2) { // directional 465 | curr2pre = -path.lightDirBound; 466 | } else if (path.lightType == 3) { // spot 467 | if (path.lightDirBound.dot(-curr2pre.normalized()).vMax < cos(path.lightCutoff)) { 468 | return false; 469 | } 470 | } 471 | // validate camera 472 | Interval3D cam2last; 473 | if (m_validateCamera) { 474 | const auto &lastNode = *path.bounces.back().pnode; 475 | cam2last = lastNode.posBox - path.camPos; 476 | if (cam2last.dot(path.camDir).vMax < 0.f) { 477 | return false; 478 | } 479 | } 480 | Interval3D ibox = curr2pre.normalized(); 481 | for (size_t i = 0; i < path.bounces.size(); i++) { 482 | const auto &node = *path.bounces[i].pnode; 483 | const auto &currBox = node.posBox; 484 | 485 | Interval3D curr2next; 486 | if (i == path.bounces.size() - 1) { 487 | if (m_validateCamera) { 488 | curr2next = -cam2last; 489 | } else { 490 | // not validating camera 491 | curr2next = Interval3D(Point(-1, -1, -1), Point(1, 1, 1)); 492 | } 493 | } else { 494 | const auto &nextNode = *path.bounces[i + 1].pnode; 495 | curr2next = nextNode.posBox - currBox; 496 | } 497 | Interval3D obox = curr2next.normalized(); 498 | 499 | Interval1D dotIN = node.norbox.dot(ibox); 500 | Interval1D dotON = node.norbox.dot(curr2next); 501 | 502 | float etaIn, etaOut; 503 | if (path.bounces[i].mesh->getBSDF()->isConductor()) // conductor reflection: always above the normal 504 | { 505 | if (dotIN.vMax <= 0.f || dotON.vMax <= 0.f) 506 | return false; 507 | etaIn = etaOut = 1.f; 508 | } else // dielectric reflection: on the same side. For TRT 509 | { 510 | if ((dotIN.vMax <= 0.0 && dotON.vMin >= 0.0) || (dotIN.vMin >= 0.0 && dotON.vMax <= 0.0)) 511 | return false; 512 | etaIn = etaOut = 1.f; 513 | } 514 | 515 | Interval3D hbox = (ibox + obox).normalized(); 516 | // if (!(hbox - node.norboxRough).coverZero()) { 517 | // return false; 518 | // } 519 | if (hbox.dot(node.norbox).vMax < node.cosThreshold) { 520 | return false; 521 | } 522 | Interval3D reflectBox = reflect(ibox, node.norboxRough); 523 | obox = obox.intersect(reflectBox); 524 | if (obox.isEmpty()) 525 | return false; 526 | curr2pre = -curr2next; 527 | ibox = -obox; 528 | } 529 | return true; 530 | } 531 | 532 | int findThickest(const IntervalPath &path, float &thickness) const { 533 | // find thickest node 534 | int shouldSplitIdx = -1; 535 | thickness = 0.f; 536 | for (size_t i = 0; i < path.bounces.size(); i++) { 537 | const auto &node = *path.bounces[i].pnode; 538 | if (!node.tri && node.extent >= thickness) { 539 | shouldSplitIdx = i; 540 | thickness = node.extent; 541 | } 542 | } 543 | return shouldSplitIdx; 544 | } 545 | void subdivide(std::deque &deq, const IntervalPath &path, const int &bouncIdx) const { 546 | const auto &subNodes = path.bounces[bouncIdx].pnode->children; 547 | for (int i = 0; i < 4; i++) { 548 | if (subNodes[i] != nullptr) { 549 | IntervalPath subPath(path); 550 | subPath.bounces[bouncIdx].pnode = subNodes[i].get(); 551 | deq.push_back(subPath); 552 | } 553 | } 554 | } 555 | void subdivide(std::stack &stack, const IntervalPath &path, const int &bouncIdx) const { 556 | const auto &subNodes = path.bounces[bouncIdx].pnode->children; 557 | for (int i = 0; i < 4; i++) { 558 | if (subNodes[i] != nullptr) { 559 | IntervalPath subPath(path); 560 | subPath.bounces[bouncIdx].pnode = subNodes[i].get(); 561 | stack.push(subPath); 562 | } 563 | } 564 | } 565 | 566 | bool solvePathcut(IntervalPath path, std::vector &points, std::vector &normals) { 567 | std::vector> reflectorTris; 568 | for (size_t i = 0; i < path.bounces.size() - 1; i++) { 569 | reflectorTris.emplace_back(); 570 | for (int triVertIdx = 0; triVertIdx < 3; triVertIdx++) { 571 | const auto &reflTri = path.bounces[i].mesh->getTriangles()[path.bounces[i].pnode->tri->triIdx]; 572 | auto pos = path.bounces[i].mesh->getVertexPositions()[reflTri.idx[triVertIdx]]; 573 | auto nor = path.bounces[i].mesh->getVertexNormals()[reflTri.idx[triVertIdx]]; 574 | 575 | reflectorTris.back()[triVertIdx].pos = {pos.x, pos.y, pos.z}; 576 | reflectorTris.back()[triVertIdx].normal = {nor.x, nor.y, nor.z}; 577 | } 578 | } 579 | Point recvCenter(0.f); 580 | for (int triVertIdx = 0; triVertIdx < 3; triVertIdx++) { 581 | const auto &tri = path.bounces.back().mesh->getTriangles()[path.bounces.back().pnode->tri->triIdx]; 582 | auto pos = path.bounces.back().mesh->getVertexPositions()[tri.idx[triVertIdx]]; 583 | recvCenter += pos; 584 | } 585 | recvCenter /= 3; 586 | const auto &recvNode = path.bounces.back().pnode; 587 | const auto &lastReflNode = path.bounces[path.bounces.size() - 2].pnode; 588 | Vector refl2recv = recvCenter - lastReflNode->center; 589 | float dot1 = dot(refl2recv, lastReflNode->tri->n); 590 | // offset the receiver point so that it is on the front side of the reflector 591 | if (dot1 < 0) { 592 | Vector t = cross(recvNode->tri->n, lastReflNode->tri->n); 593 | if (t.length() != 0) { 594 | Vector d = cross(normalize(t), recvNode->tri->n); 595 | float dot2 = dot(lastReflNode->tri->n, d); 596 | recvCenter = recvCenter + d * (-std::min(0.f, dot1) / dot2 + recvNode->extent / 10.f); 597 | } 598 | } 599 | 600 | // newton solver 601 | std::vector reflVerts; 602 | std::array recvCenterArray = {recvCenter.x, recvCenter.y, recvCenter.z}; 603 | std::array lightPosArray = {path.lightPos.x, path.lightPos.y, path.lightPos.z}; 604 | switch (path.bounces.size()) { 605 | case 2: 606 | VertInfo reflVert; 607 | solveOneBounce(reflVert, reflectorTris[0], lightPosArray, recvCenterArray, m_newton ? 0.001 : 1000000); 608 | reflVerts.push_back(reflVert); 609 | break; 610 | case 3: 611 | VertInfo reflVert1, reflVert2; 612 | solveTwoBounce(reflVert1, reflVert2, reflectorTris[0], reflectorTris[1], lightPosArray, recvCenterArray, m_newton ? 0.001 : 100000); 613 | reflVerts.push_back(reflVert1); 614 | reflVerts.push_back(reflVert2); 615 | break; 616 | default: 617 | return false; 618 | break; 619 | } 620 | for (size_t i = 0; i < reflVerts.size(); i++) { 621 | points.emplace_back(reflVerts[i].pos[0], reflVerts[i].pos[1], reflVerts[i].pos[2]); 622 | normals.emplace_back(reflVerts[i].normal[0], reflVerts[i].normal[1], reflVerts[i].normal[2]); 623 | } 624 | points.emplace_back(recvCenter); 625 | normals.emplace_back(recvNode->tri->n); 626 | if (m_newton) { 627 | Vector wi = normalize(path.lightPos - points.front()); 628 | for (size_t i = 0; i < points.size() - 1; i++) { 629 | Vector wo = normalize(points[i + 1] - points[i]); 630 | Vector h = normalize(wi + wo); 631 | float cosine = dot(h, normals[i]); 632 | if (cosine < path.bounces[i].pnode->cosThreshold) { 633 | return false; 634 | } 635 | wi = -wo; 636 | } 637 | } 638 | return true; 639 | } 640 | 641 | void computeSGs(std::vector &sgShapes, const IntervalPath &intervalPath, const std::vector &points, const std::vector &normals) { 642 | // approximate light transport with SG 643 | SG gli; 644 | if (intervalPath.lightType == 0) { 645 | // point light 646 | gli = SG::sgLight(0.01f, intervalPath.lightIntensity, intervalPath.lightPos - points[0]); 647 | } else if (intervalPath.lightType == 1) { 648 | // area light 649 | float radius = intervalPath.lightBound.extents().length() / 2; 650 | gli = SG::sgLight(radius, intervalPath.lightIntensity * (radius * radius), intervalPath.lightPos - points[0]); 651 | } else if (intervalPath.lightType == 3) { 652 | // spot 653 | Vector spotDir = normalize(intervalPath.lightDirBound.centerVector()); 654 | Vector first2light = intervalPath.lightPos - points[0]; 655 | if (dot(spotDir, normalize(-first2light)) < cos(intervalPath.lightCutoff)) { 656 | return; 657 | } 658 | gli = SG::sgLight(0.01f, intervalPath.lightIntensity, first2light); 659 | } 660 | 661 | Point lightPos = intervalPath.lightPos; 662 | for (size_t i = 0; i < intervalPath.bounces.size() - 1; i++) { 663 | const auto &reflPoint = points[i]; 664 | const auto &recvPoint = points[i + 1]; 665 | const auto &reflN = normals[i]; 666 | const auto &reflBounce = intervalPath.bounces[i]; 667 | const auto &recvBounce = intervalPath.bounces[i + 1]; 668 | const auto &reflMesh = reflBounce.mesh; 669 | const auto &recvMesh = recvBounce.mesh; 670 | const auto &reflPNode = reflBounce.pnode; 671 | const auto &recvPNode = recvBounce.pnode; 672 | int reflTriIdx = reflBounce.pnode->tri->triIdx; 673 | int recvTriIdx = recvBounce.pnode->tri->triIdx; 674 | float reflFocalU = reflBounce.pnode->tri->focalLengthU; 675 | float reflFocalV = reflBounce.pnode->tri->focalLengthV; 676 | 677 | Vector wi = lightPos - reflPoint; 678 | float lightDist = wi.length(); 679 | wi /= lightDist; 680 | Vector wo = normalize(recvPoint - reflPoint); 681 | Vector h = normalize(wi + wo); 682 | // curved mirror approximation 683 | const auto &tangent = reflMesh->getUVTangents()[reflTriIdx]; 684 | Vector reflS = normalize(cross(normalize(tangent.dpdv), h)); 685 | Vector reflT = normalize(cross(h, reflS)); 686 | Frame tangentFrame(reflS, reflT, h); 687 | float d_o, hoU, hoV; 688 | ParallaxStruct parallaxInfo = m_parallax ? ParallaxStruct(reflPoint, lightPos, tangentFrame, reflFocalU, reflFocalV, d_o, hoU, hoV) : ParallaxStruct(); 689 | 690 | // shading point too close to the image, consider reflector as plane to avoid image flickering. 691 | float imageDistU = INFINITY, imageDistV = INFINITY; 692 | parallaxInfo.imageDist(recvPoint, imageDistU, imageDistV); 693 | if (imageDistU < 0.5f) { 694 | parallaxInfo.diU *= 500; 695 | parallaxInfo.hiU *= 500; 696 | } 697 | if (imageDistV < 0.5f) { 698 | parallaxInfo.diV *= 500; 699 | parallaxInfo.hiV *= 500; 700 | } 701 | 702 | SG reflBrdfSG = SG::brdfSlice(reflN, wo, reflPNode->maxRoughness, reflMesh->getBSDF()); 703 | gli = SG::convolveApproximation(gli, reflBrdfSG, reflN); 704 | gli.p = -wo; 705 | 706 | if (!(gli.c.isValid() && std::isfinite(gli.lambda) && gli.lambda > 0) || 707 | gli.c.isZero()) { 708 | return; 709 | } 710 | if (recvBounce.guided) { 711 | auto &sgMixtures = sgShapes[recvMesh->getID()[0]].sgMixtures; 712 | const auto &parent = recvPNode->parent; 713 | if (parent) { 714 | omp_set_lock(&m_lock); 715 | // SG "filtering": put SGs into neighboring triangles. 716 | putSG(sgMixtures, parent, {gli, parallaxInfo}); 717 | omp_unset_lock(&m_lock); 718 | } else { 719 | omp_set_lock(&m_lock); 720 | // add a smooth lobe into the GMM 721 | if (sgMixtures[recvTriIdx].sgLights.empty()) { 722 | sgMixtures[recvTriIdx].sgLights.push_back({SG(recvPNode->norbox.centerVector(), 2, Spectrum(0.f)), ParallaxStruct()}); 723 | m_sgCount++; 724 | } 725 | m_sgCount++; 726 | sgMixtures[recvTriIdx].sgLights.push_back( 727 | {gli, parallaxInfo}); 728 | omp_unset_lock(&m_lock); 729 | } 730 | } 731 | if (i < intervalPath.bounces.size() - 2) { 732 | // using isotropic curved mirror approximation until the last bounce. 733 | float focalAvg = (std::isinf(reflFocalU) || std::isinf(reflFocalV)) ? INFINITY : (reflFocalU + reflFocalV) / 2.f; 734 | float nDotL = dot(reflN, wi); 735 | float d_o1 = nDotL * lightDist, 736 | d_i1 = 1 / (1 / focalAvg - 1 / d_o1); 737 | lightDist = d_i1 / nDotL; 738 | lightPos = reflPoint + lightDist * wo; 739 | } 740 | } 741 | } 742 | void putSG(std::vector &sgMixtures, PNode *node, const SGLight &sg) { 743 | if (node->tri) { 744 | // add a smooth lobe into the GMM 745 | if (sgMixtures[node->tri->triIdx].sgLights.empty()) { 746 | sgMixtures[node->tri->triIdx].sgLights.push_back({SG(node->norbox.centerVector(), 2, Spectrum(0.f)), ParallaxStruct()}); 747 | m_sgCount++; 748 | } 749 | m_sgCount++; 750 | sgMixtures[node->tri->triIdx].sgLights.push_back(sg); 751 | return; 752 | } 753 | for (const auto &child : node->children) { 754 | if (child) { 755 | putSG(sgMixtures, child.get(), sg); 756 | } 757 | } 758 | } 759 | }; -------------------------------------------------------------------------------- /pathcut/pathcut_wrapper.h: -------------------------------------------------------------------------------- 1 | #include "pathcut.h" 2 | #ifdef MYDEBUG 3 | #pragma GCC optimize("O0") 4 | #endif 5 | 6 | /* Wrapper class of the precomputation functions */ 7 | class PathcutWrapper { 8 | std::vector m_sgShapes; 9 | float m_mergeAngleThreshold; 10 | float m_mergeDistThreshold; 11 | 12 | float m_bsdfSamplingFraction; 13 | float m_sgCutoff; 14 | 15 | bool m_parallax; 16 | bool m_sds; 17 | bool m_newton; 18 | 19 | int m_maxDepth; 20 | 21 | public: 22 | bool guideSDS() const { 23 | return m_sds; 24 | } 25 | void initGuidingParams(const Properties &props) { 26 | m_mergeAngleThreshold = props.getFloat("mergeAngleThreshold", 0.f); 27 | m_mergeDistThreshold = props.getFloat("mergeDistThreshold", 0.f); 28 | m_bsdfSamplingFraction = props.getFloat("samplingFraction", 0.5f); 29 | m_sgCutoff = props.getFloat("sgCutoff", 0.01f); 30 | m_parallax = props.getBoolean("parallax", true); 31 | m_sds = props.getBoolean("SDS", false); 32 | m_newton = props.getBoolean("newton", true); 33 | m_maxDepth = props.getInteger("causticBounce", 3); 34 | } 35 | void precomputePathCuts(const Scene *scene, const Point &camPos, const Vector &camDir) { 36 | // Build scene hierarchy for path cut pruning. 37 | std::vector ptrees; 38 | const auto &meshes = scene->getMeshes(); 39 | int receiverID = 0; 40 | for (const auto &mesh : meshes) { 41 | if (mesh->getProperties().getBoolean("guided", false) || mesh->getProperties().getBoolean("receiver", false)) { 42 | ptrees.emplace_back(mesh, m_sgCutoff); 43 | m_sgShapes.emplace_back(); 44 | m_sgShapes.back().sgMixtures.resize(mesh->getPrimitiveCount()); 45 | mesh->setID(std::string(1, char(receiverID))); 46 | receiverID++; 47 | } else if (mesh->getProperties().getBoolean("reflector", false) || mesh->getProperties().getBoolean("refractor", false)) { 48 | ptrees.emplace_back(mesh, m_sgCutoff); 49 | mesh->setID(std::string(1, char(-1))); 50 | } else { 51 | mesh->setID(std::string(1, char(-1))); 52 | } 53 | } 54 | std::cout << "guided shape num: " << receiverID << "\n"; 55 | 56 | //Precomputation: 57 | // Finding leaf path cuts. 58 | // Folving for representative paths. 59 | // Approximating incident radiance distribution with Spherical Gaussians. 60 | Precomputation(m_sgShapes, scene, ptrees, camPos, camDir, 2, m_mergeAngleThreshold, m_mergeDistThreshold, m_sds, m_parallax, m_newton); 61 | for (int i = 3; i < m_maxDepth; i++) { 62 | //for multiple bounces 63 | Precomputation(m_sgShapes, scene, ptrees, camPos, camDir, i, m_mergeAngleThreshold, m_mergeDistThreshold, false, m_parallax, m_newton); 64 | } 65 | for (size_t i = 0; i < m_sgShapes.size(); i++) { 66 | 67 | #ifndef MYDEBUG 68 | #pragma omp parallel for schedule(dynamic) 69 | #endif 70 | //process the GMMs for sampling. 71 | for (size_t j = 0; j < m_sgShapes[i].sgMixtures.size(); j++) { 72 | m_sgShapes[i].sgMixtures[j].normalizeForSampling(); 73 | m_sgShapes[i].sgMixtures[j].computeWeights(); 74 | } 75 | } 76 | int mergedSGCount = 0; 77 | for (size_t i = 0; i < m_sgShapes.size(); i++) { 78 | for (size_t j = 0; j < m_sgShapes[i].sgMixtures.size(); j++) { 79 | mergedSGCount += m_sgShapes[i].sgMixtures[j].sgLights.size(); 80 | } 81 | } 82 | size_t memory = sizeof(SGLight) * mergedSGCount; 83 | std::cout << "total SG count for rendering: " << mergedSGCount << "\n"; 84 | std::cout << "memory: " << memory / 1024 << " KB.\n"; 85 | } 86 | Spectrum sampleMat(Float &woPdf, const BSDF *bsdf, BSDFSamplingRecord &bRec, mitsuba::ETransportMode mode, Point2 sample) const { 87 | Spectrum result; 88 | Float bsdfPdf = 0, sTreePdf = 0; 89 | 90 | auto shapeIdx = bRec.its.shape->getID()[0]; 91 | auto triIdx = bRec.its.primIndex; 92 | if (m_bsdfSamplingFraction == 1.f || 93 | shapeIdx == char(-1) || 94 | bRec.its.shape->isEmitter() || 95 | mode != ERadiance || 96 | m_sgShapes[shapeIdx].sgMixtures[triIdx].sgLights.size() == 0) { 97 | result = bsdf->sample(bRec, woPdf, sample); 98 | return result; 99 | } else { 100 | const auto &sgMixture = m_sgShapes[shapeIdx].sgMixtures[triIdx]; 101 | if (sample.x < m_bsdfSamplingFraction) { 102 | sample.x /= m_bsdfSamplingFraction; 103 | result = bsdf->sample(bRec, bsdfPdf, sample); 104 | if (result.isZero()) { 105 | woPdf = 0; 106 | return Spectrum{0.0f}; 107 | } 108 | result *= bsdfPdf; 109 | Vector wo = bRec.its.toWorld(bRec.wo); 110 | sTreePdf = sgMixture.pdfWeighted(bRec.its.p, wo); 111 | } else { 112 | sample.x = (sample.x - m_bsdfSamplingFraction) / (1 - m_bsdfSamplingFraction); 113 | Vector wo = sgMixture.sampleWeighted(bRec.its.p, sample); 114 | bRec.eta = 1.0f; 115 | bRec.sampledComponent = 0; 116 | bRec.sampledType = BSDF::EGlossyReflection; 117 | 118 | sTreePdf = sgMixture.pdfWeighted(bRec.its.p, wo); 119 | bRec.wo = bRec.its.toLocal(wo); 120 | result = bsdf->eval(bRec); 121 | bsdfPdf = bsdf->pdf(bRec); 122 | } 123 | } 124 | woPdf = m_bsdfSamplingFraction * bsdfPdf + (1 - m_bsdfSamplingFraction) * sTreePdf; 125 | if (result.isZero() || woPdf == 0) { 126 | return Spectrum{0.0f}; 127 | } 128 | 129 | return result / woPdf; 130 | } 131 | 132 | Float pdfMat(const BSDF *bsdf, BSDFSamplingRecord &bRec, mitsuba::ETransportMode mode, EMeasure measure = ESolidAngle) const { 133 | auto shapeIdx = bRec.its.shape->getID()[0]; 134 | auto triIdx = bRec.its.primIndex; 135 | if (m_bsdfSamplingFraction == 1.f || 136 | shapeIdx == char(-1) || 137 | bRec.its.shape->isEmitter() || 138 | mode != ERadiance || 139 | m_sgShapes[shapeIdx].sgMixtures[triIdx].sgLights.size() == 0) { 140 | return bsdf->pdf(bRec, measure); 141 | } else { 142 | const auto &sgMixture = m_sgShapes[shapeIdx].sgMixtures[triIdx]; 143 | float sTreePdf = sgMixture.pdfWeighted(bRec.its.p, bRec.its.toWorld(bRec.wo)); 144 | Float bsdfPdf = bsdf->pdf(bRec); 145 | Float woPdf = m_bsdfSamplingFraction * bsdfPdf + (1 - m_bsdfSamplingFraction) * sTreePdf; 146 | return woPdf; 147 | } 148 | } 149 | 150 | Spectrum evalSGs(const Point &pos, const Intersection &its, const Vector &dir) const { 151 | const auto &sgMixtures = m_sgShapes[its.shape->getID()[0]].sgMixtures[its.primIndex]; 152 | return sgMixtures.evalParallax(pos, dir); 153 | } 154 | }; -------------------------------------------------------------------------------- /pathcut/ptree.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "interval.h" 9 | 10 | using namespace mitsuba; 11 | 12 | void Barycentric(Point p, Point a, Point b, Point c, float &u, float &v, float &w) { 13 | Vector v0 = b - a, v1 = c - a, v2 = p - a; 14 | float d00 = dot(v0, v0); 15 | float d01 = dot(v0, v1); 16 | float d11 = dot(v1, v1); 17 | float d20 = dot(v2, v0); 18 | float d21 = dot(v2, v1); 19 | float denom = d00 * d11 - d01 * d01; 20 | v = (d11 * d20 - d01 * d21) / denom; 21 | w = (d00 * d21 - d01 * d20) / denom; 22 | u = 1.0f - v - w; 23 | } 24 | struct PTriNode { 25 | Vector n; 26 | float focalLengthU, focalLengthV; 27 | int triIdx; 28 | }; 29 | struct PNode { 30 | Interval3D posBox; 31 | Interval3D norbox; 32 | Interval3D norboxRough; 33 | float cosThreshold; 34 | Point center; 35 | float extent; 36 | float minRoughness; 37 | float maxRoughness; 38 | std::unique_ptr tri; 39 | PNode *parent; 40 | 41 | std::array, 4> children; 42 | }; 43 | 44 | class PTree { 45 | public: 46 | std::unique_ptr shapeRoot; 47 | std::vector m_leafNodes; 48 | const TriMesh *trimesh; 49 | 50 | public: 51 | PTree() {} 52 | PTree(const TriMesh *mesh, float sgCutOff) { 53 | ref timer = new Timer(); 54 | 55 | trimesh = mesh; 56 | std::vector triNodeIndices; 57 | AABB shapeAABB; 58 | shapeAABB.expandBy(mesh->getAABB()); 59 | for (size_t triIdx = 0; triIdx < mesh->getTriangleCount(); triIdx++) { 60 | m_leafNodes.push_back(buildTriangleNode(mesh, triIdx, sgCutOff)); 61 | triNodeIndices.push_back(m_leafNodes.size() - 1); 62 | } 63 | shapeRoot = std::unique_ptr(buildTreeForMeshMedian(shapeAABB.getExtents()[shapeAABB.getLargestAxis()], 64 | shapeAABB, 65 | triNodeIndices, 0, triNodeIndices.size(), nullptr)); 66 | 67 | std::cout << "\nptree done in " << timer->getSeconds() << "s\n"; 68 | } 69 | PTree(const TriMesh *mesh, float sgCutOff, int triIdx) { 70 | ref timer = new Timer(); 71 | 72 | std::vector triNodeIndices; 73 | AABB shapeAABB; 74 | m_leafNodes.push_back(buildTriangleNode(mesh, triIdx, sgCutOff)); 75 | shapeAABB = mesh->getTriangles()[triIdx].getAABB(mesh->getVertexPositions()); 76 | triNodeIndices.push_back(m_leafNodes.size() - 1); 77 | 78 | shapeRoot = std::unique_ptr(buildTreeForMeshMedian(shapeAABB.getExtents()[shapeAABB.getLargestAxis()], 79 | shapeAABB, 80 | triNodeIndices, 0, triNodeIndices.size(), nullptr)); 81 | 82 | std::cout << "\nptree done in " << timer->getSeconds() << "s\n"; 83 | } 84 | 85 | private: 86 | void focalUV(const Frame &tangentFrame, float posExtentU, float posExtentV, 87 | const Point &a, const Point &b, const Point &c, 88 | const Vector &na, const Vector &nb, const Vector &nc, 89 | float &focalU, float &focalV) { 90 | Point center = (a + b + c) / 3.f; 91 | Point positiveP = center + posExtentU / 2 * tangentFrame.s; 92 | Point negativeP = center - posExtentU / 2 * tangentFrame.s; 93 | float u, v, w; 94 | Barycentric(positiveP, a, b, c, u, v, w); 95 | Vector np = normalize(u * na + v * nb + w * nc); 96 | Barycentric(negativeP, a, b, c, u, v, w); 97 | Vector nn = normalize(u * na + v * nb + w * nc); 98 | float nExtentU = tangentFrame.toLocal(np - nn).x; 99 | focalU = -posExtentU * (1.f / (2.f * nExtentU) - nExtentU / 8.f); 100 | positiveP = center + posExtentV / 2 * tangentFrame.t; 101 | negativeP = center - posExtentV / 2 * tangentFrame.t; 102 | Barycentric(positiveP, a, b, c, u, v, w); 103 | np = normalize(u * na + v * nb + w * nc); 104 | Barycentric(negativeP, a, b, c, u, v, w); 105 | nn = normalize(u * na + v * nb + w * nc); 106 | float nExtentV = tangentFrame.toLocal(np - nn).y; 107 | focalV = -posExtentV * (1.f / (2.f * nExtentV) - nExtentV / 8.f); 108 | } 109 | PNode *buildTriangleNode(const TriMesh *mesh, int triIdx, float sgCutOff) { 110 | const auto &bsdf = mesh->getBSDF(); 111 | if (!bsdf->hasComponent(BSDF::ESmooth) || bsdf->hasComponent(BSDF::ETransmission)) { 112 | std::cout << "bsdf not supported!\n"; 113 | return nullptr; 114 | } 115 | const Point *vertexPositions = mesh->getVertexPositions(); 116 | const Normal *vertexNormals = mesh->getVertexNormals(); 117 | const TangentSpace *tangents = mesh->getUVTangents(); 118 | const Triangle *triangles = mesh->getTriangles(); 119 | const auto &triangle = triangles[triIdx]; 120 | 121 | float roughness = bsdf->hasComponent(BSDF::EGlossy) ? bsdf->getProperties().getFloat("alpha") : 1; 122 | const float m2 = roughness * roughness; 123 | const float logPiM2 = std::log(sgCutOff); 124 | float cosThres = std::max(1e-10f, logPiM2 * m2 / 2 + 1); 125 | float angle = radToDeg(acos(cosThres)); 126 | 127 | Point center = (vertexPositions[triangle.idx[0]] + vertexPositions[triangle.idx[1]] + vertexPositions[triangle.idx[2]]) / 3; 128 | Vector side1 = vertexPositions[triangle.idx[0]] - vertexPositions[triangle.idx[1]]; 129 | Vector side2 = vertexPositions[triangle.idx[0]] - vertexPositions[triangle.idx[2]]; 130 | Vector triN = normalize(cross(side1, side2)); 131 | const auto &tan = tangents[triIdx]; 132 | Vector s, t; 133 | s = normalize(tan.dpdu); 134 | t = normalize(cross(triN, s)); 135 | Frame tangentFrame(s, t, triN); 136 | 137 | Interval1D posBoundU, posBoundV; 138 | 139 | Interval3D norbox; 140 | Interval3D norboxRough; 141 | auto posaabb = triangle.getAABB(vertexPositions); 142 | Interval3D posbox(posaabb.min, posaabb.max); 143 | 144 | for (int i = 0; i < 3; i++) { 145 | const Vector &n = vertexNormals[triangle.idx[i]]; 146 | Vector pLocal = tangentFrame.toLocal(vertexPositions[triangle.idx[i]] - center); 147 | // Vector nLocal = tangentFrame.toLocal(n); 148 | posBoundU.expand(pLocal.x); 149 | posBoundV.expand(pLocal.y); 150 | 151 | norbox.expand(n); 152 | norboxRough.expand(n); 153 | if (mesh->hasUVTangents()) { 154 | norboxRough.expand(Transform::rotate(s, angle)(n)); 155 | norboxRough.expand(Transform::rotate(s, -angle)(n)); 156 | norboxRough.expand(Transform::rotate(t, angle)(n)); 157 | norboxRough.expand(Transform::rotate(t, -angle)(n)); 158 | } else { 159 | norboxRough.expand(Transform::rotate(Vector(1, 0, 0), angle)(n)); 160 | norboxRough.expand(Transform::rotate(Vector(1, 0, 0), -angle)(n)); 161 | norboxRough.expand(Transform::rotate(Vector(0, 1, 0), angle)(n)); 162 | norboxRough.expand(Transform::rotate(Vector(0, 1, 0), -angle)(n)); 163 | norboxRough.expand(Transform::rotate(Vector(0, 0, 1), angle)(n)); 164 | norboxRough.expand(Transform::rotate(Vector(0, 0, 1), -angle)(n)); 165 | } 166 | } 167 | float posExtentU = posBoundU.length(), 168 | posExtentV = posBoundV.length(); 169 | // nExtentU = nBoundU.length(), 170 | // nExtentV = nBoundV.length(); 171 | float focalLengthU, focalLengthV; 172 | focalUV(tangentFrame, posExtentU, posExtentV, 173 | vertexPositions[triangle.idx[0]], vertexPositions[triangle.idx[1]], vertexPositions[triangle.idx[2]], 174 | vertexNormals[triangle.idx[0]], vertexNormals[triangle.idx[1]], vertexNormals[triangle.idx[2]], 175 | focalLengthU, focalLengthV); 176 | PTriNode *tri = new PTriNode{ 177 | triN, 178 | focalLengthU, 179 | focalLengthV, 180 | triIdx, 181 | }; 182 | return new PNode{ 183 | posbox, 184 | norbox.normalized(), 185 | norboxRough, 186 | cosThres, 187 | center, 188 | posaabb.getExtents().length(), 189 | roughness, 190 | roughness, 191 | std::unique_ptr(tri), 192 | nullptr, 193 | {nullptr}, 194 | }; 195 | } 196 | 197 | PNode *buildTreeForMeshMedian(const float &largestExtent, const AABB &aabb, std::vector &triNodeIndices, size_t beginIdx, size_t endIdx, PNode *parent) { 198 | if (endIdx - beginIdx == 1) { 199 | m_leafNodes[triNodeIndices[beginIdx]]->parent = parent; 200 | return m_leafNodes[triNodeIndices[beginIdx]]; 201 | } 202 | 203 | PNode *node = new PNode(); 204 | if (endIdx - beginIdx <= 4) { 205 | for (size_t i = 0; i < endIdx - beginIdx; i++) { 206 | // construct a node for a triangle 207 | node->children[i] = std::unique_ptr(m_leafNodes[triNodeIndices[i + beginIdx]]); 208 | node->children[i]->parent = node; 209 | } 210 | } else { 211 | std::array temp = {}; 212 | size_t median = splitMidanPos(aabb, temp[0], temp[1], triNodeIndices, beginIdx, endIdx); 213 | 214 | std::array childAABB = {}; 215 | size_t leftMedian = splitMidanPos(temp[0], childAABB[0], childAABB[1], triNodeIndices, beginIdx, median); 216 | size_t rightMedian = splitMidanPos(temp[1], childAABB[2], childAABB[3], triNodeIndices, median, endIdx); 217 | 218 | node->children[0] = std::unique_ptr(buildTreeForMeshMedian(largestExtent, childAABB[0], triNodeIndices, beginIdx, leftMedian, node)); 219 | node->children[1] = std::unique_ptr(buildTreeForMeshMedian(largestExtent, childAABB[1], triNodeIndices, leftMedian, median, node)); 220 | node->children[2] = std::unique_ptr(buildTreeForMeshMedian(largestExtent, childAABB[2], triNodeIndices, median, rightMedian, node)); 221 | node->children[3] = std::unique_ptr(buildTreeForMeshMedian(largestExtent, childAABB[3], triNodeIndices, rightMedian, endIdx, node)); 222 | } 223 | node->minRoughness = 1.f; 224 | node->maxRoughness = 0.f; 225 | node->cosThreshold = 1.f; 226 | int nChild = 0; 227 | for (size_t i = 0; i < node->children.size(); i++) { 228 | const auto &child = node->children[i]; 229 | if (child != nullptr) { 230 | nChild++; 231 | node->posBox = minmax(node->posBox, child->posBox); 232 | node->norbox = minmax(node->norbox, child->norbox); 233 | node->norboxRough = minmax(node->norboxRough, child->norboxRough); 234 | node->cosThreshold = std::min(node->cosThreshold, child->cosThreshold); 235 | node->minRoughness = std::min(child->minRoughness, node->minRoughness); 236 | node->maxRoughness = std::max(child->maxRoughness, node->maxRoughness); 237 | } 238 | } 239 | node->center = node->posBox.center(); 240 | node->extent = node->posBox.extents().length(); 241 | node->parent = parent; 242 | node->tri = nullptr; 243 | return node; 244 | } 245 | 246 | size_t splitMidanPos(const AABB &aabb, 247 | AABB &leftAABB, AABB &rightAABB, 248 | std::vector &triNodeIndices, size_t beginIdx, size_t endIdx) { 249 | int axis = aabb.getLargestAxis(); 250 | 251 | auto centerComparator = [&](uint32_t idx1, uint32_t idx2) { 252 | return m_leafNodes[idx1]->center[axis] < m_leafNodes[idx2]->center[axis]; 253 | }; 254 | // from small to large 255 | std::sort(triNodeIndices.begin() + beginIdx, triNodeIndices.begin() + endIdx, centerComparator); 256 | size_t median = (beginIdx + endIdx) / 2; 257 | for (size_t i = beginIdx; i < median; i++) { 258 | const Point ¢er = m_leafNodes[triNodeIndices[i]]->center; 259 | leftAABB.expandBy(center); 260 | } 261 | for (size_t i = median; i < endIdx; i++) { 262 | const Point ¢er = m_leafNodes[triNodeIndices[i]]->center; 263 | rightAABB.expandBy(center); 264 | } 265 | return median; 266 | } 267 | }; -------------------------------------------------------------------------------- /pathcut/visualizer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "pathcut_wrapper.h" 7 | 8 | MTS_NAMESPACE_BEGIN 9 | 10 | class PathcutVisualizer : public MonteCarloIntegrator { 11 | private: 12 | PathcutWrapper pwrapper; 13 | 14 | Transform m_camTrafo; 15 | Point m_visualizerPos; 16 | Intersection m_its; 17 | 18 | public: 19 | PathcutVisualizer(const Properties &props) : MonteCarloIntegrator(props) { 20 | pwrapper.initGuidingParams(props); 21 | m_camTrafo = props.getTransform("camTrafo"); 22 | } 23 | 24 | PathcutVisualizer(Stream *stream, InstanceManager *manager) : MonteCarloIntegrator(stream, manager) {} 25 | 26 | bool render(Scene *scene, RenderQueue *queue, const RenderJob *job, int sceneResID, int sensorResID, int samplerResID) { 27 | ref sched = Scheduler::getInstance(); 28 | ref sensor = static_cast(sched->getResource(sensorResID)); 29 | ref film = sensor->getFilm(); 30 | int integratorResID = sched->registerResource(this); 31 | 32 | m_visualizerPos = sensor->getWorldTransform()->eval(0.f)(Point(0.f)); 33 | 34 | Point camPos = m_camTrafo(Point(0.f)); 35 | Vector camDir = m_camTrafo(Vector(0.f, 0.f, 1.f)); 36 | 37 | const auto &meshes = scene->getMeshes(); 38 | for (size_t i = 0; i < meshes.size(); i++) { 39 | meshes[i]->setID(std::string(1, char(i))); 40 | } 41 | 42 | Ray r(camPos, normalize(m_visualizerPos - camPos), (m_visualizerPos - camPos).length() - 0.1, (m_visualizerPos - camPos).length() + 0.1, 0.f); 43 | Intersection its; 44 | scene->rayIntersect(r, its); 45 | if (its.isValid() && (its.shape->getProperties().getBoolean("guided", false) || its.shape->getProperties().getBoolean("receiver", false))) { 46 | m_its = its; 47 | } else { 48 | return false; 49 | } 50 | 51 | pwrapper.precomputePathCuts(scene, camPos, camDir); 52 | 53 | ref proc; 54 | proc = new BlockedRenderProcess(job, queue, scene->getBlockSize()); 55 | proc->bindResource("integrator", integratorResID); 56 | proc->bindResource("scene", sceneResID); 57 | proc->bindResource("sensor", sensorResID); 58 | proc->bindResource("sampler", samplerResID); 59 | scene->bindUsedResources(proc); 60 | bindUsedResources(proc); 61 | sched->schedule(proc); 62 | m_process = proc; 63 | sched->wait(proc); 64 | m_process = NULL; 65 | 66 | sched->unregisterResource(integratorResID); 67 | 68 | return proc->getReturnStatus() == ParallelProcess::ESuccess; 69 | } 70 | 71 | Spectrum Li(const RayDifferential &r, RadianceQueryRecord &rRec) const { 72 | /* Some aliases and local variables */ 73 | Spectrum Li = pwrapper.evalSGs(m_visualizerPos, m_its, r.d); 74 | return Li; 75 | } 76 | 77 | void serialize(Stream *stream, InstanceManager *manager) const { 78 | MonteCarloIntegrator::serialize(stream, manager); 79 | } 80 | 81 | std::string toString() const { 82 | std::ostringstream oss; 83 | oss << "PathcutVisualizer[" << endl 84 | << " strictNormals = " << m_strictNormals << endl 85 | << "]"; 86 | return oss.str(); 87 | } 88 | 89 | public: 90 | MTS_DECLARE_CLASS() 91 | }; 92 | 93 | MTS_IMPLEMENT_CLASS(PathcutVisualizer, false, MonteCarloIntegrator) 94 | MTS_EXPORT_PLUGIN(PathcutVisualizer, "Pathcut visualizer"); 95 | MTS_NAMESPACE_END -------------------------------------------------------------------------------- /pathcut_guided_path.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of Mitsuba, a physically based rendering system. 3 | 4 | Copyright (c) 2007-2014 by Wenzel Jakob 5 | Copyright (c) 2017 by ETH Zurich, Thomas Mueller. 6 | 7 | Mitsuba is free software; you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License Version 3 9 | as published by the Free Software Foundation. 10 | 11 | Mitsuba is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "pathcut/pathcut_wrapper.h" 26 | 27 | MTS_NAMESPACE_BEGIN 28 | 29 | static StatsCounter avgPathLength("Path tracer", "Average path length", EAverage); 30 | 31 | class PathcutGuidedPathTracer : public MonteCarloIntegrator { 32 | private: 33 | // std::vector> m_sTrees; 34 | PathcutWrapper pwrapper; 35 | 36 | public: 37 | PathcutGuidedPathTracer(const Properties &props) : MonteCarloIntegrator(props) { 38 | pwrapper.initGuidingParams(props); 39 | } 40 | 41 | /// Unserialize from a binary data stream 42 | PathcutGuidedPathTracer(Stream *stream, InstanceManager *manager) 43 | : MonteCarloIntegrator(stream, manager) {} 44 | 45 | bool render(Scene *scene, RenderQueue *queue, const RenderJob *job, 46 | int sceneResID, int sensorResID, int samplerResID) { 47 | ref sched = Scheduler::getInstance(); 48 | ref sensor = static_cast(sched->getResource(sensorResID)); 49 | ref film = sensor->getFilm(); 50 | 51 | size_t nCores = sched->getCoreCount(); 52 | const Sampler *sampler = static_cast(sched->getResource(samplerResID, 0)); 53 | size_t sampleCount = sampler->getSampleCount(); 54 | 55 | Log(EInfo, "Starting render job (%ix%i, " SIZE_T_FMT " %s, " SIZE_T_FMT " %s, " SSE_STR ") ..", film->getCropSize().x, film->getCropSize().y, 56 | sampleCount, sampleCount == 1 ? "sample" : "samples", nCores, 57 | nCores == 1 ? "core" : "cores"); 58 | 59 | auto trafo = sensor->getWorldTransform()->eval(0.f); 60 | Point camPos = trafo(Point(0.f)); 61 | Vector camDir = trafo(Vector(0.f, 0.f, 1.f)); 62 | 63 | pwrapper.precomputePathCuts(scene, camPos, camDir); 64 | 65 | ref proc = new BlockedRenderProcess(job, 66 | queue, scene->getBlockSize()); 67 | int integratorResID = sched->registerResource(this); 68 | proc->bindResource("integrator", integratorResID); 69 | proc->bindResource("scene", sceneResID); 70 | proc->bindResource("sensor", sensorResID); 71 | proc->bindResource("sampler", samplerResID); 72 | scene->bindUsedResources(proc); 73 | bindUsedResources(proc); 74 | sched->schedule(proc); 75 | 76 | m_process = proc; 77 | sched->wait(proc); 78 | m_process = NULL; 79 | sched->unregisterResource(integratorResID); 80 | 81 | return proc->getReturnStatus() == ParallelProcess::ESuccess; 82 | } 83 | 84 | void renderBlock(const Scene *scene, 85 | const Sensor *sensor, Sampler *sampler, ImageBlock *block, 86 | const bool &stop, const std::vector> &points) const { 87 | 88 | size_t sampleCount = sampler->getSampleCount(); 89 | Float diffScaleFactor = 1.0f / std::sqrt((Float)sampleCount); 90 | 91 | bool needsApertureSample = sensor->needsApertureSample(); 92 | bool needsTimeSample = sensor->needsTimeSample(); 93 | 94 | RadianceQueryRecord rRec(scene, sampler); 95 | Point2 apertureSample(0.5f); 96 | Float timeSample = 0.5f; 97 | RayDifferential sensorRay; 98 | 99 | block->clear(); 100 | 101 | uint32_t queryType = RadianceQueryRecord::ESensorRay; 102 | 103 | if (!sensor->getFilm()->hasAlpha()) /* Don't compute an alpha channel if we don't have to */ 104 | queryType &= ~RadianceQueryRecord::EOpacity; 105 | 106 | for (size_t i = 0; i < points.size(); ++i) { 107 | Point2i offset = Point2i(points[i]) + Vector2i(block->getOffset()); 108 | if (stop) 109 | break; 110 | 111 | sampler->generate(offset); 112 | 113 | for (size_t j = 0; j < sampleCount; j++) { 114 | rRec.newQuery(queryType, sensor->getMedium()); 115 | Point2 samplePos(Point2(offset) + Vector2(rRec.nextSample2D())); 116 | 117 | if (needsApertureSample) 118 | apertureSample = rRec.nextSample2D(); 119 | if (needsTimeSample) 120 | timeSample = rRec.nextSample1D(); 121 | 122 | Spectrum spec = sensor->sampleRayDifferential( 123 | sensorRay, samplePos, apertureSample, timeSample); 124 | 125 | sensorRay.scaleDifferential(diffScaleFactor); 126 | 127 | spec *= Li(sensorRay, rRec); 128 | block->put(samplePos, spec, rRec.alpha); 129 | sampler->advance(); 130 | } 131 | } 132 | } 133 | 134 | Spectrum Li(const RayDifferential &r, RadianceQueryRecord &rRec) const { 135 | /* Some aliases and local variables */ 136 | const Scene *scene = rRec.scene; 137 | Intersection &its = rRec.its; 138 | RayDifferential ray(r); 139 | Spectrum Li(0.0f); 140 | bool scattered = false; 141 | 142 | /* Perform the first ray intersection (or ignore if the 143 | intersection has already been provided). */ 144 | rRec.rayIntersect(ray); 145 | ray.mint = Epsilon; 146 | 147 | Spectrum throughput(1.0f); 148 | Float eta = 1.0f; 149 | 150 | while (rRec.depth <= m_maxDepth || m_maxDepth < 0) { 151 | if (!its.isValid()) { 152 | /* If no intersection could be found, potentially return 153 | radiance from a environment luminaire if it exists */ 154 | if ((rRec.type & RadianceQueryRecord::EEmittedRadiance) && (!m_hideEmitters || scattered)) 155 | Li += throughput * scene->evalEnvironment(ray); 156 | break; 157 | } 158 | 159 | const BSDF *bsdf = its.getBSDF(); 160 | 161 | // if (sNodes.empty() && rRec.depth == 1) { 162 | // float data[3] = {0, 1, 0}; 163 | // return Spectrum(data); 164 | // } 165 | 166 | /* Possibly include emitted radiance if requested */ 167 | if (its.isEmitter() && (rRec.type & RadianceQueryRecord::EEmittedRadiance) && (!m_hideEmitters || scattered)) 168 | Li += throughput * its.Le(-ray.d); 169 | 170 | // /* Include radiance from a subsurface scattering model if requested */ 171 | // if (its.hasSubsurface() && (rRec.type & RadianceQueryRecord::ESubsurfaceRadiance)) 172 | // Li += throughput * its.LoSub(scene, rRec.sampler, -ray.d, rRec.depth); 173 | 174 | if ((rRec.depth >= m_maxDepth && m_maxDepth > 0) || (m_strictNormals && dot(ray.d, its.geoFrame.n) * Frame::cosTheta(its.wi) >= 0)) { 175 | 176 | /* Only continue if: 177 | 1. The current path length is below the specifed maximum 178 | 2. If 'strictNormals'=true, when the geometric and shading 179 | normals classify the incident direction to the same side */ 180 | break; 181 | } 182 | 183 | /* ==================================================================== */ 184 | /* Direct illumination sampling (NEE) */ 185 | /* ==================================================================== */ 186 | 187 | /* Estimate the direct illumination if this is requested */ 188 | DirectSamplingRecord dRec(its); 189 | 190 | if ((rRec.type & RadianceQueryRecord::EDirectSurfaceRadiance) && 191 | (bsdf->getType() & BSDF::ESmooth)) { 192 | Spectrum value = scene->sampleEmitterDirect(dRec, rRec.nextSample2D()); 193 | if (!value.isZero()) { 194 | const Emitter *emitter = static_cast(dRec.object); 195 | 196 | /* Allocate a record for querying the BSDF */ 197 | BSDFSamplingRecord bRec(its, its.toLocal(dRec.d), ERadiance); 198 | 199 | /* Evaluate BSDF * cos(theta) */ 200 | const Spectrum bsdfVal = bsdf->eval(bRec); 201 | 202 | /* Prevent light leaks due to the use of shading normals */ 203 | if (!bsdfVal.isZero() && (!m_strictNormals || dot(its.geoFrame.n, dRec.d) * Frame::cosTheta(bRec.wo) > 0)) { 204 | 205 | /* Calculate prob. of having generated that direction 206 | using BSDF sampling */ 207 | Float bsdfPdf = (emitter->isOnSurface() && dRec.measure == ESolidAngle) 208 | ? (its.shape->getID()[0] == char(-1) ? bsdf->pdf(bRec) : pwrapper.pdfMat(bsdf, bRec, ERadiance)) 209 | : 0; 210 | 211 | /* Weight using the power heuristic */ 212 | Float weight = miWeight(dRec.pdf, bsdfPdf); 213 | #ifdef SINGLE_BOUNCE 214 | if (rRec.depth == m_maxDepth - 1) 215 | #endif 216 | Li += throughput * value * bsdfVal * weight; 217 | } 218 | } 219 | } 220 | 221 | /* ==================================================================== */ 222 | /* BSDF sampling */ 223 | /* ==================================================================== */ 224 | Spectrum bsdfWeight; 225 | BSDFSamplingRecord bRec(its, rRec.sampler, ERadiance); 226 | Float bsdfPdf; 227 | bool rayHit; 228 | 229 | bsdfWeight = pwrapper.sampleMat(bsdfPdf, bsdf, bRec, ERadiance, bRec.sampler->next2D()); 230 | 231 | if (bsdfWeight.isZero()) 232 | break; 233 | 234 | scattered |= bRec.sampledType != BSDF::ENull; 235 | 236 | // /* Prevent light leaks due to the use of shading normals */ 237 | const Vector wo = its.toWorld(bRec.wo); 238 | Float woDotGeoN = dot(its.geoFrame.n, wo); 239 | if (m_strictNormals && woDotGeoN * Frame::cosTheta(bRec.wo) <= 0) 240 | break; 241 | 242 | bool hitEmitter = false; 243 | Spectrum value; 244 | 245 | ray = Ray(its.p, wo, ray.time); 246 | rayHit = scene->rayIntersect(ray, its); 247 | if (rayHit) { 248 | /* Intersected something - check if it was a luminaire */ 249 | if (its.isEmitter()) { 250 | value = its.Le(-ray.d); 251 | dRec.setQuery(ray, its); 252 | hitEmitter = true; 253 | } 254 | } else { 255 | /* Intersected nothing -- perhaps there is an environment map? */ 256 | const Emitter *env = scene->getEnvironmentEmitter(); 257 | 258 | if (env) { 259 | if (m_hideEmitters && !scattered) 260 | break; 261 | 262 | value = env->evalEnvironment(ray); 263 | if (!env->fillDirectSamplingRecord(dRec, ray)) 264 | break; 265 | hitEmitter = true; 266 | } else { 267 | break; 268 | } 269 | } 270 | /* Keep track of the throughput and relative 271 | refractive index along the path */ 272 | throughput *= bsdfWeight; 273 | eta *= bRec.eta; 274 | 275 | /* If a luminaire was hit, estimate the local illumination and 276 | weight using the power heuristic */ 277 | if (hitEmitter && 278 | (rRec.type & RadianceQueryRecord::EDirectSurfaceRadiance)) { 279 | /* Compute the prob. of generating that direction using the 280 | implemented direct illumination sampling technique */ 281 | const Float lumPdf = !(bRec.sampledType & BSDF::EDelta) ? scene->pdfEmitterDirect(dRec) : 0; 282 | #ifdef SINGLE_BOUNCE 283 | if (rRec.depth == m_maxDepth - 1) 284 | #endif 285 | Li += throughput * value * miWeight(bsdfPdf, lumPdf); 286 | } 287 | 288 | /* ==================================================================== */ 289 | /* Indirect illumination */ 290 | /* ==================================================================== */ 291 | 292 | /* Set the recursive query type. Stop if no surface was hit by the 293 | BSDF sample or if indirect illumination was not requested */ 294 | if (!its.isValid() || !(rRec.type & RadianceQueryRecord::EIndirectSurfaceRadiance)) 295 | break; 296 | rRec.type = RadianceQueryRecord::ERadianceNoEmission; 297 | 298 | if (rRec.depth++ >= m_rrDepth) { 299 | /* Russian roulette: try to keep path weights equal to one, 300 | while accounting for the solid angle compression at refractive 301 | index boundaries. Stop with at least some probability to avoid 302 | getting stuck (e.g. due to total internal reflection) */ 303 | 304 | Float q = std::min(throughput.max() * eta * eta, (Float)0.95f); 305 | if (rRec.nextSample1D() >= q) 306 | break; 307 | throughput /= q; 308 | } 309 | } 310 | 311 | /* Store statistics */ 312 | avgPathLength.incrementBase(); 313 | avgPathLength += rRec.depth; 314 | 315 | return Li; 316 | } 317 | 318 | inline Float miWeight(Float pdfA, Float pdfB) const { 319 | pdfA *= pdfA; 320 | pdfB *= pdfB; 321 | return pdfA / (pdfA + pdfB); 322 | } 323 | 324 | void serialize(Stream *stream, InstanceManager *manager) const { 325 | MonteCarloIntegrator::serialize(stream, manager); 326 | } 327 | 328 | std::string toString() const { 329 | std::ostringstream oss; 330 | oss << "PathcutGuidedPathTracer[" << endl 331 | << " maxDepth = " << m_maxDepth << "," << endl 332 | << " rrDepth = " << m_rrDepth << "," << endl 333 | << " strictNormals = " << m_strictNormals << endl 334 | << "]"; 335 | return oss.str(); 336 | } 337 | 338 | public: 339 | MTS_DECLARE_CLASS() 340 | }; 341 | 342 | MTS_IMPLEMENT_CLASS(PathcutGuidedPathTracer, false, MonteCarloIntegrator) 343 | MTS_EXPORT_PLUGIN(PathcutGuidedPathTracer, "Path tracer guided with pathcut"); 344 | MTS_NAMESPACE_END --------------------------------------------------------------------------------