SLProject  4.3.020
A platform independent 3D computer graphics framework for desktop OS, Android, iOS and online in web browsers
SLPathtracer Class Reference

Classic Monte Carlo Pathtracing algorithm for real global illumination. More...

#include <SLPathtracer.h>

Inheritance diagram for SLPathtracer:
[legend]

Public Member Functions

 SLPathtracer ()
 
 ~SLPathtracer ()
 
SLbool render (SLSceneView *sv)
 
void renderSlices (bool isMainThread, SLint currentSample, SLuint threadNum)
 
SLCol4f trace (SLRay *ray, SLfloat bsdfPdf)
 Traces one ray. bsdfPdf is the solid angle density with which the. More...
 
SLCol4f shade (SLRay *ray, SLCol4f *mat)
 
void saveImage ()
 Saves the current PT image as PNG image. More...
 
void computeNoise ()
 
void calcDirect (SLbool di)
 
void calcIndirect (SLbool ii)
 
void sampleClamp (SLfloat max)
 
SLbool calcDirect () const
 
SLbool calcIndirect () const
 
SLfloat sampleClamp () const
 
SLfloat noiseRSE () const
 Mean relative standard error of the pixels of the last render. More...
 
SLfloat noiseRSE999 () const
 Relative standard error of the noisiest 0.1% of the pixels. More...
 
SLfloat efficiency () const
 Monte Carlo efficiency, the inverse of variance times time. More...
 
- Public Member Functions inherited from SLRaytracer
 SLRaytracer ()
 
 ~SLRaytracer () override
 
SLbool renderClassic (SLSceneView *sv)
 
SLbool renderDistrib (SLSceneView *sv)
 
void renderSlices (bool isMainThread, SLuint threadNum)
 
void renderSlicesMS (bool isMainThread, SLuint threadNum)
 
SLCol4f trace (SLRay *ray)
 
SLCol4f shade (SLRay *ray)
 
void sampleAAPixels (bool isMainThread, SLuint threadNum)
 
void renderUIBeforeUpdate ()
 Must be called before an inbetween frame updateRec. More...
 
void setPrimaryRay (SLfloat x, SLfloat y, SLRay *primaryRay)
 Set the parameters of a primary ray for a pixel position at x, y. More...
 
void getAAPixels ()
 
SLCol4f fogBlend (SLfloat z, SLCol4f color)
 
virtual void printStats (SLfloat sec)
 
virtual void initStats (SLint depth)
 
void state (SLRTState state)
 
void maxDepth (SLint depth)
 
void resolutionFactor (SLfloat rf)
 
void doDistributed (SLbool distrib)
 
void doContinuous (SLbool cont)
 
void doFresnel (SLbool fresnel)
 
void aaSamples (SLint samples)
 
void gamma (SLfloat g)
 
SLRTState state () const
 
SLint maxDepth () const
 
SLbool doDistributed () const
 
SLbool doContinuous () const
 
SLbool doFresnel () const
 
SLint aaSamples () const
 
SLint progressPC () const
 
SLfloat aaThreshold () const
 
SLfloat renderSec () const
 
SLfloat gamma () const
 
SLfloat oneOverGamma () const
 
SLfloat resolutionFactor () const
 
SLint resolutionFactorPC () const
 
SLfloat raysPerMS ()
 Rays per ms of the last completed render, for comparing machines. More...
 
virtual void prepareImage ()
 
virtual void renderImage (bool updateTextureGL)
 
- Public Member Functions inherited from SLGLTexture
 SLGLTexture ()
 Default ctor for all stack instances (not created with new) More...
 
 SLGLTexture (SLAssetManager *assetMgr, const SLVCol4f &colors, SLint min_filter=GL_LINEAR, SLint mag_filter=GL_LINEAR, SLint wrapS=GL_REPEAT, const SLstring &name="2D-Texture")
 ctor for 1D texture with internal image allocation More...
 
 SLGLTexture (SLAssetManager *assetMgr, SLint min_filter, SLint mag_filter, SLint wrapS, SLint wrapT, SLenum target=GL_TEXTURE_2D)
 ctor for empty 2D textures More...
 
 SLGLTexture (SLAssetManager *assetMgr, unsigned char *data, int width, int height, int cvtype, SLint min_filter, SLint mag_filter, SLTextureType type, SLint wrapS, SLint wrapT)
 ctor for 2D textures from byte pointer More...
 
 SLGLTexture (SLAssetManager *assetMgr, const SLstring &imageFilename, SLint min_filter=GL_LINEAR_MIPMAP_LINEAR, SLint mag_filter=GL_LINEAR, SLTextureType type=TT_unknown, SLint wrapS=GL_REPEAT, SLint wrapT=GL_REPEAT)
 ctor for 2D textures with internal image allocation More...
 
 SLGLTexture (SLAssetManager *assetMgr, const SLVstring &imageFilenames, SLint min_filter=GL_LINEAR, SLint mag_filter=GL_LINEAR, SLint wrapS=GL_REPEAT, SLint wrapT=GL_REPEAT, const SLstring &name="3D-Texture", SLbool loadGrayscaleIntoAlpha=false)
 ctor for 3D texture with internal image allocation More...
 
 SLGLTexture (SLAssetManager *assetMgr, SLint depth, const SLstring &imageFilename, SLint min_filter=GL_LINEAR, SLint mag_filter=GL_LINEAR, SLint wrapS=GL_REPEAT, SLint wrapT=GL_REPEAT, const SLstring &name="3D-Texture", SLbool loadGrayscaleIntoAlpha=false)
 ctor for 3D texture from a single file with depth as 3rd dimension More...
 
 SLGLTexture (SLAssetManager *assetMgr, const SLstring &imageFilenameXPos, const SLstring &imageFilenameXNeg, const SLstring &imageFilenameYPos, const SLstring &imageFilenameYNeg, const SLstring &imageFilenameZPos, const SLstring &imageFilenameZNeg, SLint min_filter=GL_LINEAR, SLint mag_filter=GL_LINEAR, SLTextureType type=TT_unknown)
 ctor for cube mapping with internal image allocation More...
 
 ~SLGLTexture () override
 
virtual void build (SLint texUnit)
 
void deleteData (SLbool deleteAlsoOnGPU)
 Delete all data (CVImages and GPU textures) More...
 
void deleteDataGpu ()
 Deletes the OpenGL texture objects and releases the memory on the GPU. More...
 
void deleteImages ()
 Deletes the CVImages in _images. No more texture mapping in ray tracing. More...
 
void bindActive (SLuint texUnit=0)
 
void fullUpdate ()
 
void drawSprite (SLbool doUpdate, SLfloat x, SLfloat y, SLfloat w, SLfloat h)
 Draws the texture as 2D sprite with OpenGL buffers. More...
 
void cubeUV2XYZ (SLint index, SLfloat u, SLfloat v, SLfloat &x, SLfloat &y, SLfloat &z)
 Computes the unnormalised vector x,y,z from tex. coords. uv with cubemap index. More...
 
void cubeXYZ2UV (SLfloat x, SLfloat y, SLfloat z, SLint &index, SLfloat &u, SLfloat &v)
 Computes the uv and cubemap image index from a unnormalised vector x,y,z. More...
 
SLstring filterString (SLint glFilter)
 Returns OpenGL texture filter as string. More...
 
void texType (SLTextureType bt)
 
void uvIndex (SLbyte i)
 
void bumpScale (SLfloat bs)
 
void minFiler (SLint minF)
 
void magFiler (SLint magF)
 
void needsUpdate (SLbool update)
 
void textureSize (int width, int height)
 
void deleteImageAfterBuild (SLbool delImg)
 If deleteImageAfterBuild is set to true you won't be able to ray trace the scene. More...
 
SLuint width ()
 
SLuint height ()
 
SLuint depth ()
 
SLbyte uvIndex ()
 
SLint bytesPerPixel ()
 
SLint bytesOnGPU ()
 
SLint bytesInFile ()
 
CVVImageimages ()
 
SLenum target () const
 
SLuint texID () const
 
SLTextureType texType ()
 
SLfloat bumpScale () const
 
SLCol4f getTexelf (SLfloat u, SLfloat v, SLuint imgIndex=0)
 SLGLTexture::getTexelf returns a pixel color from u & v texture coordinates. More...
 
SLCol4f getTexelf (const SLVec3f &cubemapDir)
 SLGLTexture::getTexelf returns a pixel color at the specified cubemap direction. More...
 
SLbool hasAlpha ()
 
SLMat4f tm ()
 
SLbool autoCalcTM3D () const
 
SLbool needsUpdate ()
 
SLstring typeName ()
 Returns the texture type as string. More...
 
SLstring typeShortName ()
 Returns the texture type short. More...
 
bool isTexture ()
 
SLstring minificationFilterName ()
 
SLstring magnificationFilterName ()
 
void build2DMipmaps (SLint target, SLuint index)
 
SLbool copyVideoImage (SLint camWidth, SLint camHeight, CVPixelFormatGL glFormat, SLuchar *data, SLbool isContinuous, SLbool isTopLeft)
 Copies the image data from a video camera into the current video image. More...
 
SLbool copyVideoImage (SLint camWidth, SLint camHeight, CVPixelFormatGL srcFormat, CVPixelFormatGL dstFormat, SLuchar *data, SLbool isContinuous, SLbool isTopLeft)
 
void calc3DGradients (SLint sampleRadius, const function< void(int)> &onUpdateProgress=nullptr)
 
void smooth3DGradients (SLint smoothRadius, function< void(int)> onUpdateProgress=nullptr)
 
SLVec2f dudv (SLfloat u, SLfloat v)
 
- Public Member Functions inherited from SLObject
 SLObject (const SLstring &Name="", const SLstring &url="")
 
virtual ~SLObject ()
 
void name (const SLstring &Name)
 
void url (const SLstring &url)
 
const SLstringname () const
 
const SLstringurl () const
 

Private Attributes

function< void(bool, int, SLuint)> renderSlicesPTAsync
 
SLbool _calcDirect
 flag to calculate direct illumination More...
 
SLbool _calcIndirect
 flag to calculate indirect illumination More...
 
SLfloat _sampleClamp
 Upper limit on the radiance of a single sample, 0 to switch it off. More...
 
vector< SLCol4f_radianceSum
 Linear, unclamped sum of all radiance samples taken so far per pixel. More...
 
vector< SLdouble_lumSum
 Sum and sum of squares of the luminance of every sample, per pixel. More...
 
vector< SLdouble_lumSumSq
 
SLfloat _noiseRSE
 mean relative standard error, see noiseRSE() More...
 
SLfloat _noiseRSE999
 the same for the worst 0.1%, see noiseRSE999() More...
 

Additional Inherited Members

- Static Public Member Functions inherited from SLRaytracer
static SLuint numThreads ()
 
- Static Public Member Functions inherited from SLGLTexture
static SLTextureType detectType (const SLstring &filename)
 Detects the texture type from the filename appendix (See SLTexType def.) More...
 
static string internalFormatStr (int internalFormat)
 Returns the internal pixel format from OpenGL. More...
 
- Static Public Attributes inherited from SLGLTexture
static SLfloat maxAnisotropy = -1.0f
 Returns the derivation as [s,t]. More...
 
static SLuint totalNumBytesOnGPU = 0
 Total NO. of bytes used for textures on GPU. More...
 
- Protected Member Functions inherited from SLGLTexture
void load (const SLstring &filename, SLbool flipVertical=true, SLbool loadGrayscaleIntoAlpha=false)
 Loads the texture, converts color depth & applies vertical mirroring. More...
 
void load (const SLVCol4f &colors)
 Loads the 1D color data into an image of height 1. More...
 
- Protected Attributes inherited from SLRaytracer
function< void(bool, SLuint)> renderSlicesAsync
 
function< void(bool, SLuint)> sampleAAPixelsAsync
 
SLSceneView_sv
 Parent sceneview. More...
 
SLRTState _state
 RT state;. More...
 
SLCamera_cam
 shortcut to the camera More...
 
SLfloat _resolutionFactor
 screen to RT image size factor (default 1.0) More...
 
SLint _maxDepth
 Max. allowed recursion depth. More...
 
SLbool _doContinuous
 if true state goes into ready again More...
 
SLbool _doDistributed
 Flag for parallel distributed RT. More...
 
SLbool _doFresnel
 Flag for Fresnel reflection. More...
 
SLint _progressPC
 progress in % More...
 
SLfloat _renderSec
 Rendering time in seconds. More...
 
AvgFloat _raysPerMS
 Rays per ms of the last completed render. More...
 
SLfloat _pxSize
 Pixel size. More...
 
SLVec3f _eye
 Camera position. More...
 
SLVec3f _la
 
SLVec3f _lu
 
SLVec3f _lr
 Camera lookat, lookup, lookright. More...
 
SLVec3f _bl
 Bottom left vector. More...
 
SLint _nextLine
 next line index to render RT in a thread More...
 
SLVPixel _aaPixels
 Vector for antialiasing pixels. More...
 
SLfloat _gamma
 gamma correction value More...
 
SLfloat _oneOverGamma
 one over gamma correction value More...
 
SLfloat _aaThreshold
 threshold for anti aliasing More...
 
SLint _aaSamples
 SQRT of uneven num. of AA samples. More...
 
- Protected Attributes inherited from SLGLTexture
CVVImage _images
 Vector of CVImage pointers. More...
 
SLuint _texID
 OpenGL texture ID. More...
 
SLTextureType _texType
 See SLTextureType. More...
 
SLint _width
 Texture image width in pixels (images exist either in _images or on the GPU or on both) More...
 
SLint _height
 Texture image height in pixels (images exist either in _images or on the GPU or on both) More...
 
SLint _depth
 3D Texture image depth (images exist either in _images or on the GPU or on both) More...
 
SLbyte _uvIndex
 Texture coordinate index in SLMesh (0 = default) More...
 
SLint _internalFormat
 Internal OpenGL format. More...
 
SLint _bytesPerPixel
 Bytes per texture image pixel (images exist either in _images or on the GPU or on both) More...
 
SLint _min_filter
 Minification filter. More...
 
SLint _mag_filter
 Magnification filter. More...
 
SLint _wrap_s
 Wrapping in s direction. More...
 
SLint _wrap_t
 Wrapping in t direction. More...
 
SLenum _target
 texture target More...
 
SLMat4f _tm
 texture matrix More...
 
SLuint _bytesOnGPU
 NO. of bytes on GPU. More...
 
SLuint _bytesInFile
 NO. of bytes in file. More...
 
SLbool _autoCalcTM3D
 Flag if texture matrix should be calculated from AABB for 3D mapping. More...
 
SLfloat _bumpScale
 Bump mapping scale factor. More...
 
SLbool _resizeToPow2
 Flag if image should be resized to n^2. More...
 
SLGLVertexArray _vaoSprite
 Vertex array object for sprite rendering. More...
 
std::atomic< bool > _needsUpdate {}
 Flag if image needs an single update. More...
 
std::mutex _mutex
 Mutex to protect parallel access (used in ray tracing) More...
 
SLbool _deleteImageAfterBuild
 Flag if images should be deleted after build on GPU. More...
 
SLbool _compressedTexture = false
 True for compressed texture format on GPU. More...
 
- Protected Attributes inherited from SLObject
SLstring _name
 name of an object More...
 
SLstring _url
 uniform resource locator More...
 

Detailed Description

Classic Monte Carlo Pathtracing algorithm for real global illumination.

Definition at line 17 of file SLPathtracer.h.

Constructor & Destructor Documentation

◆ SLPathtracer()

SLPathtracer::SLPathtracer ( )

Definition at line 184 of file SLPathtracer.cpp.

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 }
const SLstring & name() const
Definition: SLObject.h:38
SLbool _calcIndirect
flag to calculate indirect illumination
Definition: SLPathtracer.h:95
SLbool _calcDirect
flag to calculate direct illumination
Definition: SLPathtracer.h:94
SLfloat _noiseRSE
mean relative standard error, see noiseRSE()
Definition: SLPathtracer.h:155
SLfloat _sampleClamp
Upper limit on the radiance of a single sample, 0 to switch it off.
Definition: SLPathtracer.h:123
SLfloat _noiseRSE999
the same for the worst 0.1%, see noiseRSE999()
Definition: SLPathtracer.h:156
SLfloat gamma() const
Definition: SLRaytracer.h:124

◆ ~SLPathtracer()

SLPathtracer::~SLPathtracer ( )
inline

Definition at line 21 of file SLPathtracer.h.

21 { SL_LOG("Destructor : ~SLPathtracer"); }
#define SL_LOG(...)
Some debugging and error handling macros.
Definition: SL.h:279

Member Function Documentation

◆ calcDirect() [1/2]

SLbool SLPathtracer::calcDirect ( ) const
inline

Definition at line 44 of file SLPathtracer.h.

44 { return _calcDirect; }

◆ calcDirect() [2/2]

void SLPathtracer::calcDirect ( SLbool  di)
inline

Definition at line 39 of file SLPathtracer.h.

39 { _calcDirect = di; }

◆ calcIndirect() [1/2]

SLbool SLPathtracer::calcIndirect ( ) const
inline

Definition at line 45 of file SLPathtracer.h.

45 { return _calcIndirect; }

◆ calcIndirect() [2/2]

void SLPathtracer::calcIndirect ( SLbool  ii)
inline

Definition at line 40 of file SLPathtracer.h.

40 { _calcIndirect = ii; }

◆ computeNoise()

void SLPathtracer::computeNoise ( )

Turns the two luminance moments that renderSlices accumulated into the noise figures of the Timing panel. Called once, after the last sample pass.

For a pixel that received N samples with luminances x_i, from the sums

S1 = sum(x_i)  and  S2 = sum(x_i^2)

the variance of one sample and the standard error of the pixel, which is the mean of those samples, are

s^2 = (S2 - S1^2 / N) / (N - 1)
SE  = sqrt(s^2 / N)

SE is in the units of the pixel, so it is divided by the pixel to give a relative standard error that can be compared between scenes, exposures and resolutions. noiseRSE is the mean of that over the image, noiseRSE999 the 99.9th percentile, which is the figure fireflies move.

A Monte Carlo estimator converges with 1/sqrt(N), so noiseRSE has to fall by half when the samples per pixel are quadrupled. That makes it a check on the estimators in trace() and shade() as much as a readout: if it does not halve, something upstream is wrong.

Definition at line 822 of file SLPathtracer.cpp.

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 }
float SLfloat
analog to GLfloat
Definition: SL.h:200
double SLdouble
analog to GLdouble
Definition: SL.h:201
vector< SLdouble > _lumSumSq
Definition: SLPathtracer.h:153
vector< SLdouble > _lumSum
Sum and sum of squares of the luminance of every sample, per pixel.
Definition: SLPathtracer.h:152
SLint _aaSamples
SQRT of uneven num. of AA samples.
Definition: SLRaytracer.h:167

◆ efficiency()

SLfloat SLPathtracer::efficiency ( ) const
inline

Monte Carlo efficiency, the inverse of variance times time.

The figure to compare two renderers or two settings with, because it is the only one that stays fair when a change alters what a sample costs, as Russian roulette does. It does not depend on the sample count: the variance of a pixel falls as 1/N while the render time grows as N, so their product is constant in N and two renders at different samples per pixel can be compared directly. Higher is better.

Definition at line 84 of file SLPathtracer.h.

85  {
86  return _noiseRSE > 0.0f && _renderSec > 0.0f
87  ? 1.0f / (_noiseRSE * _noiseRSE * _renderSec)
88  : 0.0f;
89  }
SLfloat _renderSec
Rendering time in seconds.
Definition: SLRaytracer.h:153

◆ noiseRSE()

SLfloat SLPathtracer::noiseRSE ( ) const
inline

Mean relative standard error of the pixels of the last render.

How far the average pixel is expected to sit from the value this estimator converges to, as a fraction of the pixel itself. It is a measure of the noise only: it says nothing about the bias that sampleClamp adds, and it falls monotonically as the clamp tightens. See computeNoise.

Definition at line 53 of file SLPathtracer.h.

53 { return _noiseRSE; }

◆ noiseRSE999()

SLfloat SLPathtracer::noiseRSE999 ( ) const
inline

Relative standard error of the noisiest 0.1% of the pixels.

The figure to watch for fireflies. A firefly covers a handful of pixels out of hundreds of thousands, so it barely moves noiseRSE, but it dominates this one.

It has a floor that comes straight from its definition: it reports the pixel at rank 99.9%, so it only moves once the fireflies reach more than 0.1% of the image. Measured on synthetic fireflies at 640x360 and 1000 spp, with the outliers 3000 times an ordinary sample:

firefly pixels noiseRSE noiseRSE999 0.010% 0.00320 0.00338 0.100% 0.00370 0.00342 still an ordinary pixel 0.200% 0.00421 0.60547 5.000% 0.02910 0.75061

Below that threshold this figure reads as if the image were clean and only noiseRSE moves, by very little. It is the right figure for the Muttenzer Box, whose fireflies cover about 4% of the far wall (see _sampleClamp), but a scene with rarer ones needs a higher percentile or the maximum.

Definition at line 75 of file SLPathtracer.h.

75 { return _noiseRSE999; }

◆ render()

SLbool SLPathtracer::render ( SLSceneView sv)

Main render function. The Path Tracing algorithm starts from here

Definition at line 198 of file SLPathtracer.cpp.

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 }
unsigned int SLuint
analog to GLuint
Definition: SL.h:198
int SLint
analog to GLint
Definition: SL.h:197
SLSceneView * sv
Definition: SLGLImGui.h:28
@ 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
void computeNoise()
vector< SLCol4f > _radianceSum
Linear, unclamped sum of all radiance samples taken so far per pixel.
Definition: SLPathtracer.h:136
void renderSlices(bool isMainThread, SLint currentSample, SLuint threadNum)
function< void(bool, int, SLuint)> renderSlicesPTAsync
Definition: SLPathtracer.h:92
static SLuint totalNumRays()
Total NO. of rays shot during RT.
Definition: SLRay.h:87
virtual void prepareImage()
SLSceneView * _sv
Parent sceneview.
Definition: SLRaytracer.h:144
SLint _nextLine
next line index to render RT in a thread
Definition: SLRaytracer.h:160
SLRTState _state
RT state;.
Definition: SLRaytracer.h:145
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
static SLVec4 BLACK
Definition: SLVec4.h:213
void set(T value)
Sets the current value in the value array and builds the average.
Definition: Averaged.h:53
unsigned int maxThreads()
Returns in release config the max. NO. of threads otherwise 1.
Definition: Utils.cpp:1188

◆ renderSlices()

void SLPathtracer::renderSlices ( bool  isMainThread,
SLint  currentSample,
SLuint  threadNum 
)

Renders a slice of 4px width.

Definition at line 275 of file SLPathtracer.cpp.

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);
367  color.gammaCorrect(_oneOverGamma);
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 }
cv::Vec4f CVVec4f
Definition: CVTypedefs.h:54
#define PROFILE_FUNCTION()
Definition: Instrumentor.h:41
#define PROFILE_THREAD(name)
Definition: Profiler.h:38
static const SLfloat PDF_NO_MIS
Sentinel pdf for SLPathtracer::trace.
SLfloat rnd01()
Definition: SLRay.cpp:50
std::mutex _mutex
Mutex to protect parallel access (used in ray tracing)
Definition: SLGLTexture.h:324
SLCol4f trace(SLRay *ray, SLfloat bsdfPdf)
Traces one ray. bsdfPdf is the solid angle density with which the.
Ray class with ray and intersection properties.
Definition: SLRay.h:40
SLfloat _oneOverGamma
one over gamma correction value
Definition: SLRaytracer.h:163
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.
cbOnWndUpdate onWndUpdate
C-Callback for app for intermediate window repaint.
Definition: SLSceneView.h:145

◆ sampleClamp() [1/2]

SLfloat SLPathtracer::sampleClamp ( ) const
inline

Definition at line 46 of file SLPathtracer.h.

46 { return _sampleClamp; }

◆ sampleClamp() [2/2]

void SLPathtracer::sampleClamp ( SLfloat  max)
inline

Definition at line 41 of file SLPathtracer.h.

41 { _sampleClamp = max; }

◆ saveImage()

void SLPathtracer::saveImage ( )
virtual

Saves the current PT image as PNG image.

Reimplemented from SLRaytracer.

Definition at line 876 of file SLPathtracer.cpp.

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 }
char SLchar
analog to GLchar (char is signed [-128 ... 127]!)
Definition: SL.h:189

◆ shade()

SLCol4f SLPathtracer::shade ( SLRay ray,
SLCol4f mat 
)

Calculates the direct illumination at the hit point of the ray by sampling the light sources explicitly (next event estimation).

A rectangular light is an AREA light and is estimated with a Monte Carlo area estimator: one point is sampled uniformly on the rectangle (pdf = 1/area) and the radiance reflected towards the ray is

Lo = albedo/PI * Le * cosSurface * cosLight / dist^2 * area

with the direction, the distance and both cosines taken at the SAMPLED point.

The previous version took all of them at the light CENTRE while it tested the visibility at a random point, and it replaced the geometric term by the OpenGL attenuation and the spot cone exponent. That is the Blinn-Phong rasteriser model, not an estimator of the area light integral. It missed the area factor completely, which made the direct light 1/area too bright (1.54x for the 1.0 x 0.65 light of the Muttenzer Box), and it broke down for surfaces close to a large light. Above all it put the direct illumination on a different scale than the emissive material of the light mesh that the paths see when they hit the light through the mirror or the glass sphere, so the two estimates of the same illumination did not agree.

All other light types are point or directional (delta) lights without any area. For those the classic attenuation and spot cone model is kept.

Definition at line 696 of file SLPathtracer.cpp.

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 }
static SLfloat misWeight(SLfloat pdfThis, SLfloat pdfOther)
Power heuristic (beta = 2) weight of the strategy whose density is pdfThis.
Light node class for a rectangular light source.
Definition: SLLightRect.h:39
SLNode represents a node in a hierarchical scene graph.
Definition: SLNode.h:148
virtual bool hitRec(SLRay *ray)
Definition: SLNode.cpp:508
SLVec3f hitPoint
Point of intersection.
Definition: SLRay.h:113
SLVec3f hitNormal
Surface normal at intersection point.
Definition: SLRay.h:114
SLVLight & lights()
Definition: SLScene.h:107
void root3D(SLNode *root3D)
Definition: SLScene.h:78
SLScene * s()
Definition: SLSceneView.h:171
static const float ONEOVERPI
Definition: Utils.h:241

◆ trace()

SLCol4f SLPathtracer::trace ( SLRay ray,
SLfloat  bsdfPdf 
)

Traces one ray. bsdfPdf is the solid angle density with which the.

scattering at the previous vertex produced this ray, or the negative sentinel PDF_NO_MIS if next event estimation cannot generate the same path (the primary ray, and every specular or transmissive bounce). It is what lets a light hit be weighted against the light sampling in shade().

Recursively traces ray in scene.

Definition at line 395 of file SLPathtracer.cpp.

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 }
bool SLbool
analog to GLbool
Definition: SL.h:202
static SLfloat russianRoulette(SLint depth, SLCol4f albedo)
Russian roulette survival test for the continuation of a path.
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.
Abstract Light class for OpenGL light sources.
Definition: SLLight.h:61
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
SLCol4f shade(SLRay *ray, SLCol4f *mat)
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
void diffuseMC(SLRay *scattered) const
Definition: SLRay.cpp:542
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
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
SLVec3 & normalize()
Definition: SLVec3.h:124
T dot(const SLVec3 &v) const
Definition: SLVec3.h:117
T maxXYZ()
Definition: SLVec4.h:141

Member Data Documentation

◆ _calcDirect

SLbool SLPathtracer::_calcDirect
private

flag to calculate direct illumination

Definition at line 94 of file SLPathtracer.h.

◆ _calcIndirect

SLbool SLPathtracer::_calcIndirect
private

flag to calculate indirect illumination

Definition at line 95 of file SLPathtracer.h.

◆ _lumSum

vector<SLdouble> SLPathtracer::_lumSum
private

Sum and sum of squares of the luminance of every sample, per pixel.

The two moments that the variance of a pixel is computed from, in computeNoise. Luminance rather than colour, because the noise of a pixel is one number and not three, and it is linear, so summing the luminance of the samples and taking the luminance of _radianceSum come to the same thing.

They are double and not float, although _radianceSum is float, because the variance is the small difference of two large sums, S2 - S1*S1/N. In a quiet region those two agree to several digits and a float would leave almost none of the result. A firefly makes it worse from the other side: 1e4 squared is 1e8, and adding an ordinary 1e-4 to that is lost entirely in float. Double carries both ends. The cost is 16 bytes per pixel, 3.7 MB at 640x480.

Indexed like _radianceSum, with y * width + x.

Definition at line 152 of file SLPathtracer.h.

◆ _lumSumSq

vector<SLdouble> SLPathtracer::_lumSumSq
private

Definition at line 153 of file SLPathtracer.h.

◆ _noiseRSE

SLfloat SLPathtracer::_noiseRSE
private

mean relative standard error, see noiseRSE()

Definition at line 155 of file SLPathtracer.h.

◆ _noiseRSE999

SLfloat SLPathtracer::_noiseRSE999
private

the same for the worst 0.1%, see noiseRSE999()

Definition at line 156 of file SLPathtracer.h.

◆ _radianceSum

vector<SLCol4f> SLPathtracer::_radianceSum
private

Linear, unclamped sum of all radiance samples taken so far per pixel.

The progressive mean of a path tracer must never be kept in an 8 bit image. Rounding the running mean to 1/255 after every sample stops the convergence as soon as the correction of one sample, which is |sample - mean| / sampleNo, falls below half a quantisation step, i.e. as soon as sampleNo > 510 * |sample - mean|. A bright outlier (a firefly) then freezes at a wrong value and never averages out again, no matter how many samples are rendered. That is why this buffer holds the raw sum in full float precision and is divided by the sample number for the display only. Its size is _images[0]->width() * _images[0]->height() and it is indexed with y * width + x.

Definition at line 136 of file SLPathtracer.h.

◆ _sampleClamp

SLfloat SLPathtracer::_sampleClamp
private

Upper limit on the radiance of a single sample, 0 to switch it off.

A firefly is a path that carries far more energy than the pixel it lands in, most often a caustic: light that reaches a diffuse surface through the mirror or the glass sphere. The estimator is right about it, but such a path is found so seldom that the average is still visibly lumpy after a thousand samples. Capping what one sample may contribute removes it at once.

This is a deliberate bias, and the only one left in the renderer: it discards the part of a caustic above the limit and makes it darker than it is. Measured on the Muttenzer Box at 100 spp, against no clamp:

limit fireflies, far wall mean radiance of the caustic off 4.35% 1.000 30 4.75% 0.985 10 4.08% 0.938 3 0.02% 0.695

The fireflies of this scene sit between 3 and 10, so 30 does nothing and 10 barely helps. 3 removes them almost entirely and costs 2 to 5% on ordinary surfaces, 15% on the ceiling below the light and 30% on the caustic under the glass sphere. That is the trade, and it is why this is a menu item that can be switched off rather than a constant.

Note that it caps the SAMPLE and never the running mean. Clamping the mean is the defect that the float accumulation buffer removed: it froze the fireflies at a wrong value instead of averaging them away.

Definition at line 123 of file SLPathtracer.h.

◆ renderSlicesPTAsync

function<void(bool, int, SLuint)> SLPathtracer::renderSlicesPTAsync
private

Definition at line 92 of file SLPathtracer.h.


The documentation for this class was generated from the following files: