Also look at the other shaders in the demonstration code. Run the code as
where the <shader name> is one of the following:shader <shader name>
basic shader that computes CCS position and passes through a colour
VCS normal is computed in the vertex shader and passed to fragment shader.
VCS normal comes in to fragment shader and is used in the $N \cdot L$ calculation. Colour is modulated by $N \cdot L$. A negative $N \cdot L$ means the light is behind the surface, so the colour is black.
Attributes coming in to the fragment shader can be 'flat' or 'smooth'. 'flat' attributes are constant across the primitive (e.g. triangle). 'smooth' attributes are interpolated across the primitive from the attribute values on the vertices.
With Gouraud shading, the shading is computed at each vertex, then the shading interpolated across the primitive. This produces poor renderings, but is faster than computing shading on each fragment. Not used these days, as GPUs are fast.
Phong shading is performed in each fragment. This produces good rendering, but is slower than Gouraud shading.
The normal vector of each fragment can be show as an $(r,g,b)$ colour: For normal $(x,y,z)$, $r=x$, $g=y$, $b=z$. Normals in the OCS remain constant with respect to the object. Normals in the VCS remain constant with respect to the viewpoint.
The fragment depth in [0,1] can be computed from the fragment's position in the CCS: Position $(x,y,z,w)$ in the CCS is $(x/w,y/w,z/w)$ in the NDCS. $z/w$ is the depth in [-1,1], which must be mapped to [0,1] to render.
The shading can be quantized to look cartoonish: called 'toon shading'. Typically compute $N \cdot L$ and quantize the result into a few levels.
A fragment is on the silhouette if its normal is near-perpendicular to the viewing direction. This occurs if the dot product between the normal and the view direction is close to zero (i.e. perpendicular vectors). This is done in the VCS. But its better to look for depth discontinuities than for perpendicular normals.