up to Schedule & Notes

Shading in Shaders

We'll talk about how local lighting calculations are performed in the GPU-side shaders.

Light Sources

We'll consider two kinds of light source to illuminate our surfaces:

For sources at infinity, the direction to the source is constant, so the direction can be provided to the shaders as a uniform (i.e. global) variable.

For point sources, the position of the source is constant, to the position can be provided to the shaders as a uniform variable. For point sources, the shaders have to compute the direction to the source.

Coordinate System for Lighting Calculations

Lighting calculations on a fragment involve computing $N \cdot L$ and $R \cdot V$.

All of the vectors have to be in the same coordinate system for these calculations to be meaningful.

The calculations are usually performed in the viewpoint coordinate system (VCS):

Shading on the GPU

Shading requires coordination betweent the code on the CPU side (C++ in our case), the code in the vertex shader, and the code in the fragment shader.

CPU-side code

Since the GPU must use the modelview transform (called $MV$, but actually $V \times M$) the CPU-side code must provide $MV$ as a uniform.

The CPU-side code must also provide the $MVP$ transform, as usual, so that the vertex shader can compute gl_Position, which is the vertex position, but in the clipping coordinate system.

If the lighting direction is fixed in the world, the CPU-side code must also provide the world-to-viewpoint transform, $V$, so that the lighting direction can be moved into the VCS.

Finally, the CPU-side code must provide these constants as uniform variables:

Recall that $k_D$, $k_S$, $k_A$, $I_\textrm{E}$, and $n$ are surface properties, so they might be different when rendering different surfaces.

See 03-shaders/shader.cpp in openGL.zip. (But that code is simpler: It does not pass the constant uniforms listed above and it uses a light direction in the VCS.)

Vertex shader code

The vertex shader needs to output to the fragment shader:

For purely diffuse shading, a vertex shader is

#version 300 es

uniform mat4 MVP;		// OCS-to-CCS
uniform mat4 MV;		// OCS-to-VCS

layout (location = 0) in vec3 vertPosition;
layout (location = 1) in vec3 vertNormal;

flat out mediump vec3 colour;
smooth out mediump vec3 normal;

void main()

{
  // calc vertex position in CCS

  gl_Position = MVP * vec4( vertPosition, 1.0f );

  // assign (r,g,b) colour

  colour = 2.0 * vec3( 0.33, 0.42, 0.18 );

  // calc normal in VCS
  //
  // To do this, apply the non-translational parts of MV to the model
  // normal.  The 0.0 as the fourth component of the normal ensures
  // that no translational component is added.

  normal = vec3( MV * vec4( vertNormal, 0.0 ) );
}

See 03-shaders/*.vert in openGL.zip for more examples.

Fragment shader code

The fragment shader does the actual calculation. In the case of purely diffuse shading, a vertex shader is

#version 300 es

uniform mediump vec3 lightDir; // direction *toward* light in VCS

flat in mediump vec3 colour;
smooth in mediump vec3 normal;

out mediump vec4 outputColour;

void main()

{
  mediump float NdotL = dot( normalize(normal), lightDir );

  if (NdotL < 0.0)  // For light behind the surface, there is no illumination
    NdotL = 0.0;

  mediump vec3 diffuseColour = NdotL * colour;

  outputColour = vec4( diffuseColour, 1.0 );
}

See 03-shaders/*.frag in openGL.zip for more examples.


up to Schedule & Notes