SLProject  4.3.020
A platform independent 3D computer graphics framework for desktop OS, Android, iOS and online in web browsers
SLPathtracer.cpp
Go to the documentation of this file.
1 /**
2  * \file SLPathtracer.cpp
3  * \date July 2014
4  * \authors Thomas Schneiter, Marcus Hudritsch
5  * \copyright http://opensource.org/licenses/GPL-3.0
6  * \remarks Please use clangformat to format the code. See more code style on
7  * https://github.com/cpvrlab/SLProject4/wiki/SLProject-Coding-Style
8  */
9 
10 #include <algorithm>
11 #include <cmath>
12 #include <typeinfo>
13 
14 #include <SLCamera.h>
15 #include <SLLightRect.h>
16 #include <SLPathtracer.h>
17 #include <SLSceneView.h>
18 #include <GlobalTimer.h>
19 #include <Profiler.h>
20 
21 extern SLfloat rnd01();
22 
23 //-----------------------------------------------------------------------------
24 //! Sentinel pdf for SLPathtracer::trace
25 /*! Handed down for rays that next event estimation cannot generate: the
26 primary ray, and every specular or transmissive bounce. A light source reached
27 by such a ray contributes its full emission, because shade() never produced a
28 competing estimate of the same path. */
29 static const SLfloat PDF_NO_MIS = -1.0f;
30 //-----------------------------------------------------------------------------
31 //! Path depth up to which Russian roulette always lets the path continue
32 /*! Purely a variance and cost knob: every value here is unbiased, so this
33 trades render time against noise and nothing else.
34 
35 Roulette is what makes the estimator unbiased, but it is not free. Because the
36 survival probability is the albedo, the 1/survival of a survivor cancels that
37 bounce's attenuation exactly, so a path that survives fifteen bounces arrives
38 carrying full weight instead of 0.75^15. Roulette therefore turns "many paths,
39 each tiny" into "few paths, each full size": the same mean with a much heavier
40 tail. Taking the first bounces deterministically keeps that tail out of the
41 part of the sum that carries most of the energy.
42 
43 A model of the interreflection series alone (albedo 0.75, exact answer
44 1/(1-0.75) = 4) says the roulette tail shrinks quickly with this value:
45 
46  start depth mean sd mean path length
47  3 3.998 1.46 7.00
48  5 4.000 0.82 9.02
49  8 4.000 0.34 12.00
50 
51 Measured on the real scene, it does not. Raising it from 3 to 8 made the
52 ceiling around the light *worse* (median absolute residual 3.77 -> 4.05 at
53 100 spp) for 1.7 times the path length. The model is wrong about what dominates
54 there: the noise in that region is not the roulette tail but the near field of
55 the area light, where a surface centimetres from a 1.0 x 0.65 emitter receives
56 an enormous radiance that neighbouring surfaces then see through a rare bounce.
57 Tracing deeper only finds more of those. So the value is kept low: roulette
58 removes the depth bias, and the near field needs a different fix (solid angle
59 sampling of the rectangle, or the sample clamp). */
60 static const SLint RR_START_DEPTH = 3;
61 //-----------------------------------------------------------------------------
62 //! Russian roulette survival test for the continuation of a path
63 /*! Returns 0 if the path is absorbed and must not be continued, otherwise the
64 survival probability that the caller has to divide its contribution by.
65 
66 Russian roulette is not the same thing as Monte Carlo, although both are
67 random. Monte Carlo is the estimator itself: sample from a density and average
68 f(x)/p(x). Russian roulette is a technique used inside it, and it answers a
69 different question — how to terminate an unbounded recursion without making the
70 answer wrong. Light bounces between the walls forever, so the recursion has to
71 be stopped somewhere. Stopping it at a fixed depth discards the light that the
72 longer paths would have carried, which is a bias that no number of samples can
73 remove. Stopping it at random and dividing the survivors by their survival
74 probability leaves the mean untouched:
75 
76  E = q * 0 + (1 - q) * L / (1 - q) = L
77 
78 The survival probability is the albedo of the surface, so that the 1/survival
79 of a survivor cancels the albedo of that bounce exactly. That is the classic
80 formulation: the colour of the surface decides how likely the path is to
81 continue, rather than how much it is dimmed. A surface with an albedo of 1
82 absorbs nothing and therefore always survives, which is why the hard depth cap
83 in trace() is still needed as a safety net. */
84 static SLfloat russianRoulette(SLint depth, SLCol4f albedo)
85 {
86  if (depth <= RR_START_DEPTH) return 1.0f;
87 
88  SLfloat survival = std::min(albedo.maxXYZ(), 1.0f);
89 
90  if (survival <= 0.0f) return 0.0f; // black surface, nothing to continue
91  if (survival >= 1.0f) return 1.0f; // absorbs nothing, always continues
92 
93  return (rnd01() < survival) ? survival : 0.0f;
94 }
95 //-----------------------------------------------------------------------------
96 //! Weight of one sample drawn from the Phong lobe of the given exponent
97 /*! SLRay::reflectMC and SLRay::refractMC draw a direction from the Phong lobe
98 
99  p(w) = (n + 1) / (2 * PI) * cos^n(alpha)
100 
101 where alpha is the angle to the perfect specular or transmissive direction and
102 n is the shininess or the translucency. The matching normalised Phong BSDF is
103 
104  f(w) = rho * (n + 2) / (2 * PI) * cos^n(alpha)
105 
106 and the Monte Carlo estimator of the rendering equation weights the incoming
107 radiance by f * cos(theta) / p, with theta the angle to the SURFACE NORMAL and
108 not to the lobe axis. The cos^n(alpha) and the 2*PI cancel and what is left is
109 
110  rho * (n + 2) / (n + 1) * cos(theta)
111 
112 The colour rho is applied by the caller as the material's specular or
113 transmissive colour, so this returns the scalar part.
114 
115 The cos(theta) is what was missing: the code applied only (n+2)/(n+1), so every
116 glossy sample was too bright by 1/cos(theta). That is a factor of 1 straight
117 along the normal and unbounded at the horizon, which is the direction a wide
118 lobe on a grazing surface samples most often, so the error showed up as a
119 bright rim exactly where a glossy highlight is supposed to fall off.
120 
121 For a perfect mirror the caller applies no weight at all rather than calling
122 this: (n+2)/(n+1) is the normalisation of the lobe estimator and there is no
123 lobe to normalise when the direction was not drawn from one. At the PERFECT
124 limit of 1000 that factor is 1.001, so it was silently adding a tenth of a
125 percent of energy per specular bounce.
126 
127 \param exponent shininess for reflection, translucency for transmission
128 \param sampleDir sampled direction, already known to be on the right side
129 \param normal the surface normal at the hit point */
131  const SLVec3f& sampleDir,
132  const SLVec3f& normal)
133 {
134  // The absolute value, because SLMesh::preShade does not flip the hit
135  // normal towards the ray: on a back face hit both the normal and the
136  // sampled direction sit on the other side and the cosine comes out
137  // negative although the geometry is the same.
138  SLfloat cosTheta = std::abs(sampleDir.dot(normal));
139 
140  return (exponent + 2.0f) / (exponent + 1.0f) * cosTheta;
141 }
142 //-----------------------------------------------------------------------------
143 //! Power heuristic (beta = 2) weight of the strategy whose density is pdfThis
144 /*! Two strategies that can both generate the same path each return an unbiased
145 estimate of it, so simply adding them would count the path twice. Weighting
146 them with w(pdfThis) + w(pdfOther) = 1 counts it exactly once, and the power
147 heuristic puts nearly all of that weight on whichever strategy had the higher
148 density for this particular path, which is the one with the lower variance
149 here. Written as 1/(1+r^2) rather than a^2/(a^2+b^2) so that a very large
150 density cannot overflow. */
151 static SLfloat misWeight(SLfloat pdfThis, SLfloat pdfOther)
152 {
153  if (pdfThis <= 0.0f) return 0.0f;
154 
155  SLfloat ratio = pdfOther / pdfThis;
156  return 1.0f / (1.0f + ratio * ratio);
157 }
158 //-----------------------------------------------------------------------------
159 //! Solid angle density with which shade() would have sampled this light hit
160 /*! Returns 0 if the hit surface is not a light that shade() samples over its
161 area. shade() draws a point uniformly on the rectangle, so the density over the
162 area is 1/area; the conversion to a density over solid angle, as seen from the
163 shaded point, is the usual dist^2 / (cosLight * area). */
165 {
166  if (!ray->hitNode || typeid(*ray->hitNode) != typeid(SLLightRect))
167  return 0.0f;
168 
169  auto* rect = (SLLightRect*)ray->hitNode;
170  if (!rect->isOn()) return 0.0f;
171 
172  // SLRay::setDir does not normalise, so do not assume a unit direction
173  SLVec3f dir(ray->dir);
174  dir.normalize();
175 
176  // spotDirWS is the normal of the rectangle, as in shade()
177  SLfloat cosLight = -dir.dot(rect->spotDirWS());
178  if (cosLight <= 0.0f) return 0.0f;
179 
180  return (ray->length * ray->length) / (cosLight * rect->area());
181 }
182 
183 //-----------------------------------------------------------------------------
185 {
186  name("PathTracer");
187  _calcDirect = true;
188  _calcIndirect = true;
189  _sampleClamp = 3.0f; // see SLPathtracer.h; 0 switches it off
190  _noiseRSE = 0.0f;
191  _noiseRSE999 = 0.0f;
192  gamma(2.2f);
193 }
194 //-----------------------------------------------------------------------------
195 /*!
196 Main render function. The Path Tracing algorithm starts from here
197 */
199 {
200  _sv = sv;
201  _state = rtBusy; // From here we state the PT as busy
202  _renderSec = 0.0f; // reset time
203  _progressPC = 0; // % rendered
204 
205  initStats(0); // init statistics
206  prepareImage();
207 
208  // Drop the 8 bit accumulation image that older versions kept here.
209  // The path tracer needs only _images[0], which holds the clamped and
210  // gamma corrected image for the display. The progressive mean itself is
211  // accumulated in _radianceSum (see SLPathtracer.h).
212  while (_images.size() > 1)
213  {
214  delete _images[_images.size() - 1];
215  _images.pop_back();
216  }
217 
218  // Allocate and clear the high precision accumulation buffer. Its size is
219  // taken from _images[0] and not from the viewport, because prepareImage
220  // scales the image with _resolutionFactor.
221  const size_t numPixels = (size_t)_images[0]->width() *
222  (size_t)_images[0]->height();
223  _radianceSum.assign(numPixels, SLCol4f::BLACK);
224 
225  // The two moments the noise figure is computed from. See computeNoise.
226  _lumSum.assign(numPixels, 0.0);
227  _lumSumSq.assign(numPixels, 0.0);
228  _noiseRSE = 0.0f;
229  _noiseRSE999 = 0.0f;
230 
231  // Measure time
232  double t1 = GlobalTimer::timeS();
233 
234  // Lambda function for async slice rendering
235  renderSlicesPTAsync = [this](bool isMainThread, SLint curSample, SLuint threadNum)
236  {
237  SLPathtracer::renderSlices(isMainThread, curSample, threadNum);
238  };
239 
240  // Do multi-threading only in release config
241  SL_LOG("\n\nRendering with %d samples", _aaSamples);
242  SL_LOG("\nCurrent Sample: ");
243  for (int currentSample = 1; currentSample <= _aaSamples; currentSample++)
244  {
245  vector<thread> threads; // vector for additional threads
246  _nextLine = 0;
247 
248  // Start additional threads on the renderSlices function
249  for (SLuint t = 0; t < Utils::maxThreads() - 1; t++)
250  threads.emplace_back(renderSlicesPTAsync, false, currentSample, t);
251 
252  // Do the same work in the main thread
253  renderSlicesPTAsync(true, currentSample, 0);
254 
255  for (auto& thread : threads)
256  thread.join();
257 
258  _progressPC = (SLint)((SLfloat)currentSample / (SLfloat)_aaSamples * 100.0f);
259  }
260 
262  _raysPerMS.set((float)SLRay::totalNumRays() / _renderSec / 1000.0f);
263  _progressPC = 100;
264  computeNoise();
265 
266  SL_LOG("\nTime to render image: %6.3fsec", _renderSec);
267 
268  _state = rtFinished;
269  return true;
270 }
271 //-----------------------------------------------------------------------------
272 /*!
273 Renders a slice of 4px width.
274 */
275 void SLPathtracer::renderSlices(const bool isMainThread,
276  SLint currentSample,
277  SLuint threadNum)
278 {
279  if (!isMainThread)
280  {
281  PROFILE_THREAD(string("PT-Worker-") + std::to_string(threadNum));
282  }
283 
285 
286  // Time points
287  double t1 = 0;
288 
289  const SLint imgW = (SLint)_images[0]->width();
290  const SLint imgH = (SLint)_images[0]->height();
291 
292  while (_nextLine < imgW)
293  {
294  // The next section must be protected
295  // Making _nextLine an atomic was not sufficient.
296  _mutex.lock();
297  SLint minX = _nextLine;
298  _nextLine += 4;
299  _mutex.unlock();
300 
301  // The image width is not necessarily a multiple of the slice width of
302  // 4px, so the last slice has to be cut off. Without this the loop below
303  // would index past the end of _radianceSum.
304  SLint maxX = std::min(minX + 4, imgW);
305 
306  for (SLint x = minX; x < maxX; ++x)
307  {
308  for (SLint y = 0; y < imgH; ++y)
309  {
310  SLCol4f color(SLCol4f::BLACK);
311 
312  // calculate direction for primary ray - scatter with random variables for anti aliasing
313  SLRay primaryRay;
314  setPrimaryRay((SLfloat)((SLfloat)x - rnd01() + 0.5f),
315  (SLfloat)((SLfloat)y - rnd01() + 0.5f),
316  &primaryRay);
317 
318  ///////////////////////////////////
319  color += trace(&primaryRay, PDF_NO_MIS);
320  ///////////////////////////////////
321 
322  // Optional firefly clamp. The brightest channel is brought
323  // down to the limit and the other two are scaled with it, so
324  // that the sample loses energy but keeps its colour. Clamping
325  // each channel on its own would shift the hue of everything it
326  // touches. See _sampleClamp for why this is off by default.
327  if (_sampleClamp > 0.0f)
328  {
329  SLfloat maxChannel = color.maxXYZ();
330  if (maxChannel > _sampleClamp)
331  color *= _sampleClamp / maxChannel;
332  }
333 
334  // Add the raw linear radiance of this sample to the running sum.
335  // Nothing is clamped or quantised here on purpose: the estimator
336  // only converges with 1/sqrt(N) if every sample keeps its full
337  // value and its full precision. The correction that one sample
338  // applies to the mean shrinks with 1/currentSample, so rounding
339  // the mean to 8 bit (as the old _images[1] did) would freeze
340  // bright outliers (fireflies) at a wrong value forever.
341  const size_t pixel = (size_t)y * (size_t)imgW + (size_t)x;
342 
343  SLCol4f& radianceSum = _radianceSum[pixel];
344  radianceSum += color;
345 
346  // The same sample as a single number, for the noise figure of
347  // computeNoise. It is taken here and not from _images[0],
348  // because that image is clamped to [0,1] and gamma corrected
349  // for the display, which flattens every firefly to white and
350  // destroys exactly the signal being measured. Rec. 709
351  // luminance, matching the linear primaries the renderer works
352  // in. When _sampleClamp is on the sample arriving here is
353  // already clamped, so the figure describes the noise of the
354  // clamped estimator, which is the image actually produced.
355  const SLdouble lum = 0.2126 * (SLdouble)color.r +
356  0.7152 * (SLdouble)color.g +
357  0.0722 * (SLdouble)color.b;
358  _lumSum[pixel] += lum;
359  _lumSumSq[pixel] += lum * lum;
360 
361  // The mean of all samples taken so far for this pixel
362  color = radianceSum / (SLfloat)currentSample;
363 
364  // From here on the color is for the display only: clamp it into
365  // the displayable range and apply the gamma correction.
366  color.clampMinMax(0.0f, 1.0f);
368 
369  // image to render
370  _images[0]->setPixeliRGB(x,
371  y,
372  CVVec4f(color.r,
373  color.g,
374  color.b,
375  color.a));
376  }
377 
378  // update image after 500 ms
379  if (_sv->onWndUpdate && isMainThread)
380  {
381  if (GlobalTimer::timeS() - t1 > 0.5f)
382  {
384  _sv->onWndUpdate(); // update window
385  t1 = GlobalTimer::timeS();
386  }
387  }
388  }
389  }
390 }
391 //-----------------------------------------------------------------------------
392 /*!
393 Recursively traces ray in scene.
394 */
396 {
397  // The radiance gathered at this hit point. It must start at BLACK. It used
398  // to start at ray->backgroundColor, which added the background to EVERY
399  // surface at EVERY bounce and therefore compounded down the path. That
400  // stayed invisible in scenes with a black background and no skybox only.
401  SLCol4f finalColor(SLCol4f::BLACK);
402 
403  // Participating Media init
404  SLfloat absorption = 1.0f; // used to calculate absorption along the ray
405  SLfloat scaleBy = 1.0f; // used to scale surface reflectance at the end of random walk
406 
407  // Intersect scene
408  SLNode* root = _sv->s()->root3D();
409  if (root) root->hitRec(ray);
410 
411  // End of recursion: the ray escaped the scene, so it gathers the radiance
412  // of the environment (skybox or camera background). This is the only place
413  // where the background may contribute.
414  if (ray->length >= FLT_MAX)
415  return ray->backgroundColor;
416 
417  // End of recursion: the safety net. Russian roulette below decides how long
418  // a path lives, but it cannot terminate a surface that absorbs nothing,
419  // such as the perfect mirror and the glass of the Muttenzer Box, so a hard
420  // cap is still needed against an endless specular chain. The cap is set
421  // high enough (see the startPathtracing call sites) that roulette and not
422  // this test ends virtually every path, so the bias it used to cause is now
423  // negligible instead of dominant.
424  if (ray->depth > maxDepth())
425  return SLCol4f::BLACK;
426 
427  // hit material
428  SLMaterial* mat = ray->hitMesh->mat();
429  ray->hitMesh->preShade(ray);
430 
431  // set object color
432  SLCol4f objectColor = SLCol4f::BLACK;
433  if (ray->hitMatIsDiffuse())
434  objectColor = mat->diffuse();
435  else if (ray->hitMatIsReflective())
436  objectColor = mat->specular();
437  else if (ray->hitMatIsTransparent())
438  objectColor = mat->transmissive();
439 
440  // set object emission
441  SLCol4f objectEmission = mat->emissive();
442  SLfloat maxEmission = objectEmission.maxXYZ();
443 
444  // End of recursion: a light source is hit
445  if (maxEmission > 0)
446  {
447  // The primary ray, or a specular or transmissive bounce. shade() only
448  // ever samples from a diffuse surface, so it cannot have produced a
449  // competing estimate of this path and the emission counts in full.
450  // This also covers the camera looking straight at the light.
451  if (bsdfPdf < 0.0f)
452  return mat->emissive() * absorption;
453 
454  SLfloat lightPdf = lightPdfMC(ray);
455 
456  if (lightPdf <= 0.0f)
457  {
458  // Emissive, but not a light that shade() samples over an area. If
459  // it is a light node at all, shade() samples it as a point or
460  // directional delta light, whose direction the scattering can
461  // never reproduce, so counting it here as well would count it
462  // twice. SLLightSpot::hitRec and SLLightDirect::hitRec currently
463  // reject every ray that is not primary, so this cannot be reached;
464  // the test keeps the invariant here rather than resting on those
465  // two overrides. Anything else is an ordinary emissive mesh that
466  // shade() ignores, so it counts in full.
467  if (dynamic_cast<SLLight*>(ray->hitNode) != nullptr)
468  return SLCol4f::BLACK;
469 
470  return mat->emissive() * absorption;
471  }
472 
473  // Both shade() and the scattering can generate this path. Weight the
474  // two estimates with the power heuristic; shade() applies the
475  // complementary weight, so together they count the path exactly once.
476  return mat->emissive() * absorption * misWeight(bsdfPdf, lightPdf);
477  }
478 
479  // add absorption to base color from Participating Media
480  objectColor = objectColor * absorption;
481 
482  // diffuse reflection
483  if (ray->hitMatIsDiffuse())
484  {
485  // Add component wise the texture color
486  if (mat->numTextures() > 0)
487  {
488  objectColor &= ray->hitTexColor;
489  }
490 
491  if (_calcDirect)
492  finalColor += shade(ray, &objectColor) * scaleBy;
493 
494  // Russian roulette decides whether the path continues. The direct
495  // illumination above is a terminal estimate at this vertex and is
496  // always taken; only the continuation is gambled on. The roll is made
497  // inside the test so that switching the indirect illumination off does
498  // not consume random numbers here.
499  SLfloat survival = _calcIndirect ? russianRoulette(ray->depth, objectColor)
500  : 0.0f;
501 
502  if (survival > 0.0f)
503  {
504  SLRay scatter;
505  ray->diffuseMC(&scatter);
506 
507  // diffuseMC draws the direction from the cosine distribution, so
508  // its solid angle density is cos(theta)/PI. It is handed down so
509  // that a light hit further along the path can be weighted against
510  // the light sampling that shade() just did at this same vertex.
511  // With the direct illumination switched off shade() never runs,
512  // so there is nothing to weight against and the hit counts fully.
513  SLfloat scatterPdf = PDF_NO_MIS;
514  if (_calcDirect)
515  scatterPdf = std::max(scatter.dir.dot(ray->hitNormal), 0.0f) *
517 
518  // material emission, material diffuse and recursive indirect
519  // illumination, scaled up by 1/survival for the paths that lived
520  finalColor += (trace(&scatter, scatterPdf) & objectColor) *
521  (scaleBy / survival);
522  }
523  }
524  else if (ray->hitMatIsReflective())
525  {
526  // Russian roulette, as in the diffuse branch. A perfect mirror has an
527  // albedo of 1, always survives, and is bounded only by the depth cap.
528  SLfloat survival = russianRoulette(ray->depth, objectColor);
529  if (survival <= 0.0f) return finalColor;
530 
531  // scatter toward perfect specular direction
532  SLRay reflected;
533  ray->reflect(&reflected);
534 
535  // scatter around perfect reflected direction only if material not perfect
536  SLfloat lobeWeight = 1.0f;
537  if (mat->shininess() < SLMaterial::PERFECT)
538  {
539  if (!ray->reflectMC(&reflected, SLRay::lobeToWorld(reflected.dir)))
540  return finalColor; // sample below the horizon, see reflectMC
541 
542  lobeWeight = phongLobeWeight(mat->shininess(),
543  reflected.dir,
544  ray->hitNormal);
545  }
546 
547  // lobe weight * recursive indirect illumination and material base color
548  finalColor += (lobeWeight * (trace(&reflected, PDF_NO_MIS) & objectColor)) *
549  (scaleBy / survival);
550  }
551  else if (ray->hitMatIsTransparent())
552  {
553  // Russian roulette, as in the diffuse branch. This is independent of
554  // the Fresnel choice further down, which picks reflection or
555  // transmission but never ends the path.
556  SLfloat survival = russianRoulette(ray->depth, objectColor);
557  if (survival <= 0.0f) return finalColor;
558 
559  // scatter toward perfect transmissive direction
560  SLRay refracted;
561  ray->refract(&refracted);
562 
563  // init Schlick's approximation
564  SLVec3f rayDir = ray->dir;
565  rayDir.normalize();
566  SLVec3f refrDir = refracted.dir;
567  refrDir.normalize();
568  SLfloat n, nt;
569  SLVec3f hitNormal = ray->hitNormal;
570  hitNormal.normalize();
571 
572  // ray from outside in
573  if (ray->isOutside)
574  {
575  n = 1.0f;
576  nt = mat->kn();
577  }
578  else // ray from inside out
579  {
580  n = mat->kn();
581  nt = 1.0f;
582  }
583 
584  // calculate Schlick's approx.
585  SLfloat nbig, nsmall;
586  nbig = n > nt ? n : nt;
587  nsmall = n < nt ? n : nt;
588  SLfloat R0 = ((nbig - nsmall) / (nbig + nsmall));
589  R0 = R0 * R0;
590  SLbool into = (rayDir * hitNormal) < 0;
591  SLfloat c = 1.0f - (into ? (-rayDir * hitNormal) : (refrDir * hitNormal));
592  SLfloat schlick = R0 + (1 - R0) * c * c * c * c * c;
593 
594  SLfloat P = 0.25f + 0.5f * schlick; // probability of reflectance
595  SLfloat reflectionProbability = schlick / P;
596  SLfloat refractionProbability = (1.0f - schlick) / (1.0f - P);
597 
598  // scatter around perfect transmissive direction only if material not perfect
599  SLfloat lobeWeight = 1.0f;
600  SLbool refractIsValid = true;
601  if (mat->translucency() < SLMaterial::PERFECT)
602  {
603  refractIsValid = ray->refractMC(&refracted,
604  SLRay::lobeToWorld(refracted.dir));
605 
606  if (refractIsValid)
607  lobeWeight = phongLobeWeight(mat->translucency(),
608  refracted.dir,
609  ray->hitNormal);
610  }
611 
612  // probability of reflection
613  if (rnd01() > (0.25f + 0.5f * schlick))
614  {
615  // scatter toward transmissive direction. A sample that came back
616  // out on the incident side carries no energy, see refractMC.
617  if (refractIsValid)
618  finalColor += (lobeWeight *
619  (trace(&refracted, PDF_NO_MIS) & objectColor) *
620  refractionProbability) *
621  (scaleBy / survival);
622  }
623  else
624  {
625  // scatter toward perfect specular direction
626  SLRay scattered;
627  ray->reflect(&scattered);
628 
629  // Scatter around the perfect specular direction if the surface is
630  // not perfectly smooth, exactly as the reflective branch above
631  // does. A rough dielectric is rough on both sides of the interface
632  // -- the same microscopic slopes that spread the transmitted lobe
633  // spread the Fresnel reflected one -- so frosted glass whose
634  // transmission is blurred but whose surface still mirrors its
635  // surroundings sharply looks wrong. The width of the two lobes is
636  // controlled separately here: shininess for this reflection and
637  // translucency for the transmission above, so a material can still
638  // be given a polished surface over a diffusing interior by leaving
639  // its shininess at PERFECT.
640  //
641  // The lobe weight is the normalisation of the Phong lobe
642  // estimator and belongs only where a direction was actually drawn
643  // from that lobe, which is why a perfect surface still carries
644  // no weight at all: multiplying a mirror ray by
645  // (shininess + 2) / (shininess + 1) used to add 1% of energy per
646  // bounce at the shininess of 100 of the Muttenzer Box glass.
647  SLfloat reflLobeWeight = 1.0f;
648  if (mat->shininess() < SLMaterial::PERFECT)
649  {
650  if (!ray->reflectMC(&scattered,
651  SLRay::lobeToWorld(scattered.dir)))
652  return finalColor; // sample below the horizon, see reflectMC
653 
654  reflLobeWeight = phongLobeWeight(mat->shininess(),
655  scattered.dir,
656  ray->hitNormal);
657  }
658 
659  // lobe weight * recursive indirect illumination and material base color
660  finalColor += (reflLobeWeight *
661  (trace(&scattered, PDF_NO_MIS) & objectColor) *
662  reflectionProbability) *
663  (scaleBy / survival);
664  }
665  }
666 
667  return finalColor;
668 }
669 //-----------------------------------------------------------------------------
670 /*!
671 Calculates the direct illumination at the hit point of the ray by sampling the
672 light sources explicitly (next event estimation).
673 
674 A rectangular light is an AREA light and is estimated with a Monte Carlo area
675 estimator: one point is sampled uniformly on the rectangle (pdf = 1/area) and
676 the radiance reflected towards the ray is
677 
678  Lo = albedo/PI * Le * cosSurface * cosLight / dist^2 * area
679 
680 with the direction, the distance and both cosines taken at the SAMPLED point.
681 
682 The previous version took all of them at the light CENTRE while it tested the
683 visibility at a random point, and it replaced the geometric term by the OpenGL
684 attenuation and the spot cone exponent. That is the Blinn-Phong rasteriser
685 model, not an estimator of the area light integral. It missed the area factor
686 completely, which made the direct light 1/area too bright (1.54x for the
687 1.0 x 0.65 light of the Muttenzer Box), and it broke down for surfaces close to
688 a large light. Above all it put the direct illumination on a different scale
689 than the emissive material of the light mesh that the paths see when they hit
690 the light through the mirror or the glass sphere, so the two estimates of the
691 same illumination did not agree.
692 
693 All other light types are point or directional (delta) lights without any area.
694 For those the classic attenuation and spot cone model is kept.
695 */
697 {
698  SLCol4f color = SLCol4f::BLACK;
699  SLNode* root3D = _sv->s()->root3D();
700  SLVec3f N(ray->hitNormal);
701 
702  // loop over light sources in scene
703  for (auto* light : _sv->s()->lights())
704  {
705  if (!light || !light->isOn()) continue;
706 
707  if (typeid(*light) == typeid(SLLightRect))
708  {
709  ///////////////////////////////////////////////////////
710  // Area light: Monte Carlo estimate over its surface //
711  ///////////////////////////////////////////////////////
712 
713  auto* rect = (SLLightRect*)light;
714 
715  // One uniformly distributed sample point on the light (pdf=1/area)
716  SLVec3f toLight(rect->samplePointMC() - ray->hitPoint);
717  SLfloat distSqr = toLight.lengthSqr();
718  if (distSqr < FLT_EPSILON) continue;
719  SLfloat dist = sqrt(distSqr);
720  toLight /= dist;
721 
722  // Cosine at the shaded surface and at the sampled light point.
723  // spotDirWS is the normal of the light rectangle.
724  SLfloat cosSurface = toLight.dot(N);
725  SLfloat cosLight = -toLight.dot(rect->spotDirWS());
726 
727  // Sample is below the surface or behind the (one sided) light
728  if (cosSurface <= 0.0f || cosLight <= 0.0f) continue;
729 
730  // Visibility of the sampled point. The light mesh itself does not
731  // block, see SLLightRect::hitRec which ignores shadow rays.
732  SLRay shadowRay(dist, toLight, ray);
733  root3D->hitRec(&shadowRay);
734  if (shadowRay.length < dist) continue;
735 
736  // Geometric term of the area formulation
737  SLfloat geometry = cosSurface * cosLight / distSqr;
738 
739  // The cosine weighted scattering in trace() can generate this same
740  // path, so weight the two estimates against each other; trace()
741  // applies the complementary weight. With the indirect
742  // illumination switched off the scattering never runs and this
743  // estimate carries the path alone.
744  SLfloat weight = 1.0f;
745  if (_calcIndirect)
746  {
747  // The same conversion of 1/area into a solid angle density
748  // that lightPdfMC does for the hit coming the other way
749  SLfloat lightPdf = distSqr / (cosLight * rect->area());
750  SLfloat bsdfPdf = cosSurface * Utils::ONEOVERPI;
751  weight = misWeight(lightPdf, bsdfPdf);
752  }
753 
754  // albedo * brdf(1/PI) * Le * G / pdf, with pdf = 1/area
755  color += (*mat & light->diffuse()) *
756  (Utils::ONEOVERPI * geometry * rect->area() * weight);
757  }
758  else
759  {
760  ///////////////////////////////////////////////////////
761  // Point or directional light: no area, classic model //
762  ///////////////////////////////////////////////////////
763 
764  SLVec3f L(light->positionWS().vec3() - ray->hitPoint);
765  SLfloat lightDist = L.length();
766  L /= lightDist;
767  SLfloat LdN = L.dot(N);
768 
769  // hit point faces away from the light
770  if (LdN <= 0.0f) continue;
771 
772  SLfloat lighted = light->shadowTestMC(ray, L, lightDist, root3D);
773  if (lighted <= 0.0f) continue;
774 
775  // calculate spot effect if light is a spotlight
776  SLfloat spotEffect = 1.0f;
777  if (light->spotCutOffDEG() < 180.0f)
778  {
779  SLfloat LdS = std::max(-L.dot(light->spotDirWS()), 0.0f);
780 
781  // check if point is in spot cone
782  if (LdS <= light->spotCosCut()) continue;
783 
784  spotEffect = pow(LdS, (SLfloat)light->spotExponent());
785  }
786 
787  // material color * light emission * LdN * brdf(1/pi) * lighted
788  SLCol4f diffuseColor = (*mat & (light->diffuse() * LdN)) *
789  (Utils::ONEOVERPI * lighted);
790 
791  color += light->attenuation(lightDist) * spotEffect * diffuseColor;
792  }
793  }
794 
795  return color;
796 }
797 //-----------------------------------------------------------------------------
798 /*!
799 Turns the two luminance moments that renderSlices accumulated into the noise
800 figures of the Timing panel. Called once, after the last sample pass.
801 
802 For a pixel that received N samples with luminances x_i, from the sums
803 
804  S1 = sum(x_i) and S2 = sum(x_i^2)
805 
806 the variance of one sample and the standard error of the pixel, which is the
807 mean of those samples, are
808 
809  s^2 = (S2 - S1^2 / N) / (N - 1)
810  SE = sqrt(s^2 / N)
811 
812 SE is in the units of the pixel, so it is divided by the pixel to give a
813 relative standard error that can be compared between scenes, exposures and
814 resolutions. noiseRSE is the mean of that over the image, noiseRSE999 the
815 99.9th percentile, which is the figure fireflies move.
816 
817 A Monte Carlo estimator converges with 1/sqrt(N), so noiseRSE has to fall by
818 half when the samples per pixel are quadrupled. That makes it a check on the
819 estimators in trace() and shade() as much as a readout: if it does not halve,
820 something upstream is wrong.
821 */
823 {
824  const size_t numPixels = _lumSum.size();
825 
826  // The variance of a sample needs at least two samples to exist.
827  if (numPixels == 0 || _aaSamples < 2)
828  {
829  _noiseRSE = 0.0f;
830  _noiseRSE999 = 0.0f;
831  return;
832  }
833 
834  const SLdouble n = (SLdouble)_aaSamples;
835 
836  // Keeps a black pixel, where mu is 0 and no number of samples is going to
837  // change that, from dividing by zero.
838  const SLdouble eps = 1e-3;
839 
840  vector<SLfloat> rse(numPixels);
841  SLdouble sum = 0.0;
842 
843  for (size_t i = 0; i < numPixels; ++i)
844  {
845  const SLdouble s1 = _lumSum[i];
846  const SLdouble s2 = _lumSumSq[i];
847  const SLdouble mu = s1 / n;
848 
849  SLdouble variance = (s2 - s1 * s1 / n) / (n - 1.0);
850 
851  // A pixel that got the same value every time, a background pixel for
852  // instance, has a true variance of 0 and the subtraction above can land
853  // just below it. Without this the sqrt would return a NaN.
854  if (variance < 0.0)
855  variance = 0.0;
856 
857  rse[i] = (SLfloat)(sqrt(variance / n) / (mu + eps));
858  sum += (SLdouble)rse[i];
859  }
860 
861  _noiseRSE = (SLfloat)(sum / (SLdouble)numPixels);
862 
863  // nth_element partitions around the wanted rank instead of sorting the
864  // whole image, which is O(numPixels) rather than O(numPixels log numPixels)
865  // and is all a percentile needs.
866  size_t k = (size_t)(0.999 * (SLdouble)numPixels);
867  if (k >= numPixels)
868  k = numPixels - 1;
869  std::nth_element(rse.begin(),
870  rse.begin() + (std::ptrdiff_t)k,
871  rse.end());
872  _noiseRSE999 = rse[k];
873 }
874 //-----------------------------------------------------------------------------
875 //! Saves the current PT image as PNG image
877 {
878  static SLint no = 0;
879  SLchar filename[255];
880  snprintf(filename,
881  sizeof(filename),
882  "Pathtraced_%d_%d.png",
883  _aaSamples,
884  no++);
885  _images[0]->savePNG(filename);
886 }
887 //-----------------------------------------------------------------------------
cv::Vec4f CVVec4f
Definition: CVTypedefs.h:54
#define PROFILE_FUNCTION()
Definition: Instrumentor.h:41
#define PROFILE_THREAD(name)
Definition: Profiler.h:38
float SLfloat
analog to GLfloat
Definition: SL.h:200
#define SL_LOG(...)
Some debugging and error handling macros.
Definition: SL.h:279
unsigned int SLuint
analog to GLuint
Definition: SL.h:198
char SLchar
analog to GLchar (char is signed [-128 ... 127]!)
Definition: SL.h:189
double SLdouble
analog to GLdouble
Definition: SL.h:201
bool SLbool
analog to GLbool
Definition: SL.h:202
int SLint
analog to GLint
Definition: SL.h:197
SLSceneView * sv
Definition: SLGLImGui.h:28
static SLfloat misWeight(SLfloat pdfThis, SLfloat pdfOther)
Power heuristic (beta = 2) weight of the strategy whose density is pdfThis.
static SLfloat russianRoulette(SLint depth, SLCol4f albedo)
Russian roulette survival test for the continuation of a path.
static const SLfloat PDF_NO_MIS
Sentinel pdf for SLPathtracer::trace.
static const SLint RR_START_DEPTH
Path depth up to which Russian roulette always lets the path continue.
static SLfloat lightPdfMC(SLRay *ray)
Solid angle density with which shade() would have sampled this light hit.
static SLfloat phongLobeWeight(SLfloat exponent, const SLVec3f &sampleDir, const SLVec3f &normal)
Weight of one sample drawn from the Phong lobe of the given exponent.
SLfloat rnd01()
Definition: SLRay.cpp:50
@ rtBusy
Definition: SLRaytracer.h:30
@ rtFinished
Definition: SLRaytracer.h:31
static float timeS()
Definition: GlobalTimer.cpp:20
SLuint height()
Definition: SLGLTexture.h:219
SLuint width()
Definition: SLGLTexture.h:218
CVVImage _images
Vector of CVImage pointers.
Definition: SLGLTexture.h:302
std::mutex _mutex
Mutex to protect parallel access (used in ray tracing)
Definition: SLGLTexture.h:324
Abstract Light class for OpenGL light sources.
Definition: SLLight.h:61
Light node class for a rectangular light source.
Definition: SLLightRect.h:39
Defines a standard CG material with textures and a shader program.
Definition: SLMaterial.h:56
void translucency(SLfloat transl)
Definition: SLMaterial.h:176
static SLfloat PERFECT
PM: shininess/translucency limit.
Definition: SLMaterial.h:238
void specular(const SLCol4f &spec)
Definition: SLMaterial.h:173
void diffuse(const SLCol4f &diff)
Definition: SLMaterial.h:171
SLuint numTextures()
Definition: SLMaterial.h:226
void shininess(SLfloat shin)
Definition: SLMaterial.h:177
void transmissive(const SLCol4f &transm)
Definition: SLMaterial.h:175
void emissive(const SLCol4f &emis)
Definition: SLMaterial.h:174
void kn(SLfloat kn)
Definition: SLMaterial.h:199
SLMaterial * mat() const
Definition: SLMesh.h:177
virtual void preShade(SLRay *ray)
Definition: SLMesh.cpp:1470
SLNode represents a node in a hierarchical scene graph.
Definition: SLNode.h:148
virtual bool hitRec(SLRay *ray)
Definition: SLNode.cpp:508
const SLstring & name() const
Definition: SLObject.h:38
SLbool _calcIndirect
flag to calculate indirect illumination
Definition: SLPathtracer.h:95
SLCol4f shade(SLRay *ray, SLCol4f *mat)
SLbool render(SLSceneView *sv)
void computeNoise()
vector< SLdouble > _lumSumSq
Definition: SLPathtracer.h:153
SLbool _calcDirect
flag to calculate direct illumination
Definition: SLPathtracer.h:94
vector< SLCol4f > _radianceSum
Linear, unclamped sum of all radiance samples taken so far per pixel.
Definition: SLPathtracer.h:136
SLfloat _noiseRSE
mean relative standard error, see noiseRSE()
Definition: SLPathtracer.h:155
void renderSlices(bool isMainThread, SLint currentSample, SLuint threadNum)
void saveImage()
Saves the current PT image as PNG image.
SLfloat _sampleClamp
Upper limit on the radiance of a single sample, 0 to switch it off.
Definition: SLPathtracer.h:123
function< void(bool, int, SLuint)> renderSlicesPTAsync
Definition: SLPathtracer.h:92
SLfloat _noiseRSE999
the same for the worst 0.1%, see noiseRSE999()
Definition: SLPathtracer.h:156
SLCol4f trace(SLRay *ray, SLfloat bsdfPdf)
Traces one ray. bsdfPdf is the solid angle density with which the.
vector< SLdouble > _lumSum
Sum and sum of squares of the luminance of every sample, per pixel.
Definition: SLPathtracer.h:152
Ray class with ray and intersection properties.
Definition: SLRay.h:40
SLbool hitMatIsTransparent() const
Returns true if the hit material transmission color is not black.
Definition: SLRay.h:208
bool reflectMC(SLRay *reflected, const SLMat3f &rotMat) const
Definition: SLRay.cpp:413
SLCol4f backgroundColor
Background color at pixel x,y.
Definition: SLRay.h:103
SLMesh * hitMesh
Points to the intersected mesh.
Definition: SLRay.h:109
SLbool isOutside
Flag if ray is inside of a material.
Definition: SLRay.h:98
SLint depth
Recursion depth for ray tracing.
Definition: SLRay.h:81
SLVec3f dir
Direction vector of ray in WS.
Definition: SLRay.h:79
void refract(SLRay *refracted)
Definition: SLRay.cpp:218
static SLMat3f lobeToWorld(const SLVec3f &lobeAxis)
Rotation matrix that maps a sample drawn around +z onto lobeAxis.
Definition: SLRay.cpp:372
SLbool hitMatIsReflective() const
Returns true if the hit material specular color is not black.
Definition: SLRay.h:197
SLfloat length
length from origin to an intersection
Definition: SLRay.h:80
SLNode * hitNode
Points to the intersected node.
Definition: SLRay.h:108
SLVec3f hitPoint
Point of intersection.
Definition: SLRay.h:113
void diffuseMC(SLRay *scattered) const
Definition: SLRay.cpp:542
static SLuint totalNumRays()
Total NO. of rays shot during RT.
Definition: SLRay.h:87
bool refractMC(SLRay *refracted, const SLMat3f &rotMat) const
Definition: SLRay.cpp:478
void reflect(SLRay *reflected) const
Definition: SLRay.cpp:177
SLCol4f hitTexColor
Color at intersection for texture or color attributes.
Definition: SLRay.h:115
SLVec3f hitNormal
Surface normal at intersection point.
Definition: SLRay.h:114
SLbool hitMatIsDiffuse() const
Returns true if the hit material diffuse color is not black.
Definition: SLRay.h:219
SLint maxDepth() const
Definition: SLRaytracer.h:115
SLfloat _oneOverGamma
one over gamma correction value
Definition: SLRaytracer.h:163
virtual void prepareImage()
SLSceneView * _sv
Parent sceneview.
Definition: SLRaytracer.h:144
SLfloat gamma() const
Definition: SLRaytracer.h:124
SLint _nextLine
next line index to render RT in a thread
Definition: SLRaytracer.h:160
SLRTState _state
RT state;.
Definition: SLRaytracer.h:145
SLfloat _renderSec
Rendering time in seconds.
Definition: SLRaytracer.h:153
SLint _aaSamples
SQRT of uneven num. of AA samples.
Definition: SLRaytracer.h:167
void renderUIBeforeUpdate()
Must be called before an inbetween frame updateRec.
void setPrimaryRay(SLfloat x, SLfloat y, SLRay *primaryRay)
Set the parameters of a primary ray for a pixel position at x, y.
virtual void initStats(SLint depth)
SLint _progressPC
progress in %
Definition: SLRaytracer.h:152
AvgFloat _raysPerMS
Rays per ms of the last completed render.
Definition: SLRaytracer.h:154
SLVLight & lights()
Definition: SLScene.h:107
void root3D(SLNode *root3D)
Definition: SLScene.h:78
SceneView class represents a dynamic real time 3D view onto the scene.
Definition: SLSceneView.h:69
cbOnWndUpdate onWndUpdate
C-Callback for app for intermediate window repaint.
Definition: SLSceneView.h:145
SLScene * s()
Definition: SLSceneView.h:171
SLVec3 & normalize()
Definition: SLVec3.h:124
T length() const
Definition: SLVec3.h:122
T dot(const SLVec3 &v) const
Definition: SLVec3.h:117
T lengthSqr() const
Definition: SLVec3.h:123
static SLVec4 BLACK
Definition: SLVec4.h:213
T g
Definition: SLVec4.h:33
T b
Definition: SLVec4.h:33
T maxXYZ()
Definition: SLVec4.h:141
void gammaCorrect(T oneOverGamma)
Gamma correction.
Definition: SLVec4.h:163
T a
Definition: SLVec4.h:33
T r
Definition: SLVec4.h:33
void clampMinMax(const T min, const T max)
Definition: SLVec4.h:119
void set(T value)
Sets the current value in the value array and builds the average.
Definition: Averaged.h:53
T abs(T a)
Definition: Utils.h:249
static const float ONEOVERPI
Definition: Utils.h:241
unsigned int maxThreads()
Returns in release config the max. NO. of threads otherwise 1.
Definition: Utils.cpp:1188