university home page


Filed in: CSharp.08022008 · Modified on : Thu, 21 Feb 08

Shaders in OpenGL (with TAO and GLFW)

Here's an example of using GLSL shaders in C#. I've made the code fairly big so you can figure out how to do most of what you're likely to be wanting to do.

First of all, the shaders:

uniform float fisheye;	// decide whether we do gl projection or custom fisheye projection
uniform bool textured;	// variable used to determine if an object is textured or not

// three vectors used for lighting calculations
vec4 Ambient;
vec4 Diffuse;
vec4 Specular;

// lighting contribution from a point light source - includes attenuation
void pointLight(in int i, in vec3 normal, in vec3 eye, in vec3 ecPosition3)
   float nDotVP;       // normal . light direction
   float nDotHV;       // normal . light half vector
   float pf;           // power factor
   float attenuation;  // computed attenuation factor
   float d;            // distance from surface to light source
   vec3  VP;           // direction from surface to light position
   vec3  halfVector;   // direction of maximum highlights

   // Compute vector from surface to light position
   VP = gl_LightSource[i].position - ecPosition3;

   // Compute distance between surface and light position
   d = length(VP);

   // Normalize the vector from surface to light position
   VP = normalize(VP);

   // Compute attenuation
   attenuation = 1.0 / (gl_LightSource[i].constantAttenuation +
       gl_LightSource[i].linearAttenuation * d +
       gl_LightSource[i].quadraticAttenuation * d * d);

   halfVector = normalize(VP + eye);

   nDotVP = max(0.0, dot(normal, VP));
   nDotHV = max(0.0, dot(normal, halfVector));

   if (nDotVP == 0.0)
       pf = 0.0;
       pf = pow(nDotHV, gl_FrontMaterial.shininess);

   Ambient  += gl_LightSource[i].ambient * attenuation;
   Diffuse  += gl_LightSource[i].diffuse * nDotVP * attenuation;
   Specular += gl_LightSource[i].specular * pf * attenuation;

// lighting contribution from a directional light
void directionalLight(in int i, in vec3 normal)
   float nDotVP;         // normal . light direction
   float nDotHV;         // normal . light half vector
   float pf;             // power factor

   nDotVP = max(0.0, dot( normal, normalize(vec3(gl_LightSource[i].position)) ));
   nDotHV = max(0.0, dot( normal, vec3(gl_LightSource[i].halfVector) ));

   if (nDotVP == 0.0)
       pf = 0.0;
       pf = pow(nDotHV, gl_FrontMaterial.shininess);

   Ambient  += gl_LightSource[i].ambient;
   Diffuse  += gl_LightSource[i].diffuse * nDotVP;
   Specular += gl_LightSource[i].specular * pf;

// lighting code . . . adds contributions from OpenGL lights
void flight(in vec3 normal, in vec4 ecPosition)
    vec4 color;
    vec3 ecPosition3;
    vec3 eye = vec3(0.0,0.0,1.0);

    ecPosition3 = (vec3 (ecPosition)) / ecPosition.w;

    // Clear the light intensity accumulators
    Ambient  = vec4 (0.0);
    Diffuse  = vec4 (0.0);
    Specular = vec4 (0.0);

    pointLight(0, normal, eye, ecPosition3);
    directionalLight(1, normal);

    // color = gl_FrontLightModelProduct.sceneColor + Ambient  * gl_FrontMaterial.ambient + Diffuse  * gl_FrontMaterial.diffuse;    // for normal gl materials
    color = gl_FrontLightModelProduct.sceneColor + (Ambient + Diffuse) * gl_Color; // gl color material 
    color += Specular * gl_FrontMaterial.specular;
    color = clamp( color, 0.0, 1.0 );
    gl_FrontColor = color;

void main (void)
    // Eye-coordinate position of vertex, needed in various calculations
    vec4 ecPosition = gl_ModelViewMatrix * gl_Vertex;

	if (fisheye <= 0.0)
		gl_Position = ftransform(); // ftransform() is almost the same as gl_ModelViewProjectionMatrix * gl_Vertex;        
		vec3 eye3 = vec3(ecPosition) * (1.0 / ecPosition.w);	

		// change to polar coordinates
		float radius = 50.0;  // fisheye hemisphere radius
		vec3 p = eye3;
		vec3 pn = p;
		float d = length(p);
		pn = normalize(p);
		d = d / radius;	    
		float u = atan2(pn.y,pn.x);  
		float v = 2.0 * acos(-pn.z) / 3.141529265358979;

		// change polar to cartesian coordinates on circle (with depth)
		gl_Position.x = cos(u) * v;
		gl_Position.y = sin(u) * v;
		gl_Position.z = d;// * -1.0 * abs(p.z) / p.z;
		gl_Position.w = 1.0;	

    vec3 normal = gl_NormalMatrix * gl_Normal;
    normal = normalize(normal);

    // do lighting calculation
    flight(normal, ecPosition);

    // pass on the texture coordinate
    gl_TexCoord[0] = gl_MultiTexCoord0;

Okay, so this is the vertex shader. As you can see most of the code is necessary for directional and point lights. This code actually started with auto-generated code created by 3DLabs GLSL ShaderGen (see I've added to uniform variables, one to control the type of projection performed, the other to assist in determining whether texture mapping is desired or not. The fisheye variable should be bool but this was causing problems (I could not retrieve a proper uniform variable reference for it in the application) so I made it a float and it magically worked.

Now, onto the fragment shader . . .

uniform sampler2D texUnit0;
uniform float fisheye;
uniform bool textured;

void main (void) 
	vec4 color = gl_Color;
	if (textured)
		color = color * texture2D(texUnit0, gl_TexCoord[0].xy);
	gl_FragColor = color;

Very straightforward, nothing exciting.

Now onto the application itself. For this application I added references to Microsoft.DirectX, Tao.Glfw, and Tao.OpenGL. I've also added the two shader files to the project and included them in the executable as an embedded resource (this is a file property under build action). I've also included a texture (water.bmp) as another embedded resource in the project.

using System;
using Tao.OpenGl;
using Tao.Glfw;
using System.Windows.Forms;
using Microsoft.DirectX;

namespace CSharpGLBase
    static class Program
        static bool exit = false;
        static bool verbose = true; // causes program to spew extra info to console
        static string windowTitle;
        const int screenWidth = 1024;
        const int screenHeight = 768;
        static float angle = 0; // controls rotation of cube
        static float xTrn=0, zTrn=0; // controls translation of camera
        static int TexID = 0;
        static Glu.GLUquadric SphereID;
        static int vs_object, fs_object, program; // shader variables
        static int shaderChoice = 0;
        static int fisheyeUniformVar = 0;
        static int texturedUniformVar = 0;
        static bool doFisheye = false;
I'm going to insert little comments here and there throughout this large file as you see here. Notice that everything is static as we're not actually instantiating this object.
        static void Main()
            double t, t0, fps;
            int frames=0;


            t0 = Glfw.glfwGetTime();
            while (!exit)

                if (frames++ > 40)
                    t = Glfw.glfwGetTime();
                    fps = (double)frames / (t - t0);
                    Glfw.glfwSetWindowTitle(windowTitle + "     FPS: " + fps.ToString("#.##"));
                    t0 = t;
                    frames = 0;
                } // end if


            } // end while

That's the main event loop. Basically we do the GLFW initialization, the OpenGL initialization, then we loop, polling events, calculating FPS, and rendering.
        static void initGLFW()
            Glfw.glfwOpenWindow(1024, 768, 8, 8, 8, 8, 16, 0, Glfw.GLFW_WINDOW);
            windowTitle = " OpenGL Version " + Gl.glGetString(Gl.GL_VERSION) + " on " + Gl.glGetString(Gl.GL_RENDERER);

            Glfw.glfwEnable(Glfw.GLFW_KEY_REPEAT); // allow for repeating keys when key held down
            Glfw.glfwSwapInterval(1); // wait for at least 1 vertical refresh before swapping buffers (0 will remove any vsync)

            // do OpenGL initialization stuff

            // set callback functions for various and sundry events
            Glfw.GLFWwindowsizefun ResizeCallback = new Glfw.GLFWwindowsizefun(Resize);
            Glfw.GLFWwindowclosefun CloseCallback = new Glfw.GLFWwindowclosefun(Close);
            Glfw.GLFWkeyfun KeyCallback = new Glfw.GLFWkeyfun(KeyHandler);
            Glfw.GLFWmousebuttonfun MouseCallback = new Glfw.GLFWmousebuttonfun(MouseButtonHandler);
            Glfw.GLFWmouseposfun MouseMoveCallback = new Glfw.GLFWmouseposfun(MouseMoveHandler);
            Glfw.GLFWmousewheelfun MouseWheelCallback = new Glfw.GLFWmousewheelfun(MouseWheelHandler);
Nothing very exciting, just setting callback functions for event handling.
        static void Resize(int w, int h)
            if (h == 0) h = 1;
            double ratio = 1.0 * w / h;
            Gl.glViewport(0, 0, w, h);
            Glu.gluPerspective(45.0, ratio, 1.0, 64.0);

        static int Close()
            exit = true;
            return 0;

        static void GLInit()
            Gl.glShadeModel(Gl.GL_SMOOTH);                                      // Enable Smooth Shading
            Gl.glClearColor(0, 0, 0, 0);                                     // Black Background
            Gl.glEnable(Gl.GL_DEPTH_TEST);                                      // Enables Depth Testing
            Gl.glHint(Gl.GL_PERSPECTIVE_CORRECTION_HINT, Gl.GL_NICEST);         // Really Nice Perspective Calculations        

            // load texture
            System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
            System.IO.Stream stream = assembly.GetManifestResourceStream("CSharpGLBase.water.bmp");
            TexID = Helpers.OpenGLUtilities.LoadTexture(stream);

            // create sphere
            SphereID = Glu.gluNewQuadric();

            // turn on the lights
	    float[] mat_shininess = { 50 };
            float[] light1_position = { -1, 2, 3, 0 };
	    float[] white_light = {1, 1, 1, 1};
	     float[] lmodel_ambient = {0.1f, 0.1f, 0.1f, 1};

	    Gl.glShadeModel( Gl.GL_SMOOTH );
	    Gl.glMaterialfv(Gl.GL_FRONT, Gl.GL_SHININESS, mat_shininess);
	    //Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_POSITION, light0_position); // note we don't need light0 now, this is done in GLPaint() 
	    Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_DIFFUSE, white_light);
	    Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_SPECULAR, white_light);
	    Gl.glLightfv(Gl.GL_LIGHT1, Gl.GL_POSITION, light1_position);
	    Gl.glLightfv(Gl.GL_LIGHT1, Gl.GL_DIFFUSE, white_light);
	    Gl.glLightfv(Gl.GL_LIGHT1, Gl.GL_SPECULAR, white_light);
	    Gl.glLightModelfv(Gl.GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
            // put in some default colors
            Gl.glMaterialfv(Gl.GL_FRONT_AND_BACK, Gl.GL_AMBIENT_AND_DIFFUSE, white_light);
            Gl.glMaterialfv(Gl.GL_FRONT_AND_BACK, Gl.GL_SPECULAR, white_light);
            // enable color material - note for some reason OpenGL does not automatically place the 
            // colors in the gl_Front_Material variable so be careful to use gl_Color in the shader instead.
            Gl.glColorMaterial(Gl.GL_FRONT_AND_BACK, Gl.GL_AMBIENT_AND_DIFFUSE);

            #region shader initialization
            stream = assembly.GetManifestResourceStream("CSharpGLBase.default.vert");
            vs_object = Helpers.OpenGLUtilities.createAndCompileShader(stream,Gl.GL_VERTEX_SHADER);
            stream = assembly.GetManifestResourceStream("CSharpGLBase.default.frag");
            fs_object = Helpers.OpenGLUtilities.createAndCompileShader(stream,Gl.GL_FRAGMENT_SHADER);

            program = Helpers.OpenGLUtilities.createAttachLinkShaderProgram(vs_object, fs_object);

            // get shader variables
            fisheyeUniformVar = Gl.glGetUniformLocation(program, "fisheye");
            texturedUniformVar = Gl.glGetUniformLocation(program, "textured");

            #endregion shader initialization
Ok, here's the start of the meat and potatoes. First of all I load a texture. The details of this are left out, eventually you'll seem them in the next file (OpenGLUtilities). All that stuff with System.Reflection.Assembly is necessary to get at the embedded resource files. Then we go on to OpenGL lighting code. Be sure to note the comment about COLOR_MATERIALS. Seems weird that OpenGL does not bother to copy over when this is enabled, but oh well. Then comes code for loading the shaders and the shader program. The details of these will be covered when we get to OpenGLUtilities. Last of all we get the shader variables (this is where the bool fisheye variable was causing problems so I turned it into a float).
        static void GLPaint()
            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

            switch (shaderChoice)
                case 0:
                case 1:
                    throw new Exception("Invalid shaderChoice value");

            Glu.gluLookAt(0.0f, 2.0f, 0.0f, 0.0f, 0.0f, 5.0f, 0.0f, 1.0f, 0.0f);          
            Gl.glTranslatef(xTrn, 0, zTrn + 5.0f);

            float[] lpos = { -10, 1, -2, 1};
            Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_POSITION, lpos);

            // draw cube
            Gl.glRotatef(angle, 0.0f, 1.0f, 0.0f);

            Gl.glUniform1i(texturedUniformVar, 0); // make the shader note that this is not shaded
            Gl.glTranslatef(2, 0, 0);
            Gl.glColor3f(1, 0, 0);
            Glu.gluSphere(SphereID, 0.5, 10, 10);

            angle += 0.3f;
Code to draw basic geometry (a rotating cube and a gluSphere. I'm leaving out the drawCube() code as it's pretty basic. If you're interested download the code.
        #region event handlers
        static void KeyHandler(int key, int action)
            // see if a key has actually been pressed
            if (action != Glfw.GLFW_PRESS)

            const float speed = 0.1f;

            switch (key)
                case Glfw.GLFW_KEY_ESC:
                    exit = true;
                case Glfw.GLFW_KEY_LEFT:
                    xTrn += speed;
                case Glfw.GLFW_KEY_RIGHT:
                    xTrn -= speed;
                case Glfw.GLFW_KEY_UP:
                    zTrn += speed;
                case Glfw.GLFW_KEY_DOWN:
                    zTrn -= speed;
                case Glfw.GLFW_KEY_F1:
                    shaderChoice = 0;
                case Glfw.GLFW_KEY_F2:
                    shaderChoice = 1;
                case (int)'P':
                    if (doFisheye)                    
                        Gl.glUniform1f(fisheyeUniformVar, 0.0f);
                        Gl.glUniform1f(fisheyeUniformVar, 1.0f);
                    doFisheye = !doFisheye;
            } // end switch
        } // end key handler

        static void MouseButtonHandler(int mouseButton, int action)
            if (action != Glfw.GLFW_PRESS)

            switch (mouseButton)
                case Glfw.GLFW_MOUSE_BUTTON_LEFT:
                    Console.WriteLine("left mouse pressed");
                case Glfw.GLFW_MOUSE_BUTTON_RIGHT:
                    Console.WriteLine("right mouse pressed");
                case Glfw.GLFW_MOUSE_BUTTON_MIDDLE:
                    Console.WriteLine("middle mouse pressed");
            } // end switch

        private static int oldMouseX=-1;
        private static int oldMouseY=-1;
        static void MouseMoveHandler(int mouseX, int mouseY)
            oldMouseX = mouseX;
            oldMouseY = mouseY;

        static void MouseWheelHandler(int wheelpos)
            Console.WriteLine("mouse wheel moved to " + wheelpos);
        #endregion event handlers
    } // end class
} // end namespace
And that's about it. Now last but not least is the my OpenGL Utilities class.
using System;
using System.Drawing;
using Tao.OpenGl;

namespace Helpers
    public class OpenGLUtilities
	public static int LoadTexture(string path)
            if (!System.IO.File.Exists(path))
                throw new Exception("Helpers.OpenGLUtilities::LoadTexture - Error, file " + path + " does not exist.");
            System.IO.Stream s = new System.IO.FileStream(path, System.IO.FileMode.Open);
            return LoadTexture(s);
        } // end load texture

        public static int LoadTexture(System.IO.Stream texfile)
            if (texfile == null)
                throw new Exception("Helpers.OpenGLUtilities::LoadTexture - Error, null stream passed as texfile parameter.");

            Bitmap[] textureImage = new Bitmap[1];
            textureImage[0] = new Bitmap(texfile);
            int[] texture = new int[1];

            // Check For Errors, if bitmap's not found complain loudly
            if (textureImage[0] != null)
                Gl.glGenTextures(1, texture);  // Create The Texture
                textureImage[0].RotateFlip(RotateFlipType.RotateNoneFlipY);     // Flip The Bitmap Along The Y-Axis
                // Rectangle For Locking The Bitmap In Memory
                Rectangle rectangle = new Rectangle(0, 0, textureImage[0].Width, textureImage[0].Height);
                // Get The Bitmap's Pixel Data From The Locked Bitmap
                System.Drawing.Imaging.BitmapData bitmapData = textureImage[0].LockBits(rectangle,
                     System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                // Typical Texture Generation Using Data From The Bitmap
                Gl.glBindTexture(Gl.GL_TEXTURE_2D, texture[0]);
                Gl.glTexImage2D(Gl.GL_TEXTURE_2D, 0, Gl.GL_RGB8, textureImage[0].Width, textureImage[0].Height, 0, Gl.GL_BGR,
                     Gl.GL_UNSIGNED_BYTE, bitmapData.Scan0);
                Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MIN_FILTER, Gl.GL_LINEAR);
                Gl.glTexParameteri(Gl.GL_TEXTURE_2D, Gl.GL_TEXTURE_MAG_FILTER, Gl.GL_LINEAR);
                // now remove the Bitmap from memory
                if (textureImage[0] != null)
                    textureImage[0].UnlockBits(bitmapData);                     // Unlock The Pixel Data From Memory
                    textureImage[0].Dispose();                                  // Dispose The Bitmap
                throw new Exception("Helpers.OpenGLUtilities::LoadTexture - Error in loading bitmap; stream return null bitmap.");
            return texture[0];
So this is my way of loading texture files. Notice how .Net's pretty Bitmap class does all the heavy lifting for us. Occasionally the new Bitmap(texfile) line of code causes a freeze (it seems to wait forever for the IO). One day when this annoys me enough I'll figure out why. For now I just stop the program and restart.
        public static int createAndCompileShader(System.IO.Stream shaderFileStream, int GLSHADERTYPE)
                throw new Exception("Helpers.OpenGLUtilities::createAndCompileShader - Invalid GLSHADERTYPE, use GL_FRAGMENT_SHADER or GL_VERTEX_SHADER");
            int shader = Gl.glCreateShader(GLSHADERTYPE);

            string[] shaderSource = loadTextFile(shaderFileStream);

            Gl.glShaderSource(shader, shaderSource.Length, shaderSource, null);

            // now test for errors, I'm not sure which error testing is better so for now I'll do both

            int[] status = new int[1];
            Gl.glGetShaderiv(shader, Gl.GL_COMPILE_STATUS, status);
            if (status[0] != Gl.GL_TRUE)
                int[] l = new int[1];
                System.Text.StringBuilder log = new System.Text.StringBuilder(1024);
                Gl.glGetShaderInfoLog(shader, 1024, l, log);
                if (GLSHADERTYPE == Gl.GL_FRAGMENT_SHADER)
                    Console.Write("FRAGMENT ");
                    Console.Write("VERTEX ");
                Console.WriteLine("SHADER LOG: \n" + log.ToString());
                throw new Exception("Could not compile shader");
            return shader;
Okay, so this loads a vertex or fragment shader for us. It's nice to have a function for this as the code to check the OpenGL logs is a bit messy. Also if you should ever happen to make a fragment shader that doesn't compile this you will notice that this code gives you the errors twice . . . I like redundancy, especially with error messages.
        public static int createAttachLinkShaderProgram(int vShader, int fShader)
            if (vShader <= 0 && fShader <= 0)
                throw new Exception("Error, passing empty shaders to creaetAttachLinkShaderProgram");

            int program = Gl.glCreateProgram();
            if (vShader >= 0)
                Gl.glAttachShader(program, vShader);
            if (fShader >= 0)
                Gl.glAttachShader(program, fShader);

            // test for errors - check program log
            int[] status = new int[1];
            Gl.glGetProgramiv(program, Gl.GL_LINK_STATUS, status);
            if (status[0] != Gl.GL_TRUE)
                int[] l = new int[1];
                System.Text.StringBuilder log = new System.Text.StringBuilder(4024);
                Gl.glGetProgramInfoLog(program, 4024, l, log);
                Console.WriteLine("PROG LOG: " + log.ToString());

                log = new System.Text.StringBuilder(4024);
                Gl.glGetProgramInfoLog(program, 4024, l, log);
                Console.WriteLine("PROG LOG2: " + log.ToString());
                throw new Exception("Problem in linking shaders");
            } // end if

            return program;
So this created a shader program object for you. If you only want a vertex or a fragment shader just pass a 0 to the one you don't want. Again this function is likely to throw redundant error messages.
        /// <summary>
        /// Loads a text file into a string array.  Each line of the text file
        /// forms a string in the array.  Coments in // style are automatically
        /// removed as these seem to causes GLSL compiler errors.
        /// </summary>
        /// <param name="textfile">stream of the file you wish to load</param>
        /// <returns></returns>
        public static string[] loadTextFile(System.IO.Stream textfile)
            if (textfile == null)
                throw new Exception("Error, null textfile stream to loadTextFile");
            System.Collections.ArrayList list = new System.Collections.ArrayList();
            System.IO.StreamReader reader = new System.IO.StreamReader(textfile);
            while (!reader.EndOfStream)
                string s = reader.ReadLine();
                // filter out shader comments . . . these seem to cause compiler errors
                int i = s.IndexOf("//");
                if (i > 0 && i < s.Length)
                    s = s.Substring(0,i);
                if (i != 0)
            return (string[])list.ToArray(typeof(String));

    } // end opengl utilities
} // end helpers namespace
Okay so we've ended with the last bit, code that loads a stream to a texture file into an array of strings. Tao OpenGL shaders need this array of strings to load shaders. The only little tricky thing I've done here is to automatically exclude the C++ style comments from the shaders. For some reason this style of comment was causing the GLSL shader grief.

That's it. One thing I really should have done is to do some sort of checking to ensure we're dealing with OpenGL 2.0+ to ensure we have proper shader support.

Here's a download of the entire project: C# OpenGL GLSL Base Code

Powered by PmWiki