Raster Surface Stone

This project is about the introduction of lighting concepts and model loading

Software Used  

Languages Used  

Visual Studio

C++

Feel Free to Download the Source Code.
Source Code

Project Topics

Explanations
Dot Product
The Cross Product  
Optimization: Back-face Culling  
Back-face Culling  
What is a “Normal”?  
Face Normals  
What is Light?  
Problems with simulating actual photons  
Lambertian Reflectance  
Specular Reflectance  
Basic Types of Light Sources  
Directional Light Formula  
Point Light Formula  
Spot Light Formula  
Ambient Term
Light Attenuation [Linear]  
Specular Component  
Adding Vertex Lighting (Gouraud Shading)  
Adding Per-Pixel Lighting (Phong Shading)
Physically Based Rendering

Code
Main
Defines
3D Math
Shaders
Raster Functions

Objectives

image

Draw star field

Triangle drawing routine that supports 3D, Texturing and apply Near plane clipping

Load and Draw (including texturing) indexed geometry

Add Directional and Ambient lighting

Add Point light with expanding/shrinking radius (includes attenuation)

Programming Code

Guidance

dot_product

> dot product between two vectors with x,y,z components

cross_product

  > cross product between two vectors with x,y,z components

vec3_normalize

  > normalize a vector with x,y,z components

combine_colors

  > additively combine two colors

modulate_colors

  > multiplicatively combine two colors

saturate

  > clamps a value between 0 and 1

vec3_length

  > computes the vectors length

Here are some important values for your lights before we begin the lighting routine.  

Directional Light: direction = -0.577, -0.577, 0.577; color = 0xFFC0C0F0

Point Light: pos = -1, 0.5, 1;  color = 0xFFFFFF00

In order to do lighting we will accomplish this in the vertex shader.  We will do per-vertex lighting and store the current lighting color in the vertex’s color data member.  In the vertex shader you will transform the vertex’s position and normal into world space by multiplying them by the world matrix.

Programming Explanation

The Dot Product

Can be used to determine the relationship between two vectors of equal length.  

A result of ZERO indicates the two vectors are perpendicular to one another.

A result of 1 or -1 indicates the two vectors are PARALLEL to each other.

It is often important to NORMALIZE vectors before calculating the Dot Product.

The Cross Product

Given two 3D vectors of any length, the cross product can be used to solve a third vector that will be PERPENDICULAR to both.

Notice that any two non-equivalent 3D vectors will form a plane.

The Cross-Product can be used to find the GRADIENT of that plane.

Both the cross product & dot product will come in handy when we start to look at how light should interact with our surfaces.

Optimization: Back-face Culling

  • The Cross Product can be used to determine which side of a triangle is facing the screen.
  • Through vector subtraction one may solve two planar vectors aligned with the triangle’s plane.
  • You can then cross these vectors to find a perpendicular vector.
  • Assuming the triangle’s plane is not perfectly parallel to the view direction, the resulting Z value of the cross product will be either positive or negative.
  • You can then order a triangle’s vertices in a consistent fashion based on which way you wish to see them. Either “clockwise” or “counter-clockwise”. (This is known as polygon “winding”)
  • By ignoring “Back-facing” triangles during rasterization you can boost drawing speed.    

Back-face Culling

By winding all of our triangles based on the direction we wish to see them, it becomes possible to detect which side we are looking at using the cross product.

By ignoring “hidden” triangles (such as on the opposite facing side of this cube) we can dramatically decrease time spent rasterizing surfaces that will be obscured anyway.

You can add this check with only a few lines extra lines of code at the top of your triangle drawing routine.

What is a “Normal”?

Remember the “Gradient” from lesson 2? It was a 2D vector perpendicular to the direction of a line.

In a similar fashion a “Normal” is a 3D vector perpendicular to the surface of a plane.

In both cases each one represents whichever direction is considered “Up” relative to that primitive.

Unlike the gradient however, which can be any length. A normal is meant to be “unit length” which indicates it has a magnitude of one.

A triangle’s normal can be found easily by using the cross product and then “normalizing” the resulting 3D vector.

To normalize a vector simply divide each component (xyz) by the current length of the vector.

Face Normals

These are the “true” normals of each primitive in a given mesh. They are calculated as previously shown.

Each vertex assigned to the primitive shares this same exact normal.

As you will see, normals may be used to estimate how much light a surface should receive.

When used to shade a surface, they create a very “faceted” appearance, since each normal reflects the true surface geometry underneath.  

Vertex Normals

Unlike face normals, vertex normals are stored independently for each vertex.

Because of this, vertices on a single primitive can actually be different from one another.

By averaging face normals between overlapping vertices, we can simulate smoother surfaces than we actually have access to.

This comes in handy when shading surfaces that are supposed to be more smooth or organic in nature.  

What is Light?

  • Visible light only makes up a tiny portion of the electromagnetic spectrum.
  • Visible/Infrared/Ultraviolet Light, X-Rays, Gamma Rays & Radio Waves are all composed of Photons which interact with matter by being absorbed, reflected or refracted.
  • Each Photon is a discrete packet of energy (depending on wavelength) traveling at the speed of light. (~3.00×108 m/s)  
  • Different materials absorb different portions of the electromagnetic spectrum. The unabsorbed frequency is typically part of the color we perceive.
  • A vehicle is perceived as blue because it’s paint absorbs more red & green wavelength photons. This is also why a black car gets hotter in the sun than a white car.
  • The microscopic geometry of the surface or its “roughness” also impacts on how light interacts with it. But more on that later…  

Problems with simulating actual photons

  • On a sunny day approximately 2.9x1014 photons of visible light strike one square inch in the span of a single second.
  • The current worlds fastest supercomputer (which takes up a whole building) can only do 3.38x1013  computations in one second.
  • Never mind the fact that a single photon would require far more than a single computation…
  • Another problem is that our triangles are perfectly flat. Even the smoothest surfaces you encounter day to day are not perfectly flat at the microscopic level!
  • To simulate light in real-time we will have to take some pretty massive shortcuts. Thankfully there are a number of ways to “approximate” these surface interactions.

Lambertian Reflectance

his refection model is a simple way to approximate the behavior of a perfectly “lambertian” surface.

A lambertian surface, is a material that absorbs and then randomly scatters all outgoing light rays. (perfect diffusion)

A point on this surface appears to be the same luminance regardless of the angle of the observer.  

This is fair approximation of the behavior of very rough or “matte” surfaces such as wood or unpainted dry clay.    

Specular Reflectance

Unlike lambertian reflectance, specular reflectance is designed to mimic photons bouncing off of the material’s surface before being absorbed & emitted.

This is an effective model for producing the various “highlights” on shiny surfaces.

Generally most visual surfaces can be represented using some combination of lambetinan & specular reflectance.

There are of course some exceptions to this however…

Basic Types of Light Sources

  • Directional Light
  • Typically used to model the overhead sun/moon light from a very distant point.
  • Point Light
  • Models any Omni-directional light source such as torches, lightbulbs and explosions.
  • Spot Light
  • As the name implies, used for conical light sources such as flashlights & head lights.
  • Ambient Term
  • Used to model the “indirect” lighting within a scene. In it’s most basic form simply a flat term added to all surfaces to simulate all the randomly scattered photons bouncing off of surfaces.

Directional Light Formula

LIGHTRATIO = CLAMP( DOT(         -LIGHTDIR, SURFACENORMAL ) )

RESULT = LIGHTRATIO * LIGHTCOLOR * SURFACECOLOR

Directional light sources are actually point light sources at an infinite distance. This assumption works well enough for highly distant light sources such as the Sun.

Point Light Formula

LIGHTDIR = NORMALIZE( LIGHTPOS – SURFACEPOS )

LIGHTRATIO = CLAMP( DOT( LIGHTDIR, SURFACENORMAL ) )

RESULT = LIGHTRATIO * LIGHTCOLOR * SURFACECOLOR

A point light emits light in all directions. To determine the specific incoming light direction for a surface, you must use vector subtraction to solve the vector between the two points.

Spot Light Formula

LIGHTDIR = NORMALIZE( LIGHTPOS – SURFACEPOS ) )

SURFACERATIO = CLAMP( DOT(   -LIGHTDIR, CONEDIR ) )

SPOTFACTOR = ( SURFACERATIO > CONERATIO ) ? 1 : 0

LIGHTRATIO = CLAMP( DOT( LIGHTDIR, SURFACENORMAL ) )

RESULT = SPOTFACTOR * LIGHTRATIO * LIGHTCOLOR * SURFACECOLOR

The spot light is essentially a point light which has been restricted to a cone pointed in a specific direction. The dot product is used to determine if the point falls within the cone.

Ambient Term

LIGHTRATIO =

CLAMP( LIGHTRATIO +

AMBIENTTERM )

While far from realistic, the ambient term helps to fill in the missing sense of scattered light within a scene.

Different environments may have significantly different ambient terms. (ex: Cave vs Beach)

Light Attenuation [Linear]

Falloff by Radius

ATTENUATION =

1.0 – CLAMP( MAGNITUDE( LIGHTPOS – SURFACEPOS ) / LIGHTRADIUS )

ATTENUATION =

1.0 – CLAMP( MAGNITUDE( LIGHTPOS – SURFACEPOS ) / LIGHTRADIUS )

Falloff from Cone Edge

ATTENUATION =

1.0 – CLAMP( ( INNERCONERATIO – SURFACERATIO ) / ( INNERCONERATIO – OUTERCONERATIO ) )

To make any falloff [Quadratic], simply multiply the attenuation by itself.

A quadratic curve is much closer to how lights lose energy over distance.

Specular Component

VIEWDIR =  

NORMALIZE( CAMWORLDPOS – SURFACEPOS )  

HALFVECTOR =  

NORMALIZE(( -LIGHTDIR ) + VIEWDIR )  

INTENSITY = MAX( CLAMP( DOT( NORMAL, HALFVECTOR ))SPECULARPOWER , 0 )

REFLECTEDLIGHT = LIGHTCOLOR * SPECULARINTENSITY * INTENSITY  

Specular highlights represent light that is reflected from a surface to your eye. This adds an extra level of detail & realism to a surface. (ADD this to a light’s lambertian result)

Adding Vertex Lighting (Gouraud Shading)

  • Step 1:
  • Add normals to your vertex structure and compute them or load them alongside other pre-built mesh data. (Normalize them if they are not already unit length)
  • Step 2:
  • Create lighting parameters accessible by your shaders. For example: A directional light might have a color & normalized light direction in world space.
  • Step 3:
  • In the vertex shader use the world space vertex information (normal & position) to calculate the amount of light reaching a vertex. Embed this information into the color of this vertex.
  • Step 4:
  • Use the interpolated light information within the Pixel Shader to shade the surface by multiplying the light hitting the surface by the original surface color. (Modulation)

Adding Per-Pixel Lighting (Phong Shading)

  • Step 1 & 2:
  • Same as per-vertex lighting.
  • Step 3:
  • Add a “world position” variable to your vertex structure. (3 floats)
  • Step 4:
  • Instead of computing the light amount in the vertex shader, move the vertex normal into world space and “save” the world space positon into your new “world position” variable.
  • Step 5:
  • During rasterization you now interpolate both the world normal and the “world position” components of the vertex across the surface of your primitive. Send these to your Pixel Shader.  
  • Step 6:
  • At this point you know the actual position & normal of each individual pixel! Compute the lighting results just like you would in the vertex shader but now with significantly more accuracy!

Physically Based Rendering

Modern real-time computer graphics has started to embrace more physically accurate light transport models such as Cook-Torrence.

Unlike models such as Blinn-Phong which are approximations based on observation, these computations are based on physical material properties and how they actually interact with light. (Dielectrics & Metals)

The cost of such shading, while significantly higher does produce more accurate results for a surface under a variety of different lighting conditions.