Pythonstuff GLSL in English Pythonstuff GLSL auf Deutsch Pythonstuff GLSL Pythonstuff
PythonStuff Home
 

 

Example 2 - my first GLSL with Pyglet: Phong shading

Based on the previous example we add our first GLSL-shader.

It implements two lights with Diffuse and Phong Shading, looking like this:

Phong Shaded Torus

To run this demo, you need Pyglet and Tristam Macdonald's Library Shader.py. You find everything on the installation page.

(If want to take a look at “shader.py”: the file is in the directory App/Lib/site-packages/shader of the installation package glslpythonpack.zip).

Program description

Here comes the shader code:

from shader import Shader

we import the “Shader” class from Tristam Macdonald's Library Shader.py and create a “shader” object, consisting of two strings:

  • the Vertex Shader
  • the Fragment Shader

by simply

shader = Shader(['... vertex shader code ...'] , ['... fragment shader code ...'])

(of course we use the triple-quote notation for multiline shader programs - we will look at the shader code in a moment).

We define a key (ENTER) to toggle the variable “shaderon” to control the binding of the shader:

...
elif symbol == key.ENTER:
    print 'Shader toggle'
    shaderon = not shaderon
...
if shaderon:
    # bind our shader
    shader.bind()
    if togglefigure:
        batch1.draw()
    else:
        batch2.draw()
    shader.unbind()
else:
    if togglefigure:
        batch1.draw()
    else:
        batch2.draw()
...

(could be a little more elegant, but we also like to switch between batch1 and batch2).

The Vertex Shader Code

Now we look at the shader code - the moment we “bind” the shader, the normal rendering pipe is disabled and we have full control, but also the need to control the calculation of vertices:

void main()
{
    gl_Position = ftransform();
}

This is just a way to say “give me the standard vertex transformations, just as the fixed pipeline”.

In the fragment (or “pixel”) shader we want to calculate lighting - to improve over the fixed pipeline function of calculating lighting at the vertices and interpolate the light values, we interpolate

  • the surface normal
  • the position of the eye (relative to the surface pixel)
  • the position of the (two) lights (relative to the surface pixel)

The resulting vertex program is now:

varying vec3 normal, lightDir0, lightDir1, eyeVec;
void main()
{
    normal = gl_NormalMatrix * gl_Normal;
    vec3 vVertex = vec3(gl_ModelViewMatrix * gl_Vertex);
    lightDir0 = vec3(gl_LightSource[0].position.xyz - vVertex);
    lightDir1 = vec3(gl_LightSource[1].position.xyz - vVertex);
    eyeVec = -vVertex;
    gl_Position = ftransform();
}

For the lighting calculations in the fragment shader we need the vectors normalized - but as errors are introduces by the interpolation process, we postpone the normalization to the fragment shader (If you interpolate component-wise between unit-vectors, the result does not necessarily have unit length). You may save a few GPU-Cycles by normalizing here, but precision will suffer.

The Fragment Shader Code

varying vec3 normal, lightDir0, lightDir1, eyeVec;
void main (void)
{
    vec4 final_color =
    (gl_FrontLightModelProduct.sceneColor * gl_FrontMaterial.ambient) +
    (gl_LightSource[0].ambient * gl_FrontMaterial.ambient) +
    (gl_LightSource[1].ambient * gl_FrontMaterial.ambient);
    gl_FragColor = final_color;
}

This short version just calculates the ambient term in the same way as the fixed pipeline.

Then we add directional lighting:

float lambertTerm0 = dot(N,L0);
if(lambertTerm0 > 0.0)
  {
  final_color += gl_LightSource[0].diffuse *
                 gl_FrontMaterial.diffuse *
                 lambertTerm0;
  }

This is the diffuse term - it depends on the brightness of the light source, the material and the direction of the normal relative to the light source (calculated by the “dot”- or “inner” product). We only bother to calculate it, if the Term is positive, so light and eye is above the surface.

Finally we add a specular highlight. This depends on the half-angle between the eye vector and the light vector (and of the brightness and the material, of course):

vec3 E = normalize(eyeVec);
vec3 R = reflect(-L0, N);
final_color += gl_LightSource[0].specular *
               gl_FrontMaterial.specular *
               pow( max(dot(R, E), 0.0), gl_FrontMaterial.shininess );

We use the material parameters from the fixed pipeline, so this calculation is quite generally usable.

In the example program all the calculations are done twice - for two light sources. Depending on the capabilities of your graphics card, you may easily add additional lights - even loop over an array of light vectors giving you an arbitrary number of lights (at the cost of frame speed).

The result is a smooth surface look - depending on your material parameters it look like porcellain, plastic or smooth cut wood - if you add a texturemap, which is exactly what we will do in the next example: adding a nice texturemap.

If this jumps into GLSL too fast, you may want to look at introductory texts. You could start with

or


Deutschsprachige Version, Start
Impressum & Disclaimer