8.1. General Principles
Shader development can be thought of as another form of software engineering; therefore, existing software engineering principles and practices should be brought into play when you are developing shaders. Spend some time designing the shader before writing any code. The design should aim to keep things as simple as possible while still getting the job done. If the shader is part of a larger shader development effort, take care to design the shader for reliability and reuse.
In short, you should treat shader development the same as you would any other software development tasks, allocating appropriate amounts of time for design, implementation, testing, and documentation.
Here are a few more useful thoughts for developing shaders. Consider these to be friendly advice and not mandates. There will be situations in which some of these shader development suggestions make sense and others in which they do not.
8.1.1. Understand the Problem
It is worth reminding yourself periodically that you will be most successful at developing shaders if you understand the problem before you write any of the shader code. The first step is to make sure you understand the rendering algorithm you plan on implementing. If your aim is to develop a shader for bump-mapping, make sure you understand the necessary mathematics before plunging into coding. It is usually easier to think things through with a pencil and paper and get the details straight in your mind before you begin to write code.
Because the tools for developing shaders are currently less powerful than those for developing code intended to run on the CPU, you might consider implementing a simulation of your algorithm on the CPU before coding it in the OpenGL Shading Language. Doing this will let you use the powerful debugging tools available for typical software development, single-step through source code, set breakpoints, and really watch your code in action. Of course, tools should soon be available to let you do these things directly on the graphics hardware as well.
8.1.2. Add Complexity Progressively
Many shaders depend on a combination of details to achieve the desired effect. Develop your shader in such a way that you implement and test the largest and most important details first and add progressive complexity after the basic shader is working. For instance, you may want to develop a shader that combines effects from noise with values read from several texture maps and then performs some unique lighting effects. You can approach this task in a couple of different ways. One way would be to get your unique lighting effects working first with a simple shading model. After testing this part of the shader, you can add the effects from reading the texture maps and thoroughly test again. After this, you can add the noise effects, again, testing as you proceed.
8.1.3. Test and Iterate
Sometimes it is impossible to visualize ahead of time the effect a shader will have. This is particularly true when you are dealing with mathematical functions that are complex or hard to visualize, such as noise. In this case, you may want to parameterize your algorithm and modify the parameters systematically. You can take notes as you modify the parameters and observe the effect. These observations will be useful comments in the shader source, providing insight for someone who might come along later and want to tweak the shader in a different direction.
After you have found a set of parameters that gives you the desired effect, you can consider simplifying the shader by removing some of the "tweakable" parameters and replacing them with constants. This may make your shader less flexible, but it may make it easier for someone coming along later to understand.
8.1.4. Strive for Simplicity
There's a lot to be said for simplicity. Simple shaders are easier to understand and easier to maintain. There's often more than one algorithm for achieving the effect you want. Have you chosen the simplest one? There's often more than one way to implement a particular algorithm. Have you chosen the language features that let you express the algorithm in the simplest way possible?
8.1.5. Exploit Modularity
The OpenGL Shading Language and its API support modular shaders, so take advantage of this capability. Use the principle of "divide and conquer" to develop small, simple modules that are designed to work together. Your lighting modules might all be interchangeable and offer support for standard light source types as well as custom lighting modules. You may also have fog modules that offer a variety of fog effects. If you do things right, you can mix and match any of your lighting modules with any of your fog modules. You can apply this principle to other aspects of shader computation, both for vertex shaders and for fragment shaders.