smallppm back

Board: Board index ‹ Ray tracing ‹ Tools, demos & sources

(L) [2009/07/12] [thachisu] [smallppm] Wayback!

Inspired by smallpt by Kevin Beason ([LINK http://www.kevinbeason.com/smallpt/]), I wrote a *small* implementation
of Progressive Photon Mapping, called "smallppm". The code is unfortunately longer than smallpt,
but it is still 128 lines x 79 column (or less).
[LINK http://graphics.ucsd.edu/~toshiya/smallppm.cpp]
Here is the list of features (majority of them are from smallpt):
* Global illumination via Progressive Photon Mapping
* Range query using hashed grid
* 128 lines of 79-column (or less) open source C++ code
* Multi-threading using OpenMP
* Point light source
* Specular, Diffuse, and Glass BRDFs
* Ray-sphere intersection
* Modified Cornell box scene description contains LSDSE path (difficult for unbiased methods)
* Cosine importance sampling of the hemisphere for diffuse reflection
* Russian roulette for path termination
* Russian roulette and splitting for selecting reflection and/or refraction for glass BRDF
* Quasi Monte Carlo sampling using Halton sequence
The attached image was rendered using one billion emitted photons in 1024x768 (resized to 512 x 384).
It took about 16 hours on Core Duo 1.06GHz (2 threads). As you might have noticed, it does not support
soft shadows or anti-aliasing by supersampling. I would like to thank again Kevin for his great work,
and hopefully you guys will enjoy my code as well :->
(L) [2009/07/12] [Catalytic] [smallppm] Wayback!

Very cool and instructional!
I just pasted it into XCode and voila, it works.
It even uses QMC [SMILEY ;-)]
(L) [2009/07/12] [toxie] [smallppm] Wayback!

thanx for this nice piece of code.. i took the freedom to fix compile errors (vc and ic) and fix the sampling (especially with openmp turned on [SMILEY :)])..
(hoping that i didn't introduce some garbage with these changes.. [SMILEY ;)])
so the tiny updated version is here:
edit: seems my openmp 'sampling fix' introduced some qmc related patterns on the walls.. [SMILEY :(] somebody has an idea?!
edit2: hmmm.. somehow i had to trick openmp by using a threadprivate()/copyin() instead of private()?! then it worked.. very strange.. updated source below
edit3: using hammersley seemed a bit suspicous for PPM (as it was used in the code), so i replaced the first sampling component also with halton.. plus saved some more code lines and added some tiny optimzations.. and added simple tonemapping in addition to the already existant gamma correction..
edit4: some additional code squeeze and tiny optimizations..
edit5: 116 lines! plus a bit of speedup

#include <math.h>   // smallppm, Progressive Photon Mapping by T. Hachisuka
#include <stdlib.h> // originally smallpt, a Path Tracer by Kevin Beason, 2008
#include <stdio.h>  // Usage:           time ./smallppm 100000 && xv image.ppm
const double PI=3.14159265358979,inf=1e20,eps=1e-4; //  ^^^^^^:emitted photons
const double ALPHA = .7; // the alpha parameter of PPM
const int prim[59] = {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,
 73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,
 179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277};
inline double hal(unsigned int j,const unsigned int pb) { // Halton sequence
  double h=0., f=1./prim[pb], fct=f;
  while(j>0) {h+=(j%prim[pb])*fct; j/=prim[pb]; fct*=f;} return h; }
struct Vec { double x, y, z; Vec(double v=0) : x(v),y(v),z(v) {}; // pos/r,g,b
  inline Vec(double x_, double y_, double z_) : x(x_),y(y_),z(z_) {};
  inline Vec operator+(const Vec &b) const {return Vec(x+b.x, y+b.y, z+b.z);}
  inline Vec operator-(const Vec &b) const {return Vec(x-b.x, y-b.y, z-b.z);}
  inline Vec operator*(const double b) const {return Vec(x*b, y*b, z*b);}
  inline Vec mul(const Vec &b) const {return Vec(x*b.x, y*b.y , z*b.z);}
  inline Vec& norm() {return (*this) = (*this) * (1./sqrt(x*x+y*y+z*z));}
  Vec operator%(const Vec&b){return Vec(y*b.z-z*b.y,z*b.x-x*b.z,x*b.y-y*b.x);}
  inline double dot(const Vec &b) const {return x*b.x + y*b.y + z*b.z;} };
struct AABB { Vec min, max; // axis aligned bounding box
  inline void fit(const Vec &p) {
    if(p.x<min.x)min.x=p.x; if(p.y<min.y)min.y=p.y; if(p.z<min.z)min.z=p.z;
    if(p.x>max.x)max.x=p.x; if(p.y>max.y)max.y=p.y; if(p.z>max.z)max.z=p.z; }
  inline void reset() {min=Vec(inf); max=Vec(-inf);} };
struct HPoint {Vec f,pos,nrm,flux; double r2; unsigned int n,pix;};
struct List {HPoint *id; List *next;}; unsigned int num_hash,num_photon;
List* ListAdd(HPoint *i,List* h){List* p=new List;p->id=i;p->next=h;return p;}
double hashs; List **hashg; List *hitps = NULL; AABB hpbbox;
inline unsigned int hash(const unsigned int x, const unsigned int y, const
  unsigned int z) { return (x*73856093 ^ y*19349663 ^ z*83492791)%num_hash; }
void build_hash_grid(const double wh) { hpbbox.reset(); const List *lst=hitps;
  while(lst != NULL) {HPoint *hp=lst->id; lst=lst->next; hpbbox.fit(hp->pos);}
  const Vec ssize = hpbbox.max - hpbbox.min; // compute initial radius
  const double irad = (ssize.x + ssize.y + ssize.z) * (4.0/3.0) / wh;
  hpbbox.reset(); lst = hitps; num_hash = 0; // determine hash size
  while(lst != NULL) { HPoint *hp=lst->id; lst=lst->next; hp->r2=irad*irad;
      hp->n=0; hp->flux=Vec(); ++num_hash; hpbbox.fit(hp->pos-irad);
      hpbbox.fit(hp->pos+irad); }   hashs=0.5/irad; hashg=new List*[num_hash];
    for (unsigned int i=0; i<num_hash;++i) hashg[i] = NULL;
    lst = hitps; while(lst != NULL) { // store hitpoints in the hashed grid
      HPoint *hp=lst->id; const Vec BMin=((hp->pos-irad)-hpbbox.min)*hashs,
      BMax=((hp->pos+irad)-hpbbox.min)*hashs; lst=lst->next;
      for (int iz = abs(int(BMin.z)); iz <= abs(int(BMax.z)); ++iz)
        for (int iy = abs(int(BMin.y)); iy <= abs(int(BMax.y)); ++iy)
          for (int ix = abs(int(BMin.x)); ix <= abs(int(BMax.x)); ++ix)
            {const int hv=hash(ix,iy,iz); hashg[hv]=ListAdd(hp,hashg[hv]);}} }
struct Ray {Vec o, d; Ray(const Vec &o_, const Vec &d_) : o(o_), d(d_) {} };
enum Refl_t {DIFF, SPEC, REFR};  // material types, used in trace()
struct Sphere { const double rads; Vec p, c; Refl_t refl;
  Sphere(double r_,Vec p_,Vec c_,Refl_t e_):rads(r_*r_),p(p_),c(c_),refl(e_){}
  inline double intersect(const Ray &r) const { // returns distance
    const Vec op=p-r.o; double t, b=op.dot(r.d), det=b*b-op.dot(op)+rads;
    if(det<0) return inf; det=sqrt(det);
    return (t=b-det)>eps ? t : ((t=b+det)>eps ? t : inf); } };
const Sphere sph[9] = { // Scene: radius, position, color, material
  Sphere(1e5, Vec( 1e5+1,40.8,81.6), Vec(.75,.25,.25),DIFF),//Left
  Sphere(1e5, Vec(-1e5+99,40.8,81.6),Vec(.25,.25,.75),SPEC),//Rght
  Sphere(1e5, Vec(50,40.8, 1e5),     Vec(.75)        ,DIFF),//Back
  Sphere(1e5, Vec(50,40.8,-1e5+170), Vec(),           DIFF),//Frnt
  Sphere(1e5, Vec(50, 1e5, 81.6),    Vec(.75)        ,DIFF),//Botm
  Sphere(1e5, Vec(50,-1e5+81.6,81.6),Vec(.75)        ,DIFF),//Top
  Sphere(16.5,Vec(27,16.5,47),       Vec(.999)       ,SPEC),//Mirr
  Sphere(16.5,Vec(73,16.5,88),       Vec(.999)       ,REFR),//Glas
  Sphere(8.5, Vec(50,8.5,60),        Vec(.999)       ,DIFF) };//Mid
int toInt(double x){return int(pow((x < 0) ? 0:(x > 1) ? 1:x,1/2.2)*255.+.5);}
inline bool intersect(const Ray &r, double &t, int &id) {// ray-sphere inters.
  const int n=sizeof(sph)/sizeof(Sphere); t=inf; for(int i=0;i<n;++i)
  {const double d=sph[i].intersect(r); if(d<t){t=d;id=i;} } return t<inf; }
void genp(Ray &pr,unsigned int j) { double p=(2.*PI)*hal(j,0),t=2.*acos(sqrt(
  hal(j,1))),st=sin(t);pr=Ray(Vec(50,60,85),Vec(cos(p)*st,cos(t),sin(p)*st));}
void trace(const Ray &r,int dpt,bool m,const Vec &flux,const Vec &adj,int j) {
  double t; int id; if (!intersect(r, t, id) || (dpt++ >= 19)) return;
  const Sphere &obj=sph[id]; const Vec x=r.o+r.d*t,n=(x-obj.p).norm(),f=obj.c;
  Vec nl=n.dot(r.d)<0?n:n*-1; double p=f.x>f.y&&f.x>f.z?f.x:f.y>f.z?f.y:f.z;
  if (obj.refl == DIFF) { if (m) { HPoint* hp=new HPoint; hp->f=f.mul(adj);
      hp->pos=x; hp->nrm=n; hp->pix=j; hitps = ListAdd(hp, hitps);
    } else { const Vec u=((fabs(nl.x)>.1?Vec(0,1,0):Vec(1,0,0))%nl).norm(),
      v=nl%u, hh=(x-hpbbox.min)*hashs;
      #pragma omp critical // critical section in OpenMP
      { List* hp=hashg[hash(abs(int(hh.x)),abs(int(hh.y)),abs(int(hh.z)))];
        while(hp!=NULL) { HPoint *hitp=hp->id;hp=hp->next; Vec v=hitp->pos-x;
          if ((hitp->nrm.dot(n) > eps) && (v.dot(v) <= hitp->r2)) {
            double g = (hitp->n*ALPHA+ALPHA) / (hitp->n*ALPHA+1.);
            hitp->r2*=g; hitp->n++;
            hitp->flux=(hitp->flux + hitp->f.mul(flux))*g; } } }
      if(hal(j,dpt*3-1)<p) { const double r1=(2.*PI)*hal(j,dpt*3),r2=hal(
        j,dpt*3+1),r2s=sqrt(r2); trace(Ray(x,(u*(cos(r1)*r2s)+v*(sin(r1)*r2s)+
                   nl*sqrt(1-r2)).norm()),dpt,0,f.mul(flux)*(1./p),adj,j); } }
  } else { const Ray lr(x,r.d-n*(2.*n.dot(r.d))); if (obj.refl == SPEC)
      trace(lr, dpt, m, f.mul(flux), f.mul(adj), j);
    else { const bool into = (n.dot(nl)>0.); const double nc=1., nt=1.5,
      nnt=into?nc/nt:nt/nc, ddn=r.d.dot(nl), cos2t=1-nnt*nnt*(1.-ddn*ddn);
      if (cos2t<0.) return trace(lr,dpt,m,flux,adj,j);
      Vec td = (r.d*nnt - n*((into?1.:-1.)*(ddn*nnt+sqrt(cos2t)))).norm();
      const double a=nt-nc, b=nt+nc, R0=a*a/(b*b), c=1.-(into?-ddn:td.dot(n));
      const double Re=R0+(1-R0)*(c*c)*(c*c)*c; Ray rr(x,td);Vec fa=f.mul(adj);
      if(m) {trace(lr,dpt,m,flux,fa*Re,j); trace(rr,dpt,m,flux,fa*(1.-Re),j);}
      else   trace((hal(j,dpt*3-1)<Re)?lr:rr,dpt,m,flux,fa,j); } } }
int main(int argc, char *argv[]) {
  int w=1024, h=768; num_photon = (argc==2) ? atoi(argv[1])/1000+1 : 1;
  const Vec camd(0,-.04257,-.99909); unsigned int pj=0;
  Vec cx=Vec(w*.5135/h,0,0), cy=(cx%camd).norm()*.5135, *c=new Vec[w*h];
  for(int y=0;y<h;++y) {fprintf(stderr,"\rHitPointPass %3.2f%%",100.*y/(h-1));
    for(int x=0;x<w;++x,++pj) { Vec d=cx*((x+.5)/w-.5)-cy*((y+.5)/h-.5)+camd;
      trace(Ray(Vec(50,52,295.6)+d*140.,d.norm()),0,1,Vec(),Vec(1),pj); } }
  fprintf(stderr,"\n"); build_hash_grid(w+h);
  #pragma omp parallel for schedule(dynamic, 1)
  for(int i=0;i<num_photon;++i) { unsigned int pj=i*1000; Vec b,f;Ray r(b,b);
    fprintf(stderr,"\rPhotonPass %3.2f%%",100.*(i+1)/num_photon);
    for(int j=0;j<1000;++j,++pj){genp(r,pj);trace(r,0,0,Vec(2e4),Vec(1),pj);}}
  List* lst=hitps; while(lst != NULL) { HPoint* hp=lst->id;lst=lst->next;
    int i=hp->pix; c[i]=c[i]+hp->flux*((1./(PI*1000.))/(hp->r2*num_photon)); }
  FILE* f = fopen("image.ppm","w"); fprintf(f,"P3\n%d %d\n%d\n",w,h,255);
  for(int i=0;i<w*h;++i){double a=.2125*c[i].x+.7154*c[i].y+.0721*c[i].z + 1.;
    fprintf(f,"%d %d %d ",toInt(c[i].x/a),toInt(c[i].y/a),toInt(c[i].z/a));} }
(L) [2009/07/12] [toxie] [smallppm] Wayback!

10.000.000 and roughly 15min on mediocre laptop:
(thanx for your code piece again, really nice to play around with! [SMILEY :)])
(L) [2009/07/12] [thachisu] [smallppm] Wayback!

toxie, that is a very good point! QMC in the original code was not thread safe. I will fix it soon by probably
following your code. I actually tried using Hammersley too, but I got some patterns in images as you mentioned.
(L) [2009/07/12] [toxie] [smallppm] Wayback!

the hammersley is there actually, in the photon generator (p=...).. although it does not seem to produce artifacts, i think it isn't really wise to use it with PPM.. i also squeezed out some additional lines of code, so it's now at 120 lines i think.. will post the code later-on.. [SMILEY :)]
(L) [2009/07/12] [thachisu] [smallppm] Wayback!

>> toxie wrote:the hammersley is there actually, in the photon generator (p=...).. although it does not seem to produce artifacts, i think it isn't really wise to use it with PPM.. i also squeezed out some additional lines of code, so it's now at 120 lines i think.. will post the code later-on..
It seems that I uploaded a little bit older version  [SMILEY :oops:] I will update it soon.
(L) [2009/07/12] [thachisu] [smallppm] Wayback!

Ok. QMC should be fixed now. However, it is always possible that I missed something [SMILEY :lol:]
(L) [2009/07/13] [toxie] [smallppm] Wayback!

still looks fishy to me (sorry [SMILEY ;)]):
the initial paths are still all traced using index 0.. that looks very strange to me.. is there a reason for that?
later you still access prime[3*0-1], which seems to work just by accident i guess?! also if you access a prime that is larger than your table, there should be strange things happening (f.e. 3*18+1)?!
(L) [2009/07/13] [toxie] [smallppm] Wayback!

oh, and one more thing: using splitting AND the same QMC numbers for the splitted paths is also a bit shaky and might lead to correlation artifacts..
and the same compile errors still are there (especially the openmp "critial" one, are you sure you ever even tested openmp?!)
(L) [2009/07/13] [thachisu] [smallppm] Wayback!

>> toxie wrote:still looks fishy to me (sorry ):
the initial paths are still all traced using index 0.. that looks very strange to me.. is there a reason for that?
later you still access prime[3*0-1], which seems to work just by accident i guess?! also if you access a prime that is larger than your table, there should be strange things happening (f.e. 3*18+1)?!
If you mean the initial paths as the paths for hitpoints, they do not use any of random numbers.
The code traces *all* the ray branches until a ray hits a diffuse object, and terminates at the diffuse object.
It does not matter what index is given to the trace for the initial paths.
It will not access prime[3*0-1] as there is dpt++ before. Inside the trace, prime[3*1-1] = prime[2]
is the first prime that will be used. It should not get larger than the number of primes in the table
as 3*18+1 = 55 < 61 (18 is the max of dpt and there are 61 primes).

 >> toxie wrote:oh, and one more thing: using splitting AND the same QMC numbers for the splitted paths is also a bit shaky and might lead to correlation artifacts..
and the same compile errors still are there (especially the openmp "critial" one, are you sure you ever even tested openmp?!)
I do not know why you say it is a bit shaky, but it I agree that it possibly leads to correleation artifacts.
I of course have tested OpenMP with gcc 4.4 and worked fine for me :->  
Maybe you could post what compile errors did you get?
(L) [2009/07/13] [toxie] [smallppm] Wayback!

"critial" should be "critical".. [SMILEY :)]
 >> If you mean the initial paths as the paths for hitpoints, they do not use any of random numbers.

but what about the sampling decision (if (hal()<p) trace(Ray(x,d),dpt,m,f.mul(flux)*(1./p),adj)) in the DIFF code path?
(L) [2009/07/13] [lycium] [smallppm] Wayback!

this thread is both hilarious and super informative, thanks toshiya + toxie [SMILEY :D]
(L) [2009/07/13] [thachisu] [smallppm] Wayback!

>> toxie wrote:"critial" should be "critical"..
apparently, gcc does not complain it  [SMILEY :oops:]
EDIT: It might indicate that "critical" is not needed in practice. In fact, CPU usage seems not be very good with
"critical" (not "critial"). Of course, weird things could happen without "critical" but that seems to be pretty rare,
and what we care are accumulated statistics.
EDIT2: obviously, i forgot to use -Wall.
 >> but what about the sampling decision (if (hal()<p) trace(Ray(x,d),dpt,m,f.mul(flux)*(1./p),adj)) in the DIFF code path?
That should be inside of "else" of "if (m)" so it will not be executed if m == true, which is the case for
the initial paths, but I cannot believe my eyes anymore.
(L) [2009/07/14] [phresnel_] [smallppm] Wayback!

oooh sexy, I am going to try that out at home [SMILEY :)]
(L) [2009/07/14] [toxie] [smallppm] Wayback!

>> thachisu wrote:EDIT: It might indicate that "critical" is not needed in practice. In fact, CPU usage seems not be very good with
"critical" (not "critial"). Of course, weird things could happen without "critical" but that seems to be pretty rare,
and what we care are accumulated statistics.

this is a cool argumentation.. [SMILEY :)] [SMILEY :)] [SMILEY :)]
 >> thachisu wrote:but what about the sampling decision (if (hal()<p) trace(Ray(x,d),dpt,m,f.mul(flux)*(1./p),adj)) in the DIFF code path?
That should be inside of "else" of "if (m)" so it will not be executed if m == true, which is the case for
the initial paths, but I cannot believe my eyes anymore.
oh, whoops, you are -so- right, sorry.. this whole code shrinkage thing did make me miss that last }  [SMILEY ;)]
(L) [2009/07/14] [thachisu] [smallppm] Wayback!

>> toxie wrote:thachisu wrote:EDIT: It might indicate that "critical" is not needed in practice. In fact, CPU usage seems not be very good with
"critical" (not "critial"). Of course, weird things could happen without "critical" but that seems to be pretty rare,
and what we care are accumulated statistics.

this is a cool argumentation..   

 [SMILEY :^o]
...anyway, efficient parallelization of this "memoryless" implementation (i.e. photon splatting) seems to be difficult.
(L) [2009/07/14] [toxie] [smallppm] Wayback!

exactly.. nevertheless, thanx a lot for this wonderful source again!
(L) [2009/07/14] [toxie] [smallppm] Wayback!

>> thachisu wrote:toxie wrote:still looks fishy to me (sorry ):
the initial paths are still all traced using index 0.. that looks very strange to me.. is there a reason for that?
later you still access prime[3*0-1], which seems to work just by accident i guess?! also if you access a prime that is larger than your table, there should be strange things happening (f.e. 3*18+1)?!
If you mean the initial paths as the paths for hitpoints, they do not use any of random numbers.
The code traces *all* the ray branches until a ray hits a diffuse object, and terminates at the diffuse object.
It does not matter what index is given to the trace for the initial paths.
It will not access prime[3*0-1] as there is dpt++ before. Inside the trace, prime[3*1-1] = prime[2]
is the first prime that will be used. It should not get larger than the number of primes in the table
as 3*18+1 = 55 < 61 (18 is the max of dpt and there are 61 primes).
i now know why i had some problems with this piece of code: the hal() is called even though it is not needed for the case m=true! and unless the compiler optimizes this, so that it is really only called for m=false, it can happen that one divides by zero then if one is unlucky.. i fixed this.. see code..
edit: oh, just forget the previous argument.. i should rather stop coding today.. [SMILEY ;)] [SMILEY ;)]
(L) [2009/07/21] [graphicsMan69] [smallppm] Wayback!

Hey thachisu or toxie -
Any particular reason why the code uses double instead of float?  Was this just to keep from adding 'f' to the end of the numeric constants?
(L) [2009/07/21] [thachisu] [smallppm] Wayback!

>> graphicsMan69 wrote:Hey thachisu or toxie -
Any particular reason why the code uses double instead of float?  Was this just to keep from adding 'f' to the end of the numeric constants?
No reason. If I have to say something, it is because the original smallpt is using double  [SMILEY :)]  Using float would work fine, too.
(L) [2009/07/21] [graphicsMan69] [smallppm] Wayback!

Okay, cool.  I do have some problems with that in terms of surface acne and speed degredation, but I guess some offsetting or epsilon tricks would be necessary to make the switch.
Thanks again for the code.
(L) [2009/07/22] [beason] [smallppm] Wayback!

Very nice work, Toshi!!
The use for double is to handle the sphere intersection with the super-huge spheres accurately, without leaks:
[LINK http://ompf.org/forum/viewtopic.php?p=11223#p11223 viewtopic.php?p=11223#p11223]
Edit: Maybe that was an old compiler bug. I think there may still be leaks though, near the top edges? Not sure.
(L) [2009/07/22] [lycium] [smallppm] Wayback!

btw, i've noticed some "stretching" artifacts in the photon map visualisation through the glass ball... i assume this will clear up, but i lack the experience with PPM to say if this is the case for sure.
similarly i'd be interested to see how the method fares with other scenes that monte carlo methods handle well (eg. glossy surfaces).
(L) [2009/07/22] [thachisu] [smallppm] Wayback!

>> graphicsMan69 wrote:Okay, cool.  I do have some problems with that in terms of surface acne and speed degredation, but I guess some offsetting or epsilon tricks would be necessary to make the switch.
You are right. Just switching into float did not work as beason pointed out.
(L) [2009/07/22] [thachisu] [smallppm] Wayback!

>> lycium wrote:btw, i've noticed some "stretching" artifacts in the photon map visualisation through the glass ball... i assume this will clear up, but i lack the experience with PPM to say if this is the case for sure.
similarly i'd be interested to see how the method fares with other scenes that monte carlo methods handle well (eg. glossy surfaces).
The stretching artifacts happen because the code is using a query with a sphere. What is spherical in a scene is not necessarily spherical after reflections/refractions. The artifacts will clear up as the radius shrinks. Ideally, we would like to use a query with the shape of pixel footprint, but in practice, we might want to specify the initial radius such that the stretched region is not too large on the image plane (e.g., using a ray differential to compute the initial radius). It could be possible to combine PPM with more sophisticated radiance estimation method such as ray maps.
Although I have not done exhaustive comparisons, I guess PPM is not the fastest for a typical scene like Cornell box.
With smallpt and smallppm, it should be easy to do such a comparison  [SMILEY :)]
(L) [2009/07/22] [toxie] [smallppm] Wayback!

@graphicsMan69: do i smell a CUDA port here or why the float vs. double questions?  [SMILEY :)]
(L) [2009/07/22] [graphicsMan69] [smallppm] Wayback!

Hmm, not really, but hey, that's a cool idea.  If I ever have free time (almost forgot what that's like), I might implement that.
(L) [2009/09/14] [Guest] [smallppm] Wayback!

The code is broken if you change the color of the glass spheres -- the caustics still retain a simple white color, and as this is a new method to me I am having a hard time understanding how to fix this simply -- any ideas guys?
(L) [2009/09/14] [graphicsMan69] [smallppm] Wayback!

>> Guest wrote:The code is broken if you change the color of the glass spheres -- the caustics still retain a simple white color, and as this is a new method to me I am having a hard time understanding how to fix this simply -- any ideas guys?
You need to apply Beer's Law to photons that have passed through the sphere the same way as you would do for rays from the eye.
(L) [2010/03/22] [@lx] [smallppm] Wayback!

hi all,
Thanks for this small implem Toshiya, it's really helpful to learn and "mind trace" some links to your original paper!  [SMILEY :wink:]
By the way, I have a question regarding the the radius reduction/flux correction factor (in your code g) : double g = (hitpoint->n*ALPHA+ALPHA) / (hitpoint->n*ALPHA+1.0);
From you paper, g = (N(x) + alpha * M(x)) / ( N(x) + M(x) )
So, I assume that in your implem M(x) = 1 (one photon is added to the current hit point), and I'm wondering why g is = (N(x) * alpha + alpha ) / ( N(x) * alpha + 1 ). Why do you multiply N(x) by alpha? I tried to use the same formula than your paper, and didn't notice any strong change in the rendering...
(L) [2010/03/22] [thachisu] [smallppm] Wayback!

>> @lx wrote:... and I'm wondering why g is = (N(x) * alpha + alpha ) / ( N(x) * alpha + 1 )
It is because N(x) this code is actually counting N(x) / alpha, not N(x) as hitpoint->n .
By counting N(x) / alpha, the local count update "N(X) = N(x) + alpha" becomes "N(X) / alpha = N(x) / alpha + 1.0", and you can use increment (hitpoint->n++).
This is purely for saving lines :->
(L) [2010/03/22] [@lx] [smallppm] Wayback!

oh,thanks, I didn't see that N(x) was in fact actualized with alpha value... that makes sense now, although I have now another question regarding this alpha value [SMILEY :)]
How do you choose this constant? Should-it be tricked on a per-scene basis or we can assume that .7 is a good average candidate? (based on your testings) I'm wondering the impact of this constant in the final quality of the rendering....
(L) [2010/03/22] [ingenious] [smallppm] Wayback!

>> @lx wrote:oh,thanks, I didn't see that N(x) was in fact actualized with alpha value... that makes sense now, although I have now another question regarding this alpha value
How do you choose this constant? Should-it be tricked on a per-scene basis or we can assume that .7 is a good average candidate? (based on your testings) I'm wondering the impact of this constant in the final quality of the rendering....
The alpha value is basically a trade-off between bias and noise. It has to be strictly larger than 0 and strictly smaller than 1, in order to ensure consistency, i.e. that the number of accumulated photons will increse and the search radius will decrease. The larger alpha, the more photons will you keep after each accumulation pass, which means slower radius decrease, which in turn results in more bias tolerance and more noise suppression. The algorithm is not too sensitive to it, as long as sane values are used (anything around 0.5 should be fine).
The algorithm is actually more sensitive to the initial radius, as this determines the amount of initial bias in your solution. Again, the larger the radius, the smoother but more biased the solution is. I'd simply recommend you to play with smallppm with different values.
Note that any non-zero initial radius and any alpha within the bounds ensure consistency.
(L) [2010/03/22] [@lx] [smallppm] Wayback!

Thanks ingenious, this is helpful.

back