Up to now the reflections that have been visible from the raytracer have been perfect reflections. This means that we only need to trace a single reflection ray and we can do perfect mirrors all over the place.
Sadly for photorealism, very few surfaces show perfect reflections. In most materials the reflections become blurry as objects get further away from a surface. This is because most surfaces aren’t perfectly flat. The surfaces have small imperfections at a microsopic level which mean that light is reflected in slightly different directions depending on which part of the imperfections they hit.
As you can see, the right hand side of the image is a reflection (it’s reversed) and the reflection itself is quite distorted. This is because I took the photo by pointing the camera at one of the kitchen units. This surface has a slightly mottled surface which leads to imperfect reflections.
To simulate this in a raytracer we need to change the part of our raytracing system that calculates the reflection colour. Currently the code looks like this…
DVector3 reflection = eyeVector - normal * (normal.Dot(eyeVector) * 2.f); scene->Intersect(DRay(hitPos, reflection), response); mRed += response.mColour.mRed * mReflectivity.mRed; mGreen += response.mColour.mGreen * mReflectivity.mGreen; mBlue += response.mColour.mBlue * mReflectivity.mBlue;
To change this so that we calculate a glossy reflection we introduce a new parameter to the material model called “shininess”. If a surface is completely shiny, then the reflection will be perfect and we won’t modify the reflection vector at all. As the shininess decreases we add an increasing amount of noise to the reflection vector. When completely non-shiny we end up with no cohesive reflections.
Now we need to calculate some “noise” for the reflection vector. For a completely uniform level of glossiness we’ll use a random perturbation vector and blend this in using the shininess value. We need to add the following line of code before intersecting the reflection vector with the scene.
DVector3 perturb = DVector3(scene->GetRandom()*2-1, scene->GetRandom()*2-1, scene->GetRandom()*2-1); perturb *= 1.0f - m_Shininess; reflection += perturb; reflection.Normalise();
This does the trick quite nicely. There may be some argument for using a random vector selected from a sphere rather than a cube, but it probably makes little difference when shininess is close to 1.0.
Once you’ve got this code in place you then to render multiple reflections per pixel, you can either do this by wrapping the reflection lookup in a while loop, or by multisampling each pixel. As the pixels are sampled multiple times the glossiness will start to appear. In the following scenes I’ve used 32 samples per pixel to show the glossiness.
First off lets look at a scene with a single reflective object.
Now lets change the shininess to 0.95 (5% perturbation). This now gives us a noticeably glossy reflection
We can go further, here’s an image with shininess of 0.9 (10% perturbation)
As you can see we’re now starting to reach the point where we need to run many more samples per pixel to reduce the noise visible in the image.
For the final feature render I’ve mixed in depth of field and area lights. To get this to look really smooth I’ve sampled each pixel 1024 times. This took about 10 minutes to render on my machine at 750×500. That’s 384 million initial rays, with many more secondary rays sent into the scene.
Hope you found this useful, as ever, drop me a comment if you’ve got any questions.