ben's notes

Visibility and Shading

Overview #

Here’s the main steps for rasterizing an image:

  • position objects in the world using transforms
  • compute the position of objects relative to the camera
  • project objects onto the screen
  • sample and interpolate triangles using barycentric coordinates
  • add texture maps
  • disable objects that are not visible to the camera
  • add shading

Visibility #

Objects can overlap others, such that some will not be visible to the viewer.

The Painter’s Algorithm is inspired by the process of painting an image: if you paint back to front, the images in the foreground will hide the background behind it.

  • In practice, this requires sorting objects by depth (n log n problem). As such, it is not typically used.

The Z-buffer algorithm is a more optimized algorithm that stores the current min z-value for each sample position using a buffer. This algorithm is $O(N)$ with repsect to the number of triangles.

for T in triangles:
	for (x, y, z) in samples(T):
		if z < zbuffer[x, y]:
			framebuffer[x,y] = color[x,y,z]
			zbuffer[x,y] = z
		# else do nothing
  • This can still be inefficient if you get unlucky and/or objects are sorted in reverse order. One mitigation for this is to do an approximate sort before Z-buffer.
  • Another issue is transparency (drawing a transparent object in between two existing objects will not work). A common solution is to draw all opaque polygons first, then draw transparent polygons in sorted order.
    • Another solution is to store a linked list of transparent objects, and insert objects into the list when needed before rendering.

Shading #

The last component of rasterizing is to light objects.

Local shading #

Main idea: compute the amount of light reflected toward the camera

  • $l$: direction of light
  • $n$: normal vector
  • $v$: direction of camera

By Lambert’s cosine law, the light per unit area is proportional to $l \cdot n$, which is equal to $\cos \theta$ assuming all vectors are normalized (where $\theta$ is the angle between $l$ and $n$).

Falloff: Light intensity decreases by distance based on $1/r^2$. This formula is used for global shading. However, for local shading, we approximate intensity using $1/r$, since we often want a more gradual dropoff in order to better light far-away objects.

Diffuse Shading #

Diffuse Lighting: Also known as Lambertian shading. Creates a matte appearance

$$L_d = k_d (I/r^2) \max (0, n \cdot l)$$
  • $L_d$ = diffusely reflected light
  • $k_d$ = diffuse coefficient (rgb value)
  • $I$ = illumination (strength of light)
  • $r$ = distance from light
  • $l \cdot n$: shading effect due to Lambert’s law
  • In practice, this is a vector operation (do it once for each RGB color channel).

Specular Shading #

Specular Lighting creates a shiny appearance.

  • Let $h$ be the half-angle vector between $l$ and $v$, computed using $$h = \frac{v+l}{||v+l||}$$ Then, we can compute the specularly reflected light using the formula $$L_s = k_s (I/r^2) \max(0, n \cdot h)^p$$
  • $k_s$ is the specular coefficient (rgb value)
  • $p$ is the power (larger $p$ = narrower, brighter lobe; smaller $p$ = wider lobe). A theoretically perfect mirror would have $p = \infty$. $p$ can be varied using a texture map.
  • .

Point vs Direction lighting #

A point light exists in world space. As you move around the surface of an object, the relative location of the light changes.

A directional light is modeled to be infinitely far away (example: the sun). In this case, the direction of $l$ is constant, and equal to the negated direction of the light.

Ambient lighting #

Ambient lighting is constant, and does not depend on anything.

$$L_a = k_a I$$

Overall lighting #

If we add the ambient, diffuse, and specular lighting values, we get the total illumination under the Blinn-Phong Reflection Model.

$$L = L_a + L_d + L_s $$

BRDF #

BRDF stands for “Bidirectional Reflectance Distribution Function.”

A BRDF takes: surface material, light direction, viewer direction, and orientation of surface; and outputs a color value. BRDFs can get complex, and are used to model real-life materials/mechanics (such as paints, subsurface scattering…)

Shading Frequency #

There are three main ways we can shade an object:

  • Shade each triangle (flat shading): calculate one normal vector per triangle. This is not good for smooth surfaces.
  • Shade each vertex (Gouraud shading): interpolate colors from vertices of triangles, where each vertex has a normal vector.
  • Shade each pixel (Phuong shading): interpolate normal vectors across each triangle. This is more costly.

Calculating Vertex Normals #

Getting a surface normal of a triangle is fairly straightforward (just take the cross product of two edges).

Vertex normals are slightly more complicated, since vertices technically don’t have normal vectors. A basic strategy we could use is to take the average of the surrounding face normals.

To get per-pixel normals, we can use interpolation on vertex normals.

To transform a normal vector, use the inverse transpose of the transform matrix.