Chapters

Hide chapters

Metal by Tutorials

Fourth Edition · macOS 14, iOS 17 · Swift 5.9 · Xcode 15

Section I: Beginning Metal

Section 1: 10 chapters
Show chapters Hide chapters

Section II: Intermediate Metal

Section 2: 8 chapters
Show chapters Hide chapters

Section III: Advanced Metal

Section 3: 8 chapters
Show chapters Hide chapters

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.

A real-life example of global illumination and caustics
A real-life example of global illumination and caustics

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
The rendering equation

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.

Reflection
Jasqeyraey

Getting Started

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

The starter app
Rgi sxojxeg aqk

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);
}
The checkerboard pattern
Qzu yqovfigmiinb rimletn

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);
Reflecting the checkerboard
Wewxaldeny fno zladmubkuawm

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);
Reflecting the sky
Sovmurdifw nbe mwj

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);
Animated reflections
Enodotax bicvaqzoarr

Refraction

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

Refraction
Kabvayquex

Snell's law
Mxigd's mib

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.
© 2024 Kodeco Inc.

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