GLSL: An IntroductionWhat Is GLSL? GLSL (GLslang) is a short term for the official OpenGL Shading Language. GLSL is a C/C++ similar high level programming language for several parts of the graphic card. With GLSL you can code (right up to) short programs, called shaders, which are executed on the GPU. Why Shaders? Until DirectX 8 hardware (GeForce 2 and lower, Radoen 7000 and lower) the graphic pipeline could only be configured, but not be programmed. For example there is the OpenGL lighting model with ambient, diffuse, specular and emissive lighting. This model is mainly used but there are many other models for lighting. In fixed-function OpenGL only this lighting model could be used, no other. With Shaders you are able to write your own lighting model. But thats only one feature of shaders. There are thousands of other really nice possibilities: Shadows, Environment Mapping, Per-Pixel Lighting, Bump Mapping, Parallax Bump Mapping, HDR, and much more! Why GLSL? Shaders are available in OpenGL till 2002 through ARB_vertex_program and ARB_fragment_program extension. But with those extensions you are only able to use assembly shaders. Because of the growing complexity of lighting and shading models assembly shaders are hard to use. GLSL is a high-level shading language, which means that you can write your shader in C/C++ style. This makes shader development much easier! What Is The Difference Between Fixed Function Pipeline And GLSL? There are two types of shaders in GLSL: vertex shaders and fragment shaders. Vertex Shader A vertex shader operates on every vertex. So if you call glVertex* (or glDrawArrays, ) the vertex shader is executed for each vertex. If you use a vertex shader you have nearly full control over what is happening with each vertex. But if you use a vertex shader ALL Per-Vertex operations of the fixed function OpenGL pipeline are replaced (see Figure 1):
For a full overview what a vertex shader replaces and what it does not replace please see reference [1], page 41. So if you want to use a vertex shader you HAVE to do all these things above on your own (of course, only if you need it :-) Fragment Shader A fragment shader operates on every fragment which is produced by rasterization. With fragment shader you have nearly full control over what is happening with each fragment. But just like a vertex shader, a fragment shader replaces ALL Per-Fragment operations of the fixed function OpenGL pipeline (see Figure 1):
For a full overview what a fragment shader replaces and what it does not replace please see reference [1], page 43. Using a fragment shader is just like using a vertex shader, because you HAVE to do all these things above on your own (of course, only if you need it). Figure 1, OpenGL 1.5 fixed function pipeline [1] What Does GLSL Look Like? As mentioned above there are 2 types of shaders, a vertex shader and a fragment shader. Each shader type has other inputs and outputs. Data Types In GLSL There are four main types: float, int, bool and sampler. For the first three types, vector types are available: vec2, vec3, vec4 2D, 3D and 4D floating point vector ivec2, ivec3, ivec4 2D, 3D and 4D integer vector bvec2, bvec3, bvec4 2D, 3D and 4D boolean vectors For floats here are also matrix types: mat2, mat3, mat4 2x2, 3x3, 4x4 floating point matrix Samplers are types representing textures. They are used for texture sampling. Sampler types have to be uniform. They are not allowed to be declared as a non-uniform type. Here are the different sampler types: sampler1D, sampler2D, sampler3D 1D, 2D and 3D texture samplerCube Cube Map texture sampler1Dshadow, sampler2Dshadow 1D and 2D depth-component texture About Attributes, Uniforms And Varyings There are three types of inputs and outputs in a shader: uniforms, attributes and varyings. Uniforms are values which do not change during a rendering, for example the light position or the light color. Uniforms are available in vertex and fragment shaders. Uniforms are read-only. Attributes are only available in vertex shader and they are input values which change every vertex, for example the vertex position or normals. Attributes are read-only. Varyings are used for passing data from a vertex shader to a fragment shader. Varyings are (perspective correct) interpolated across the primitive. Varyings are read-only in fragment shader but are read- and writeable in vertex shader (but be careful, reading a varying type before writing to it will return an undefined value). If you want to use varyings you have to declare the same varying in your vertex shader and in your fragment shader. All uniform, attribute and varying types HAVE to be global. You are not allowed to specify a uniform/attribute/varying type in a function or a void. Built-In Types GLSL has some built-in attributes in a vertex shader: gl_Vertex 4D vector representing the vertex position gl_Normal 3D vector representing the vertex normal gl_Color 4D vector representing the vertex color gl_MultiTexCoordX 4D vector representing the texture coordinate of texture unit X There are some other built-in attributes, see reference [2], page 41 for a full list. GLSL also has some built-in uniforms: gl_ModelViewMatrix 4x4 Matrix representing the model-view matrix. gl_ModelViewProjectionMatrix 4x4 Matrix representing the model-view-projection matrix. gl_NormalMatrix 3x3 Matrix representing the inverse transpose model-view matrix. This matrix is used for normal transformation. There are some other built-in uniforms, like lighting states. See reference [2], page 42 for a full list. GLSL Built-In Varyings: gl_FrontColor 4D vector representing the primitives front color gl_BackColor 4D vector representing the primitives back color gl_TexCoord[X] 4D vector representing the Xth texture coordinate There are some other built-in varyings. See reference [2], page 44 for a full list. And last but not least there are some built-in types which are used for shader output: gl_Position 4D vector representing the final processed vertex position. Only available in vertex shader. gl_FragColor 4D vector representing the final color which is written in the frame buffer. Only available in fragment shader. gl_FragDepth float representing the depth which is written in the depth buffer. Only available in fragment shader. The importance of built-in types is that they are mapped to the OpenGL states. For example if you call glLightfv(GL_LIGHT0, GL_POSITION, my_light_position) this value is available as a uniform using gl_LightSource[0].position in a vertex and/or fragment shader. Generic Types You are also able to specify your own attributes, uniforms and varyings. For example if you want to pass a 3D tangent vector for each vertex from your application to the vertex shader you can specify a Tangent attribute: attribute vec3 Tangent; Here are some other examples: uniform sampler2D my_color_texture; uniform mat4 my_texture_matrix; varying vec3 vertex_to_light_vector; varying vec3 vertex_to_eye_vector; attribute vec3 tangent; attribute vec3 binormal; Language Details GLSL is quite similar to C/C++ but there are some minor differences. See reference [1], page 57 for ANSI C features which are not supported in GLSL. Additionally GLSL has the following features/restrictions: GLSL is 100% type safe. You are not allowed to assign an integer to a float without casting (by constructor): float my_float = 1; // Wont Work! 1 Is An Integer! float my_new_float = 1.0; // Will Work! Casts have to be done using constructors. For example this wont work: vec2 my_vec; ivec2 my_int_vec; my_vec2 = (vec2)my_int_vec; // Wont Work Because No Constructor Is Used! my_vec2 = vec2(my_int_vec); // Will Work! Vectors and matrices can be only be filled with user-data using constructors: vec3 my_vec = vec3(1.0, 1.0, 1.0); mat3 my_mat = mat3(1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0); Vector multiplication is component-wise: vec3 my_vec1 = vec3(5.0, 1.0, 0.0); vec3 my_vec2 = vec3(1.0, 3.0, 4.0); vec3 product = my_vec1 * my_vec2; // Will Return This Vector: (5.0, 3.0, 0.0) Vector with matrix multiplication is also available. gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex This will simply transform the vertex position by the model-view-projection matrix. There are also many built-in function which can (and should) be used: dot a simple dot product cross a simple cross product texture2D used for sampling a texture normalize normalize a vector clamp clamping a vector to a minimum and a maximum For a full list of built-in functions see reference [2], page 46. Each shader must have a main() void. This void is called if the shader is executed. Shader Examples So, up to now we have heard a lot concerning GLSL. But how does a shader look like. Here are some simple examples: Ambient Shader The ambient shader surely is the simplest shader available. Each rendered pixel has one specific color: Vertex Shader: void main() { // Transforming The Vertex gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; } Fragment Shader: Void main() { // Setting Each Pixel To Red gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); } Diffuse Shader The diffuse lighting model is one common used lighting model. Its a little bit harder to implement: Vertex Shader: varying vec3 normal; varying vec3 vertex_to_light_vector; void main() { // Transforming The Vertex gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; // Transforming The Normal To ModelView-Space normal = gl_NormalMatrix * gl_Normal; // Transforming The Vertex Position To ModelView-Space vec4 vertex_in_modelview_space = gl_ModelViewMatrx * gl_Vertex; // Calculating The Vector From The Vertex Position To The Light Position vertex_to_light_vector = vec3(gl_LightSource[0].position vertex_in_modelview_space); } Fragment Shader: varying vec3 normal; varying vec3 vertex_to_light_vector; void main() { // Defining The Material Colors const vec4 AmbientColor = vec4(0.1, 0.0, 0.0, 1.0); const vec4 DiffuseColor = vec4(1.0, 0.0, 0.0, 1.0); // Scaling The Input Vector To Length 1 vec3 normalized_normal = normalize(normal); vec3 normalized_vertex_to_light_vector = normalize(vertex_to_light_vector); // Calculating The Diffuse Term And Clamping It To [0;1] float DiffuseTerm = clamp(dot(normal, vertex_to_light_vector), 0.0, 1.0); // Calculating The Final Color gl_FragColor = AmbientColor + DiffuseColor * DiffuseTerm; } Texture Mapping Shader This is a simple shader for texture mapping. Vertex Shader varying vec2 texture_coordinate; void main() { // Transforming The Vertex gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; // Passing The Texture Coordinate Of Texture Unit 0 To The Fragment Shader texture_coordinate = vec2(gl_MultiTexCoord0); } Fragment Shader varying vec2 texture_coordinate; uniform sampler2D my_color_texture; void main() { // Sampling The Texture And Passing It To The Frame Buffer gl_FragColor = texture2D(my_color_texture, texture_coordinate); } GLSL API How To Use GLSL In Your OpenGL Application All right, we heard how to write shaders and what they are good for. But how do we use them in our OpenGL application? GLSL is available right now through 4 extensions: GL_ARB_shader_objects GL_ARB_shading_language_100 GL_ARB_vertex_shader GL_ARB_fragment_shader All extensions documents are available in the OpenGL extension registry, see reference [3]. GLSL is very similar to C/C++. So if you want to use a GLSL shader you have to do the following steps:
A shader object represents your source code. You are able to pass your source code to a shader object and compile the shader object. A program object represents a useable part of render pipeline. How To Create Those Objects? A Program object is created with the command A Shader object is created with the commend So for example: if you want to create a vertex shader object you have to do the following: GLenum my_vertex_shader; my_vertex_shader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); How To Pass Your Shader Source Code To A Shader Object? This can be done by using void glShaderSourceARB(GLhangleARB shader, GLuint number_strings, const GLcharARB** strings, Glint * length); With this command you are able to pass more than one string to one shader object.
Here an example on how to pass a shader source to a vertex shader: char * my_source; my_source = GetSource(); GLenum my_vertex_shader; my_vertex_shader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); glShaderSourceARB(my_vertex_shader, 1, &my_source, NULL); How To Compile A Shader Object? This can simply be done by using void glCompileShader(GLhandleARB shader); shader is our shader object How to link my shader objects to a program object? First of all we have to create a program object. Then we attach all shader objects which we want to use to this program object using void glAttachObjectARB(GLhandleARB program, GLhandleARB shader); program is our program object and shader is our shader object Then we link our program object with void glLinkProgramARB(GLhandleARB program); program is our program object. How To Use A Program Object? Program objects can be used with void glUseProgramObjectARB(GLhandleARB program); program is our program object If program is 0 then standard fixed function OpenGL is used Putting All Together Here is a code example for loading, compiling, linking and using shaders: char * my_fragment_shader_source; char * my_vertex_shader_source; // Get Vertex And Fragment Shader Sources my_fragment_shader_source = GetFragmentShaderSource(); my_vertex_shader_source = GetVertexShaderSource(); GLenum my_program; GLenum my_vertex_shader; GLenum my_fragment_shader; // Create Shader And Program Objects my_program = glCreateProgramObjectARB(); my_vertex_shader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); my_fragment_shader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); // Load Shader Sources glShaderSourceARB(my_vertex_shader, 1, &my_vertex_shader_source, NULL); glShaderSourceARB(my_fragment_shader, 1, &my_fragment_shader_source, NULL); // Compile The Shaders glCompileShaderARB(my_vertex_shader); glCompileShaderARB(my_fragment_shader); // Attach The Shader Objects To The Program Object glAttachObjectARB(my_program, my_vertex_shader); glAttachObjectARB(my_program, my_fragment_shader); // Link The Program Object glLinkProgramARB(my_program); // Use The Program Object Instead Of Fixed Function OpenGL glUseProgramObjectARB(my_program); If all shaders were compiled successfully and the program object was linked successfully all renderings after glUseProgramObjectARB will be done using our shader. Some Other Important Functions Of course you also can delete (shader and program) objects with void glDeleteObjectARB(GLhandleARB object) Another VERY important command is glGetInfoLogARB(GLhandleARB object, GLsizei maxLenght, GLsizei *length, GLcharARB *infoLog) object is a shader or program object. This function gives you information about shader and program objects. For example: if you are calling this function after a failed compiling you will get information why the compiling failed. Uniforms Uniforms can be passed to the GL using void glUniform{1|2|3|4}{f|i}ARB(GLint location, TYPE val) void glUniform{1|2|3|4}{f|i}vARB(GLint location, GLuint count, const TYPE * vals) void glUniformMatrix{2|3|4|}fvARB(GLint location, GLuint count, GLboolean transpose, const GLfloat * vals) location is the location of the uniform. Getting the uniform location can easily be done with GLint glGetUniformLocationARB(GLhandleARB program, const GLcharARB * name) program is our program object. So heres an example of how to use uniforms: glUseProgramObjectARB(my_program); int my_vec3_location = glGetUniformLocationARB(my_program, my_3d_vector); glUniform3fARB(my_vec3_location, 1.0f, 4.0f, 3.0f); Note that you are not able to pass built-in uniform via glUniform! Generic Attributes Generic Attributes are just like Uniforms: void glVertexAttrib{1|2|3|4}{s|f|d}ARB(GLuint index, TYPE val) void glVertexAttrib{1|2|3|4}{s|f|d}vARB(GLuint index, const TYPE * vals) index is the location if the attribute. Getting the attribute location can also easily be done with GLint glGetAttribLocationARB(GLhandleARB program, const GLcharARB* name) program is our program object. An example: glUseProgramObjectARB(my_program); int my_vec3_location = glGetAttribLocationARB(my_program, my_3d_vector); glBegin(GL_TRIANGLES); glVertexAttrib3f(my_vec3_location, 4.0f, 2.0f, 7.0f); glVertex3f(1.0f, 1.0f, 1.0f); glVertexAttrib3f(my_vec3_location, 2.0f, 9.0f, 2.0f); glVertex3f(-1.0f, 1.0f, 1.0f); glVertexAttrib3f(my_vec3_location, 1.0f, 0.0f, 5.0f); glVertex3f(1.0f, -1.0f, 1.0f); glEnd(); Using Textures With GLSL Because many GLSL beginners have problems with texturing I want to say something about this now. As I said before... textures are available in GLSL via sampler, which have to be uniform. But how do we tell GLSL which texture image should be used for which sampler? Textures are not directly passed to GLSL, but we pass the texture unit where our texture is bound to OpenGL. This works in the following way:
Here an example: glUseProgramObjectARB(my_program); int my_sampler_uniform_location = glGetUniformLocationARB(my_program, my_color_texture); glActiveTexture(GL_TEXTURE0 + i); glBindTexture(GL_TEXTURE_2D, my_texture_object); glUniform1iARB(my_sampler_uniform_location, i); i is the texture unit where I want to bind my texture. There are many other useful functions for using GLSL. See the extension specification or reference [4] for more information. Shader Hardware/Driver Overview Alright, we have heard many about shaders and GLSL. But which hardware is able to run shaders? GLSL is very similar to DirectX Shader Model 3.0 Right now GLSL is available on the following graphic cards: ATI Radeon 9500, 9600, 9700 and 9800 using Catalyst 4.5 driver (which is the most bug-free ATI GLSL driver now). nVidia GeForce FX 5200, 5600, 5700, 5800, 5900, 5950 using forceware 60 series (you can get these drivers at http://www.3dchipset.com/drivers/beta/nvidia/nt5/6111.php ) Because of the fact that these cards only support Shader Model 2.0 some features of GLSL are not available, like loops, vertex shader sampler I don't think anything older than these cards will support GLSL fragment shader. However I think there might be vertex shader emulation in driver software for these cards. GLSL Shader Development Tools There are also some nice shader development tools for GLSL: Typhoon Labs has a really nice shader development tool called Shader Designer. You can get it at http://www.typhoonlabs.com ATI and 3Dlabs are also working together on a GLSL version of RenderMonkey. This tool is not available yet but should come out very soon! GLSL References Finally, I would like to list some nice GLSL references: http://www.clockworkcoders.com/oglsl http://www.3dlabs.com/support/developer/ogl2/index.htm http://www.ati.com/developer/sdk/RadeonSDK/Html/Samples/OpenGL/GLSLSimpleShader.html http://developer.nvidia.com/object/sdk_home.html#NV40_Video_Clips http://esprit.campus.luth.se/~humus Thanks for reading! "If you want to contact my please make sure that it doesn't look like spam! I get a lot of spam mail/ICQ requests... Everything suspicious will be deleted" References: [1] OpenGL Shading Language Master Class Notes, 3DLabs, March/April 2004
|