7.13. Application Code for Brick ShadersEach shader is going to be a little bit different. Each vertex shader may use a different set of attribute variables or different uniform variables, attribute variables may be bound to different generic vertex attribute index values, and so on. One of the demo programs whose source code is available for download from the 3Dlabs Web site is called ogl2brick. It is a small, clear example of how to create and use a vertex shader and a fragment shader. The code in ogl2brick was derived from an earlier demo program called ogl2demo, written primarily by Barthold Lichtenbelt with contributions from several others. In ogl2brick an "install" function installs the brick shaders that were presented in Chapter 6. We discuss that shader installation function, but first we define a simple function that make it a little easier to set the values of uniform variables. GLint getUniLoc(GLuint program, const GLchar *name) { GLint loc; loc = glGetUniformLocation(program, name); if (loc == -1) printf("No such uniform named \"%s\"\n", name); printOpenGLError(); // Check for OpenGL errors return loc; } Shaders are passed to OpenGL as strings. For our shader installation function, we assume that each of the shaders has been defined as a single string, and pointers to those strings are passed to the following function. This function does all the work to load, compile, link, and install our brick shaders. The function definition and local variables for this function are declared as follows: int installBrickShaders(const GLchar *brickVertex, const GLchar *brickFragment) { GLuint brickVS, brickFS, brickProg; // handles to objects GLint vertCompiled, fragCompiled; // status values GLint linked; The argument brickVertex contains a pointer to the string containing the source code for the brick vertex shader, and the argument brickFragment contains a pointer to the source code for the brick fragment shader. Next, we declare variables to refer to three OpenGL objects: a shader object that stores and compiles the brick vertex shader, a second shader object that stores and compiles the brick fragment shader, and a program object to which the shader objects will be attached. Flags to indicate the status of the compile and link operations are defined next. The first step is to create two empty shader objects, one for the vertex shader and one for the fragment shader: brickVS = glCreateShader(GL_VERTEX_SHADER); brickFS = glCreateShader(GL_FRAGMENT_SHADER); Source code can be loaded into the shader objects after they have been created. The shader objects are empty, and we have a single null terminated string containing the source code for each shader, so we can call glShaderSource as follows: glShaderSource(brickVS, 1, &brickVertex, NULL); glShaderSource(brickFS, 1, &brickFragment, NULL); The shaders are now ready to be compiled. For each shader, we call glCompileShader and then call glGetShader to see what transpired. glCompileShader sets the shader object's GL_COMPILE_STATUS parameter to GL_TRUE if it succeeded and GL_FALSE otherwise. Regardless of whether the compilation succeeded or failed, we print the information log for the shader. If the compilation was unsuccessful, this log will have information about the compilation errors. If the compilation was successful, this log may still have useful information that would help us improve the shader in some way. You would typically check the info log only during application development or after running a shader for the first time on a new platform. The function exits if the compilation of either shader fails. glCompileShader(brickVS); printOpenGLError(); // Check for OpenGL errors glGetShaderiv(brickVS, GL_COMPILE_STATUS, &vertCompiled); printShaderInfoLog(brickVS); glCompileShader(brickFS); printOpenGLError(); // Check for OpenGL errors glGetShaderiv(brickFS, GL_COMPILE_STATUS, &fragCompiled); printShaderInfoLog(brickFS); if (!vertCompiled || !fragCompiled) return 0; This section of code uses the printShaderInfoLog function that we defined previously. At this point, the shaders have been compiled successfully, and we're almost ready to try them out. First, the shader objects need to be attached to a program object so that they can be linked. brickProg = glCreateProgram(); glAttachShader(brickProg, brickVS); glAttachShader(brickProg, brickFS); The program object is linked with glLinkProgram. Again, we look at the information log of the program object regardless of whether the link succeeded or failed. There may be useful information for us if we've never tried this shader before. glLinkProgram(brickProg); printOpenGLError(); // Check for OpenGL errors glGetProgramiv(brickProg, GL_LINK_STATUS, &linked); printProgramInfoLog(brickProg); if (!linked) return 0; If we make it to the end of this code, we have a valid program that can become part of current state simply by calling glUseProgram: glUseProgram(brickProg); Before returning from this function, we also want to initialize the values of the uniform variables used in the two shaders. To obtain the location that was assigned by the linker, we query the uniform variable by name, using the getUniLoc function defined previously. Then we use that location to immediately set the initial value of the uniform variable. glUniform3f(getUniLoc(brickProg, "BrickColor"), 1.0, 0.3, 0.2); glUniform3f(getUniLoc(brickProg, "MortarColor"), 0.85, 0.86, 0.84); glUniform2f(getUniLoc(brickProg, "BrickSize"), 0.30, 0.15); glUniform2f(getUniLoc(brickProg, "BrickPct"), 0.90, 0.85); glUniform3f(getUniLoc(brickProg, "LightPosition"), 0.0, 0.0, 4.0); return 1; } When this function returns, the application is ready to draw geometry that will be rendered with our brick shaders. The result of rendering some simple objects with this application code and the shaders described in Chapter 6 is shown in Figure 6.6. The complete C function is shown in Listing 7.3. Listing 7.3. C function for installing brick shaders
|