|
Table of Contents
Example 2 - my first GLSL with Pyglet: Phong shadingBased on the previous example we add our first GLSL-shader. It implements two lights with Diffuse and Phong Shading, looking like this:
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 descriptionHere 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:
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 CodeNow 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 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 Codevarying 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 |