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 |