Processing math: 100%
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 NL and RV.

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×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 kD, kS, kA, IE, 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