# 29. Advanced Lighting Written by Caroline Begbie & Marius Horga

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

As you’ve progressed through this book, you’ve encountered various lighting and reflection models:

• In Chapter 10, “Lighting Fundamentals” you started with the Phong reflection model which defines light as a sum of three distinct components: ambient light, diffuse light and specular light.
• In Chapter 11, “Maps & Materials” you briefly looked at physically based rendering and the Fresnel effect.
• In Chapter 21, “Image-Based Lighting” you implemented skybox-based reflection and image-based lighting, and you used a Bidirectional Reflectance Distribution Function (BRDF) look-up table.

In this chapter, you’ll learn about global illumination and the famous rendering equation that defines it.

While reflection is possible using the local illumination techniques you’ve seen so far, advanced effects — like refraction, subsurface scattering, total internal reflection, caustics and color bleeding — are only possible with global illumination.

You’ll start by examining the rendering equation. From there, you’ll move on to raymarched reflection and refraction.

## The Rendering Equation

Two academic papers — one by James Kajiya, and the other by David Immel et al. — introduced the rendering equation in 1986. In its raw form, this equation might look intimidating:

The rendering equation is based on the law of conservation of energy, and in simple terms, it translates to an equilibrium equation where the sum of all source lights must equal the sum of all destination lights:

``````incoming light + emitted light = transmitted light + outgoing light
``````

If you rearrange the terms of the equilibrium equation, you get the most basic form of the rendering equation:

``````outgoing light = emitted light + incoming light - transmitted light
``````

The `incoming light - transmitted light` part of the equation is subject to recursion because of multiple light bounces at that point. That recursion process translates to an integral over a unit hemisphere that’s centered on the normal vector at the point and which contains all the possible values for the negative direction of the incoming light.

Although the rendering equation might be a bit intimidating, think of it like this: All the light leaving an object is what remains from all the lights coming into the object after some of them were transmitted through the object.

The transmitted light can be either absorbed by the surface of the object (material), changing its color; or scattered through the object, which leads to a range of interesting optical effects such as refraction, subsurface scattering, total internal reflection, caustics and so on.

## Reflection

Reflection, like any other optical phenomenon, has an equation that depends on three things: the incoming light vector, the incident angle and the normal vector for the surface.

## Getting Started

➤ In Xcode, open the starter app for this chapter and build and run (or set up the SwiftUI Canvas preview).

### Drawing a Checkerboard Pattern

To draw a pattern on the plane, you first need to have a way of identifying objects within the scene by comparing their proximity to the camera based on distance.

``````constant float PlaneObj = 0.0;
constant float SphereObj = 1.0;
``````
``````float dts = distToSphere(r, s);
``````
``````float object = (dtp > dts) ? SphereObj : PlaneObj;
``````
``````return float2(dist, object);
``````
``````float2 dist = distToScene(cam.ray);
``````
``````float closestObject = dist.y;
``````
``````// 1
if (closestObject == PlaneObj) {
// 2
float2 pos = cam.ray.origin.xz;
pos *= 0.1;
// 3
pos = floor(fmod(pos, 2.0));
float check = mod(pos.x + pos.y, 2.0);
// 4
col *= check * 0.5 + 0.5;
}
``````
``````fmod = numerator - denominator * trunc(numerator / denominator)
``````
``````mod = numerator - denominator * floor(numerator / denominator)
``````
``````float mod(float x, float y) {
return x - y * floor(x / y);
}
``````

``````Camera reflectRay(Camera cam, float3 n, float eps) {
cam.ray.origin += n * eps;
cam.ray.dir = reflect(cam.ray.dir, n);
return cam;
}
``````
``````float3 normal = getNormal(cam.ray);
cam = reflectRay(cam, normal, eps);
``````

``````if (!hit) {
col = mix(float3(.8, .8, .4), float3(.4, .4, 1.),
cam.ray.dir.y);
} else {
float3 n = getNormal(cam.ray);
float o = ao(cam.ray.origin, n);
col = col * o;
}
``````
``````col *= mix(float3(0.8, 0.8, 0.4), float3(0.4, 0.4, 1.0),
cam.ray.dir.y);
``````

``````float3 camPos = float3(15.0, 7.0, 0.0);
``````
``````float3 camPos = float3(sin(time) * 15.0,
sin(time) * 5.0 + 7.0,
cos(time) * 15.0);
``````

## Refraction

The law of refraction is a little more complicated than simple equality between the incoming and outgoing light vector angles.

``````sin(theta2) = sin(theta1) / 1.33
``````
``````bool inside = false;
``````
``````float2 dist = distToScene(cam.ray);
``````
``````dist.x *= inside ? -1.0 : 1.0;
``````
``````float3 normal = getNormal(cam.ray);
``````
``````if (dist.x < eps) {
``````
``````float3 normal = getNormal(cam.ray) * (inside ? -1.0 : 1.0);
``````
``````cam = reflectRay(cam, normal, eps);
``````
``````// 1
else if (closestObject == SphereObj) {
inside = !inside;
// 2
float ior = inside ? 1.0 / 1.33 : 1.33;
cam = refractRay(cam, normal, eps, ior);
}
``````
``````Camera refractRay(Camera cam, float3 n, float eps, float ior) {
cam.ray.origin -= n * eps * 2.0;
cam.ray.dir = refract(cam.ray.dir, n, ior);
return cam;
}
``````

## Raytraced Water

It’s relatively straightforward to create a cheap, fake water-like effect on the sphere.

``````float object = (dtp > dts) ? SphereObj : PlaneObj;
``````
``````if (object == SphereObj) {
// 1
float3 pos = r.origin;
pos += float3(sin(pos.y * 5.0),
sin(pos.z * 5.0),
sin(pos.x * 5.0)) * 0.05;
// 2
Ray ray = Ray{pos, r.dir};
dts = distToSphere(ray, s);
}
``````
``````cam.ray.origin += cam.ray.dir * dist.x;
``````
``````cam.ray.origin += cam.ray.dir * dist.x * 0.5;
``````

## Key Points

• The rendering equation is the gold standard of realistic rendering. It describes conservation of energy where the sum of incoming light must equal outgoing light.
• Reflection depends on the angle of the incoming light and the surface normal.
• Refraction takes into account the medium’s index of refraction, which defines the speed at which light travels through the medium.

## Where to Go From Here?

If you want to explore more about water rendering, the references.markdown file for this chapter contains links to interesting articles.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.