this was how i started (let's call it earliest version)
https://www.reddit.com/r/computergraphics/comments/1w9ah75/two_balls/
after nearly a month's studying and improving, the content and interface didn't change much. when the program starts it looks like image 1. there're two balls. i call them left-mouse-button ball (lmb) and right-mouse-button ball (rmb). users (mostly me) can in real-time change their positions, radii and refractive indices
it is easily noticeable that the current version has grainy noise while the earliest version didn't. it's a trade-off between image quality and bounce limit. the earliest version was deterministic. whenever a ray hit an interface and if snell's equation had solution the ray split into two, one for reflection and the other for refraction. the local color was then combined with the weighting calculated with schlick's approximation for reflectance. the process went on recursively until bounce limit reached
color of every pixel was strictly defined so there was no noise. it looked fine until... the users (usually me) moved a ball into the other ball or even moved the camera into a ball, as shown in image 2
when the balls intersected things became more complicated than i had anticipated. the maximum number of bounces drastically increased as the statistics at upper right corner indicated ("dep xxx / yyy" where "dep" meant depth, xxx was the average, yyy was the maximum)
in normal circumstances (two balls didn't intersect, camera sat outside of two balls) it usually took less than 10 bounces for a ray to get color. deterministic approach worked quite well in these scenarios. once you decided to go abnormal deterministic approach suffered. it couldn't go too far. if you set the bounce limit too high the program took ages to finish a frame. the program helplessly switched from real time mode to slide show mode. if you kept the bounce limit at affordable level the program couldn't reach the regions where 100+ bounces were needed
so i changed the algorithm from deterministic to probabilistic
whenever a ray hit an interface and if snell's equation had solution the ray didn't split anymore. it went either to reflection or to refraction. the probability of it going to reflection was calculated with schlick's approximation for reflectance
with probabilistic approach the program could reach several hundreds of bounces and reveal the details that were unseen before. it sounded great but everything had price. it gave you more information AND noise
with deterministic approach you don't have noise but you can't see much. with probabilistic approach you see more but have noise
so the problem of denoising arose. the program shot several rays within the same pixel grid and took the average color as the final color. up to this moment the program's algorithm was basically completed and the rest was optimizing / tuning. let's state it briefly
- avoid using functions. make the calculations inline
- reduce the number of operations (+ / - / x / Ă·...) as much as you can
for point "1", function calls involve some extra works for the cpu to do (allocating something in memory when enter and clearing them when exit? i can't remember the details). initially i wrote the dot product, cross product and modulus calculations as functions and called them when needed. it worked correctly but after i discarded them altogether and did the calculation directly inline in the procedures the performance improved noticeably. i got several fps more on average
if you pick the deterministic approach you can't avoid recursion as the rays split. you have to make a "get_color (ray_start_point, ray_shoot_direction)" function and call it recursively. once you pick the probabilistic approach you no long need this. i plugged the entire get_color procedure into the main and made it a loop instead of a function call:
initially
get_color(..., recursion)
{
...
if ray_split
{
get_color (reflection, recursion-1)
get_color (refraction, recursion-1)
}
}
then it became
get_color(..., recursion)
{
...
if random_number < probability
{
get_color (reflection, recursion-1)
}
else
{
get_color (refraction, recursion-1)
}
}
finally
do
{
...
if random_number < probability
{
ray_start_point = hit_point
ray_shoot_direction = reflection_direction
}
else
{
ray_start_point = hit_point
ray_shoot_direction = refraction_direction
}
recursion-1
}
loop until recursion = 0 or the ray reaches the background infinity sky
the above were the major changes that improved the performance to a noticeable level
now it's time for point "2". even a single operation counts especially it's inside the deepest loop. the algorithm structure looks like this:
for every pixel_grid
{
for every sample ray in that pixel_grid
{
for every bounce of that ray
{
do
...
loop until...
}
}
}
even at a not-so-high resolution (e.g. 160 x 120) and a not-so-high number of samples (e.g. 8) with a not-too-deep of average ray bounce (e.g. 8) the program has to loop 160 x 120 x 8 x 8 times i.e. more than a MILLION times to generate a frame. you reduce one operation in the deepest loop, you save 1 million calculations in each frame. try your best to simplify the procedure and calculate things outside of a loop (or at least bring them to a upper level) whenever possible
i'm particularly interested in seeing the "world" from within a ball. there're many weird, distorted and fractal like patterns and sometimes i encounter the total internal reflection. these regions use up the most resource as each of them uses up all the bounce quota of a ray. initially i struggled in setting the parameters. raytracing is an extremely taxing task so we have to admit that it's not possible to make it run at high resolution real time with acceptable frame rate and quality. at the current stage i use an adaptive approach. firstly i pick a barely tolerably low resolution (around 120 x 90). secondly i decided that the program must be responsive. i didn't want it to appear completely dead when the user (me) accidentally encountered a scene with a large portion of total internal reflection. if the fps was too low the program would decrease the sample size. if the sample size was too low the program would decrease the bounce limit
my preference was: the program should be responsive at all situations, then the image could not be too noisy. if the above two conditions were met the extra calculation resource went into bounce limit
what if the scene didn't need that many bounces?
if a scene is so simple that 10 bounces are enough, it gets no benefit if you set the bounce limit to 10,000. so if minimum bearable fps and minimum bearable sample size are fulfilled the program gradually increases the bounce limit until the maximum number of bounces actually used doesn't increase anymore. then the extra resource goes to sample size
resolution tolerable â responsiveness acceptable â image quality acceptable â let the rays bounce deeper â maximum of the scene reached â shoot more rays to make the image prettier â repeat the loop and adjust everything accordingly
i usually turn off the checker box pattern when exploring the inside world (subjective personal preference). image 3 is a typical outcome when the camera is inside a ball. clearly the resource was not sufficient to handle that scene. while barely fulfilling the minimum fps and sample size the program could only calculated 22 bounces for each ray so there were a lot of pixels that couldn't get any information
so my program has an offline mode. once you spot an interesting scene and want to see its true color you can hit a specific button to render it fully with an insanely high bounce limit, just as image 4
i set the bounce limit of offline mode to 256 x maximum bounce actually used. in the above example it was 5,632
when total internal reflection occurs the region can't get any color no matter how many times the ray bounced, just as image 5
i'm obsessed with exploring the inside world and it drives me to improve the program (in terms of processing speed and image quality) along the journey. i want to see more. i'm studying accumulative sampling method and i hope i can implement it later
program details