Thursday, March 31, 2016

Converting area-based pdf to direction-based pdf

One thing I covered in my new mini-book is dealing with pdf management in a path tracer.     Most of my ray tracers (and many others do it too) are set up to sample directions.   So at a point being shaded, the integral is over directions:

color(direction_out) = INTEGRAL   brdf(direction_in, direction_out) cosine

the domain of integration is all direction_in coming from the sphere of all directions.   Often the brdf is zero for directions from inside the surface so it's the hemisphere.

Basic Monte Carlo integration says that if you choose a random direction_in with pdf pdf(direction_in), the unbiased estimate is:

       color(direction_out)  =  brdf(direction_in, direction_out) cosine / pdf(direction_in)

As long as you can generate random direction_in, and can compute pdf(direction_in) then you have a working ray tracer.   It works best when you are likely to cast random rays toward bright areas like lights.    If you sample a polygonal light, you normally just sample q uniformly on the area.   The direction is just q-p.    The key observation to get from the pdf in area space p_area(q) = 1/A (where A is the area of the light) is that by definition the probability of q being in a small area dA is

       probability = p_area(q) dA

Similarly the directional pdf of a direction toward dA is:

       probability = pdf(q-p) dw

Those probabilities must be the same:

      p_area(q) dA =  pdf(q-p) dw

By algebra:

pdf(q-p) =  p_area(q) dA/dw = (1/A) (dA/dw)

But what is (dA/dw)?

Let's do some basic geometry.   As is so often the case, the right figure makes that tons easier: 


You pick a point q on the light with pdf 1/A in area space, but what is the pdf in directional space?   Drawn in limnu whitboarding program.

If dA faces p dead-on (so its normal faces p), then it's foreshotening:

      dw = dA/distance_squared

But if dA is tilted then we get a cosine shift:

      dw = dA*cosine / distance_squared

So recall we are after

      pdf(q-p) = (1/A) (dA/dw)

So we can plug and chug to get:

       pdf(q-p) =length_sqaured(q-p) / (A*cosine)

We just apply this formula directly in our code.   For my xz rectangle class this is:



Third and final ray tracing mini-book is out

My third and final Kindle ray tracing mini-book is out.    It will be free April 1-5, 2016.

Ray Tracing: The Rest Of Your Life (Ray Tracing Minibooks Book 3)

From the intro:

In this volume, I assume you will be pursuing a career related to ray tracing and we will dive into the math of creating a very serious ray tracer. When you are done you should be ready to start messing with the many serious commercial ray tracers underlying the movie and product design industries. There are many many things I do not cover in this short volume; I dive into only one of many ways to write a Monte Carlo rendering program.   I don’t do shadow rays (instead I make rays more likely to go toward lights), bidirectional methods, Metropolis methods, or photon mapping.   What I do is speak in the language of the field that studies those methods.   I think of this book as a deep exposure that can be your first of many, and it will equip you with some of the concepts, math, and terms you will need to study the others.

Friday, March 18, 2016

My buggy implimentation of Schlick approximation

One of the most used approximations in all of graphics is the Schlick approximation by Christophe Schlick.    It says the Fresnel reflectance of a surface can be approximated by a simple polynomial of cosine(theta).    For dielectrics, a key point is that this theta is the bigger of the two regardless of which direction the ray is traveling.
Regardless of light direction, the blue path is followed.   Regardless of light direction, the larger of the two angles (pictured as theta) is used for the Schlick approximation.

A sharp reader of my mini-book pointed out I have a bug related to this in my code.   I was surprised at this because the picture looked right (sarcasm).      The bug in my code and my initial fix is shown below.
The old code shown in blue is replaced with the next two lines.
My mistake was to pretend that if snell's law applies to sines,  n1*sin(theta1) = n2*sin(theta2), it must also apply to cosines.   It doesn't.    (The fix is just to use cos^2 = 1 - sin^2 and do some algebra) I make mistakes like this all the time, but usually the wonky-looking picture, but in this case it didn't.    It only affected internal reflections, and it just picked some somehwhat arbitrary value that was always between 0 and 1.   Since real glass balls look a little odd in real life, this is not something I can pick up.   In fact I am not sure which picture looks right!

Old picture before bug fix.
New picture after bug fix.

I am reminded of spherical harmonic approximation to diffuse lighting.   It looks different than the "correct" lighting, but not worse.  (In fact I think it looks better).    What matters about hacks is their robustness.    It's best to do them on purpose though...

Sunday, March 13, 2016

An example of an indirect light

On the top you see a big reflection of the Sun off my not very polished wooden floor.   On the bottom is the view in the opposite direction.   Three interesting points:

1. The secondary light effect is surprisingly (to me) strong and un-diffused.
2. The secondary light effect is achromatic even through it is off a brown floor-- the specular component is achromatic.
3. Note the tertiary effect on the wall by the picture frame.    This would be tough for most renderers.

Thursday, March 10, 2016

A simple SAH BVH build

Here is a cut at a BVH build that cuts along the longest axis using the surface-area-heuristic (SAH).   The SAH minimizes:

SUM_left_right number_of_children_in_subtree*surface_area_of_bounding_box_of_subtree

This has been shown by many people to work shockingly well.

Here's my build code and it works ok.   I still have to sort because I need to sweep along an axis.   Is it worth it to tree each of three axes instead of longest?   Maybe.... bundles of long objects would be the adversarial case for the code below.

Tuesday, March 8, 2016

BVH builds

In the new mini-book I cover BVHs.   In the book I always went for simple conceptual code figuring people can speed things up later.   I have what must be close to a minimum BVH build, but it just gets us the log(N).     It picks a random axis, splits in the middle of the list:

I could have split geometrically and done a O(N) sweep and the code might have been as small, but I wanted to set people up for a top-down surface-area heuristic (SAH) build.     As discussed by Aila, Karras, and Laine in 2013 (great paper--- download it here) we don't fully understand why lazy SAH builds work so well.   But let's just be grateful.   So what IS the most compact top down build?   I am going to write one to be supplemental for the book and just sweeping on those qsorts above is what appears best to me, but I invite pointers to good practice.

Monday, March 7, 2016

New ray tracing mini-book is out

My second and probably final kindle mini-book Ray Tracing the Next Week is out.    It will be free for five days starting tomorrow Tu Mar 8.    I will be maintaining notes and links at

Saturday, March 5, 2016

Next mini-book on ray tracing

I am almost done with the next (and probably last) mini-book on ray tracing.   It will be free for the first five days at the kindle store (probably sometime this week) so do not buy it -- there may be a short time when Amazon hasn't made it free yet.   The associated web site will be at just like the first book.   This the the image with the features added:

Final image for Ray Tracing: the Next Week

The features added are solid texture, image texture, participating medium, motion blur, instancing, and BVH.   I decided not to add explicit direct light so it's still brute force path tracing (thus the caustic is implicit).  

Tuesday, March 1, 2016

Lazy debugging

When I get a chunk of time I have been beavering away on a second part to my mini-book.     I just added implicit direct lighting (where you send scattered rays preferentially toward lights) and I got a bunch of speckles.

What are those?   Bad importance sampling?   A bug?   Some correct behavior I don't understand?   Who knows.   Should I track it down, or keep adding features and hope it luckily goes away in various refactors that come along organically.

Which one is less work?   Hope it goes away!   Sold!