This chapter describes the shader, a mechanism which allows complex rendering equations to be applied to Performer objects.
The shader is a class which contains a description of a multipass rendering algorithm which can be applied to OpenGL Performer objects. A multipass algorithm is a method of drawing an object multiple times and combining the individual results into a single, much more sophisticated result.
Using the shader, objects can be rendered with arbitrarily complex effects at the cost of rendering speed, since advanced effects may require rendering the object many times.
This chapter describes in detail how to load shaders from files, how to create them programatically, and how to apply them to objects in a scene.
A shader, at its highest level, is a description of how to render an object multiple times to achieve a particular effect that is not possible with only a single rendering pass. Multiple passes can describe more complex effects than single passes since each rendering pass can be different from the other rendering passes. The results of each pass are combined in the framebuffer with the previous passes. For example, if we wanted to render an object with two textures but we only have hardware which can render one texture at a time, we could render the object once for each texture and then add the results (see Figure 10-1).
Figure 10-1 is a very simple example, but in real world terms it could be used to environment map textured objects. Environment mapping works by using a special pfTexGen (see “Automatic Texture Coordinate Generation” in Chapter 9) to wrap a texture representing the environment around the object. This works nicely on untextured objects, but for objects which are textured, we need to render the base texture and the environment texture at the same time. If the hardware for our application does not support applying two textures to an object, we could use the algorithm described above in place of the hardware feature.
The representation of the appearance of an object as multiple rendering passes is very powerful and general. The appearance can be arbitrarily complex since it can translate into any number of rendering passes. This is exactly what a shader encapsulates: the descriptions of multiple rendering passes and how to combine the results.
OpenGL is a powerful language which allows a great deal of control over the appearance of objects it renders. In Chapter 9, you have seen how to specify appearance attributes such as color, lighting, textures, and transparency. All such attributes can be thought of as object attributes, attributes that describe the appearance of a particular object. Object attributes, though powerful, only represent a part of OpenGL's total attribute set. In the context of a multipass algorithm, they allow us to specify the appearance of an object within each pass, but they do not allow us to specify how the multiple passes are to be combined. To specify how blending between multiple passes is carried out, OpenGL Performer 2.4 introduces the pfFBState class. pfFBState stands for "framebuffer state".
The pfFBState class encapsulates such attributes as blending mode, color mask, stencil buffer operation, depth buffer operation, and more. These attributes are not associated with a particular object, but rather express how objects which have already been drawn will be combined with objects to be drawn in the future. The function names in a pfFBState are closely related to the OpenGL functions they call. pfFBState member functions are all described briefly below, but for a thorough explanation consult the OpenGL documentation.
The stencil buffer is used to count how many times something has been drawn at a particular location on the screen, and then based on those counts, it can be used to cut out pieces of objects. The OpenGL functions which control the stencil buffer are glStencilOp(), glStencilFunc() and glStencilMask(). The corresponding pfFBState member functions are as follows:
void pfFBStateStencilOp(pfFBState* fbstate, GLenum stencilfail,
GLenum zfail, GLenum zpass);
voi d pfFBStateStencilFunc(pfFBState* fbstate, GLenum func,
GLint ref, GLuint mask);
void pfFBStateStencilMask(pfFBState* fbstate, GLuint mask);
void pfGetFBStateStencilOp(pfFBState* fbstate, GLenum *stencilfail,
GLenum *zfail, GLenum *zpass);
void pfGetFBStateStencilFunc(pfFBState* fbstate, GLenum *func,
GLint *ref, GLuint *mask);
void pfGetFBStateStencilMask(pfFBState* fbstate, GLuint *mask);
|
OpenGL is capable of blending objects that are being drawn with those that are already present in the framebuffer. Many blending modes are supported, all of which are specified with the OpenGL function glBlendFunc(). The corresponding pfFBState members functions are as follows:
void pfFBStateBlendFunc(pfFBState* fbstate, GLenum sfactor,
GLenum dfactor);
void pfGetFBStateBlendFunc(pfFBState* fbstate, GLenum *sfactor,
GLenum *dfactor);
|
The behavior of the blending function can be fine-tuned with additional information which is specified with the OpenGL glBlendEquation() and glBlendColor() functions. In pfFBState, you make the following calls:
void pfFBStateBlendEquation(pfFBState* fbstate, GLenum mode);
void pfFBStateBlendColor(pfFBState* fbstate, GLfloat r,
GLfloat g, GLfloat b, GLfloat a);
void pfGetFBStateBlendEquation(pfFBState* fbstate, GLenum *mode);
void pfGetFBStateBlendColor(pfFBState* fbstate, GLfloat *r,
GLfloat *g, GLfloat *b, GLfloat *a);
|
The depth buffer is used to determine which objects occlude other objects so that interpenetrating objects are drawn correctly. The behavior of the depth buffer is controlled in OpenGL with the glDepthFunc(), glDepthRange() and glDepthMask() functions. The corresponding pfFBState functions are as follows:
void pfFBStateDepthFunc(pfFBState* fbstate, GLenum func);
void pfFBStateDepthRange(pfFBState* fbstate, GLclampd near,
GLclampd far);
void pfFBStateDepthMask(pfFBState* fbstate, GLboolean flag);
void pfGetFBStateDepthFunc(pfFBState* fbstate, GLenum *func);
void pfGetFBStateDepthRange(pfFBState* fbstate, GLclampd *near,
GLclampd *far);
void pfGetFBStateDepthMask(pfFBState* fbstate, GLboolean *flag);
|
The color mask can be used to enable or disable writing to the red, green, blue and alpha channels in the framebuffer. The color mask is specified in OpenGL using the glColorMask() function. The pfFBState analog is the following:
void pfFBStateColorMask(pfFBState* fbstate, GLboolean r,
GLboolean g, GLboolean b, GLboolean a);
void pfGetFBStateColorMask(pfFBState* fbstate, GLboolean *r,
GLboolean *g, GLboolean *b, GLboolean *a);
|
The color matrix is used to transform colors during pixel transfer operations. Each color that is about to be written to the framebuffer during a pixel transfer operation is processed through this matrix and the resultant color is what is actually drawn. Pixel transfer operations include writing pixels directly to the screen using glDrawPixels(), downloading data to texture memory via the mechanisms in pfTexture, or doing framebuffer to framebuffer copies with glCopyPixels(). The color matrix is specified in OpenGL with the glMatrixMode() and glLoadMatrix() functions. The following pfFBState functions are used to specify and query this 4x4 row-major matrix in OpenGL Performer:
void pfFBStateColorMatrix(pfFBState* fbstate, GLfloat *matrix);
void pfGetFBStateColorMatrix(pfFBState* fbstate,
GLfloat *matrix);
|
Like the color matrix, pixel scale and bias operate on pixel transfer operations. Pixel scale multiplies color values by a constant and pixel bias adds a constant according to the following equation:
Final Color = (Original Color x Scale) + Bias |
All the terms in this equation are (R, G, B, A) values, so there can be a different scale and bias value for each color component. The pixel scale and bias are specified with the OpenGL glPixelTransfer() function. The corresponding functions in pfFBState are the following:
void pfFBStatePixelBias(pfFBState* fbstate, GLfloat r, GLfloat g,
GLfloat b, GLfloat a);
void pfFBStatePixelScale(pfFBState* fbstate, GLfloat r,
GLfloat g, GLfloat b, GLfloat a);
void pfGetFBStatePixelBias(pfFBState* fbstate, GLfloat *r,
GLfloat *g, GLfloat *b, GLfloat *a);
void pfGetFBStatePixelScale(pfFBState* fbstate, GLfloat *r,
GLfloat *g, GLfloat *b, GLfloat *a);
|
Pixel maps are lookup tables which operate on the pixel transfer pipeline. When pixel maps are active, the color which would ordinarily be written into the framebuffer is instead used as an index into a lookup table. The color which is actually written to the framebuffer is the value stored at that index in the lookup table. OpenGL supports a large number of color lookup modes, all of which are controlled with the glPixelMapfv() function. The analogous pfFBState functions take the same parameters. They are as follows:
void pfFBStatePixelMap(pfFBState* fbstate, GLenum which,
GLsizei size, GLfloat *pixmapValues);
void pfGetFBStatePixelMap(pfFBState* fbstate, GLenum which,
GLsizei *size, GLfloat *pixmapValues);
|
The glShadeModel() function is used to toggle between smooth shading and flat shading in OpenGL. pfFBState wraps this function into the following:
void pfFBStateShadeModel(pfFBState* fbstate, GLenum mode); void pfGetFBStateShadeModel(pfFBState* fbstate, GLenum* mode); |
All the functions cited earlier control the behavior of a particular OpenGL feature, but many of these features must be enabled when they are to be used and disabled otherwise. To this end, pfFBState provides the following functions:
void pfFBStateEnable(pfFBState* fbstate, GLenum mode, int flag); void pfGetFBStateEnable(pfFBState* fbstate, GLenum mode, int *flag); |
These functions take a feature to enable or disable as the first parameter and a flag that specifies the enable state. Passing in 0 for the flag will disable the feature and passing in any nonnegative value will enable it. The following are features whose state this function can toggle:
Though pfFBState appears to track state like a pfGeoState, it can not be directly applied to a pfGeoSet.There are two ways to apply the pfFBState: through a shader or within a draw callback. The shader method will be covered later in this chapter.
A pfFBState can be applied in any process which has an OpenGL context. The only process in OpenGL Performer that is guaranteed to have one is the draw process; so draw callbacks are the only place pfFBStates can be applied safely.
Like pfGeoState, pfFBState has an apply function which, when invoked, will change OpenGL state in such a way that it matches the state encoded inside the pfFBState. The following functions are available:
void pfApplyFBState(pfFBState* fbstate); void pfFBStateMakeBasicState(pfFBState* fbstate); void pfFBStateApplyBasicState(void); |
When pfApplyFBState() is called, all the state settings which have been explicitly specified on the pfFBState will be applied, and all those which have not been specified will be inherited from the global pfFBState. The global pfFBState is configured as follows:
GL_DEPTH_TEST is enabled and set to GL_LEQUAL.
GL_STENCIL_TEST is disabled.
GL_BLEND is disabled.
GL_SAMPLE_ALPHA_TO_MASK_SGIS, GL_SAMPLE_ALPHA_TO_ONE_SGIS, and GL_MULTISAMPLE_SGIS are enabled if multisample antialiasing is enabled. On machines without the feature, these will always be disabled.
glColorMask() is set to (GL_ONE, GL_ONE, GL_ONE, GL_ONE).
The color matrix is identity.
glShadeModel() is set to GL_SMOOTH.
Pixel Scale is (1, 1, 1, 1).
Pixel Bias is (0, 0, 0, 0).
No pixel maps are specified and GL_MAP_COLOR is disabled.
Since pfFBStates all inherit state they do not explicitly change from a set of global settings. This means that if two pfFBStates are applied consecutively, the state settings the first one makes will not be inherited by the second. A different way to think of it is that when a pfFBState is applied, the behavior is like that of the global default pfFBState being applied first, followed by the current state.
This section gives a brief overview of the shader and a detailed description of shader passes.
A shader is a class which encapsulates a multipass rendering algorithm which can be applied to the OpenGL Performer scene graph. Shaders operate only on pfGeoSets, but OpenGL Performer supplies helper classes that make it seem as if the shader can be applied anywhere in the scene.
The shader is implemented in Performer through the pfShader class. This class contains functions which are used to specify the multipass operations to perform on objects. pfShaders can be constructed in two ways: programatically and through the helper function pfdLoadShader().
Unlike other OpenGL Performer classes that represent the appearance of objects, pfShaders are not stored within the scene graph. Shaders are mapped to objects in the scene using the helper class pfShaderManager.
A pfShader class contains a list of rendering passes to be applied to geometry. Each of these passes contains state information on how to draw the geometry and how to blend it with what has already been drawn into the framebuffer. Passes are created in the pfShader using a modal interface in which a pass is opened, its attributes are specified and then the pass is closed. This is somewhat similar to the OpenGL glBegin() and glEnd() functions.
The function used to open (create) a pass is pfShaderOpenPass(pfShader *shader, passType). Once a pass has been opened, all subsequent calls to pass modification functions will apply to that pass until it is closed with the pfShaderClosePass() function. pfShaders can have any number of passes.
The following are the four pass types:
Draw geometry
Draw quad
Copy pixels
Accumulation
In any multipass algorithm, the results of each pass must be blended with the results of other passes. To this end, each one of the four pass types contains a pfFBState, which is applied before that pass performs any rendering. The per-pass pfFBState is specified by calling pfShaderPassAttr(pfShader *shader, which, void* attr). The second parameter of this function specifies the attribute to set, which in this case is PF_SHADERPASS_FBSTATE. The third parameter is a pointer to a pfFBState instance.
This pass is created by passing the PF_SHADERPASS_GEOMETRY token to the pfShaderOpenPass() function. This pass will draw the pfGeoSets to which the current shader applies. The draw-geometry pass can be configured with several attributes as shown in Table 10-1. You specify the attributes with the pfShaderPassAttr() function.
Table 10-1. Draw-Geometry Pass Attributes
Attribute | Description |
|---|---|
The framebuffer configuration for rendering this pass. | |
The pfGeoState which will be used in rendering pfGeoSets for this pass. | |
The rendering color for pfGeoSets. This color overrides whatever per-vertex colors the pfGeoSet may have. This attribute does not have any effect if lighting is enabled in this pass' pfGeoState. This is an OpenGL-imposed limitation, pfShader will still make the color state change before rendering, but lighting will override it. |
This pass is created by passing the PF_SHADERPASS_QUAD token to the pfShaderOpenPass() function. The draw-quad pass does not draw the applicable pfGeoSets like the draw-geometry pass, but rather it draws the screen space bounding box of the pfGeoSets. Drawing the bounding box is more desirable for certain operations on geometrically complex pfGeoSets. For example, it is possible to darken the object on the screen by rendering a pass that multiplies its color values by a half. For a very complex pfGeoSet, it will be much faster to render a bounding box than the full pfGeoSet. As shown in Table 10-2, the draw-quad pass supports a set of attributes similar to those supported by the draw-geometry pass:
Table 10-2. Draw-Quad Pass Attributes
Attribute | Description |
|---|---|
The framebuffer configuration for rendering this pass. | |
The pfGeoState which will be used in rendering the bounding boxes for this pass. | |
The rendering color for bounding boxes. This attribute does not have any effect if lighting is enabled in the pass pfGeoState. |
This pass is created by calling pfShaderOpenPass() with the PF_SHADERPASS_COPYPIXELS token as the pass type. The role of this pass is to copy pixels between texture memory and the framebuffer or within the framebuffer. This pass can operate in three modes. First, it can copy the pixel region defined by the bounding box of an object from the framebuffer into texture memory. This mode is most often used to back up the results in the framebuffer for use later in the shading process. Second, it can copy pixels from texture memory to the framebuffer. This mode is used to restore results that have been previously stored in texture memory to the framebuffer. Third, this pass can copy pixels in-place within the framebuffer. This mode copies the pixels within a bounding box of a pfGeoSet back on top of themselves. It is used to apply pixel transfer operations such as the color matrix or color lookup tables.
The modes supported by this pass are specified using the pfShaderPassMode() function. The modes are described in Table 10-3.
Table 10-3. Copy-Pixels Pass Modes
Mode | Description |
|---|---|
This mode copies pixels from the framebuffer into texture memory. | |
This mode copies pixels from the texture memory to the framebuffer. | |
This mode copies pixels from the framebuffer to itself. |
The copy-pixels pass also supports three attributes, which are specified with the pfShaderPassAttr() function. Table 10-4 describes the attributes.
Table 10-4. Copy-Pixels Pass Attributes
Attribute | Description |
|---|---|
The framebuffer configuration for applying this pass. The pfFBState pixel-transfer operations only work in this pass. | |
Depending on the pass mode, this is the texture which will be either the pixel source or destination. This attribute is unused in the PF_COPYPIXELSPASS_IN_PLACE mode. | |
This is a temporary texture ID to use. Temporary textures are mapped to pfTexture objects by the pfShaderManager. |
Certain multipass algorithms use the copy-pixels pass to store a temporary result in texture memory and later restore it to the framebuffer. These temporary textures must be the full size of the output window for the shader to work properly. If each shader has its own temporary textures, a tremendous amount of texture memory would be wasted; so, to get around the problem, shaders support the notion of temporary textures.
A temporary texture is a numeric identifier. When creating a copy-pixels pass, temporary texture identifiers can be used in place of pfTexture pointers. For example, a shader can use a copy-pixels pass to store the contents of the framebuffer to a texture with a particular identifier and restore data from the texture with the same identifier in another copy-pixels pass.
When applying shaders to a scene with the pfShaderManager, the temporary texture identifiers will be mapped to actual pfTexture objects. The shader manager will efficiently allocate the temporary textures so that the smallest possible number of textures is used while rendering a frame. Temporary textures are shared between shaders so that texture memory will not be wasted. The behavior of shaders does not change due to texture sharing, since shaders are applied sequentially; this means that there will be no cross-shader interference with respect to the temporary textures.
Temporary texture identifiers must be allocated within a shader before they can be used; otherwise, the texture may not be substituted for the identifier at rendering time. Texture identifiers are allocated with the pfShaderAllocateTempTexture() function, which performs all the setup and returns an identifier which is safe to use. Each time this function is invoked, it will generate a new identifier.
This pass is created by specifying the PF_SHADERPASS_ACCUM token to the pfShaderOpenPass() function. This pass is used to control the operation of the accumulation buffer. In OpenGL, the accumulation buffer always works on the contents of the entire screen; so, even though this pass type can be applied on a per-pfGeoSet basis, it will always affect the full screen.
The accumulation pass can be configured with a mode and a value. The mode corresponds to the first parameter in the glAccum() function while the value represents the reference value in the glAccum() function call.
The accumulation buffer operation is specified with pfShaderPassMode() function. Table 10-5 describes the modes you can specify.
Table 10-5. Accumulation-Pass Operation
Mode | Description |
|---|---|
The contents of the framebuffer are accumulated with those in the accumulation buffer. | |
The contents of the framebuffer are copied into and replace the contents of the accumulation buffer. | |
The contents of the framebuffer are added to the contents of the accumulation buffer. | |
The contents of the framebuffer are multiplied by the contents of the accumulation buffer and stored in the accumulation buffer. | |
The contents of the accumulation buffer are copied into the framebuffer. |
The value term in the glAccum() function is specified with the pfShaderPassVal() function. The meaning of the value is dependent on the accumulation-pass operation. For a full description, consult the OpenGL documentation.
Table 10-6 describes the accumulation-pass attributes.
Table 10-6. Accumulation-Pass Attributes
Attributes | Description |
|---|---|
The framebuffer configuration for rendering this pass. | |
Controls what happens to the contents of the framebuffer and accumulation buffer. | |
The reference value used for the accumulation operation. The value is used as a parameter to the accumulation mode. |
Table 10-7 summarizes the mapping of pass attributes and the applicable pass types.
Token | Geometry Pass | Quad Pass | Copy Pixels Pass | Accumulation Pass |
|---|---|---|---|---|
PF_SHADERPASS_FBSTATE | Yes | Yes | Yes | Yes |
PF_SHADERPASS_GSTATE (pfShaderPassAttr()) | Yes | Yes | No | No |
PF_SHADERPASS_OVERRIDE_COLOR (pfShaderPassAttr()) | Yes | No | No | No |
PF_SHADERPASS_COLOR (pfShaderPassAttr()) | No | Yes | No | No |
PF_SHADERPASS_TEXTURE (pfShaderPassAttr()) | No | No | Yes | No |
PF_SHADERPASS_COPYPIXELS_DIRECTION | No | No | Yes | No |
PF_SHADERPASS_ACCUM_OP (pfShaderPassMode()) | No | No | No | Yes |
PF_SHADERPASS_TEMP_TEXTURE_ID | No | No | Yes | No |
PF_SHADERPASS_ACCUM_VAL (pfShaderPassVal()) | No | No | No | Yes |
In addition to per-pass state, the shader supports a default state configuration which has effect across the scope of all the passes in the shader. The shader supports a default pfGeoState and a default pfFBState. These default states are specified with the pfShaderDefaultGeoState() and pfShaderDefaultFBState() functions.
Use the default states to specify state elements which are common to many passes within a shader. In this way OpenGL Performer can minimize the number of state changes made during a shader's execution. When a shader is executed, each of its passes inherit state elements from the default shader state, providing that the passes do not explicitly set the same pass elements. As an example, consider a shader whose default state disables lighting and enables texture. All the passes that do not specify a particular state for lighting or texturing in that shader will inherit those settings from the default state. If a pass disables texture, its settings will override those of the default state. A different way of thinking about it is that all the settings of the default state are applied first for each pass, and then the pass-local state is applied. Any state settings that are not specified by the default shader state or by the per-pass state will be set to the OpenGL Performer global default state, which was described earlier.
The pfShaderManager class encapsulates operations which add, remove and apply shaders to the scene graph. Unlike other OpenGL Performer objects such as pfGeoStates and pfNodes, pfShaders do not exist within the scene graph, so the shader manager is used as an alternate mechanism to associate shaders with scene graph nodes. Shaders are applied to nodes with the following function:
pfShaderManagerApplyShader(pfShaderManager* smgr, pfNode *node,pfShader *shader) |
Shaders may be applied to any subclass of a pfNode. The entire subtree rooted at that node will then be shaded by the specified pfShader. Conversely, shaders may be removed from nodes with the following function:
pfShaderManagerRemoveShader(pfShaderManager* smgr, pfNode *node,
int shaderNum)
|
The shader removal function takes a shader number, since multiple shaders may apply to a single node. This function will remove the specified shader from the specified node. The pfShaderManager also supports querying shaders which apply to a node using the following function:
pfGetShaderManagerShader(pfShaderManager* smgr, pfNode *node,
int shaderNum)
|
Calling this function will return the shader at the specified index of the specified node. The number of shaders on a pfNode may be queried with the following function:
pfGetShaderManagerNumShaders(pfShaderManager* smgr, pfNode *node)
|
After mapping all shaders to applicable nodes in the scene graph using the pfShaderManager, the shaders must be pushed down to the pfGeoSet level since all rendering happens on that level. This is accomplished by calling the following function:
pfShaderManagerResolveShaders(pfShaderManager* smgr, pfNode *node)
|
This function compiles all the shaders which are mapped at or below the specified node into a OpenGL Performer internal format, which will then be passed to the pfGeoSets for rendering. During shader resolution, all temporary texture identifiers are mapped to pfTexture objects. The number of pfTextures created will be equal to the number of temporary texture identifiers allocated in the shader with the highest number of temporary textures. Shader resolution is an expensive operation; so, it should be only performed when shaders are either removed from nodes or added to nodes.
Since shaders may be applied anywhere in the scene graph, it often happens that one shader is applied closer to the root of the scene than another. In this case, both shaders will apply to the pfGeoSets that are present under the scene graph nodes that are common to both of them. When multiple shaders are applied to the tree above a given pfGeoSet, all the passes of the shader closest to that pfGeoSet will be rendered first, followed by all the passes of the next closest shader in the path to the root node, and so forth. Figure 10-2 shows a set of shaders mapped to a scene graph using a pfShaderManager and how the shaders end up applying to the pfGeoSets.
Although pfShaders can be constructed programmatically within an application, OpenGL Performer provides a text file format for describing pfShaders.The file format is a simple ASCII format, which allows shader files to be easily written by hand or produced by automated tools. The OpenGL Shader SDK tools islc and ipf2pf can be used to generate OpenGL Performer shader files by compiling and translating files in the Interactive Shading Language format.
Shaders are loaded from files much like database loaders load objects. OpenGL Performer 2.4 introduces the libpfdu routine pfdLoadShader( *shaderFile). This function takes a filename as an argument and attempts to load that shader. If the shader file is nonexistent or it does not parse correctly, this function will return a NULL pfShader pointer; otherwise, it will return a pointer to a valid pfShader object that can be used just as though it was created programatically. The freshly loaded shader will have its temporary texture identifiers set up correctly so that it can be mapped to a node using the pfShaderManager without any modification. As with any shader, pfShaderManagerResolveShaders() must be called for this shader to be passed down to pfGeoSets.
OpenGL Performer shader description files are identified by the the “.shader” suffix. Example shader description files ship with OpenGL Performer in the /usr/share/Performer/shaders/data directory.
This section uses the following topics to describe shader description files:
Data types
Variables
Shader header
Shader passes
State attributes
Examples
The following basic data types are recognized in a shader description file:
| Integer numbers | |
These can be specified in decimal, octal, or hexadecimal notation. Octal notation is indicated by a leading 0 followed by one or more digits, which may take on any value between 0 and 7, inclusive. Hexadecimal notation is indicated by a leading 0x or 0X followed by one or more digits which may take on values between 0 and 9 plus `a' through `f' or `A' through `F'. | |
| Real numbers | |
Any number including a decimal point or exponent. The following are all examples of real numbers from the point of view of pfdLoadShader:1.5 1. .5 0.5 2e2 2e-3 4.5e+4 .4e5 | |
| Vectors | |
A vector is a group of four real or integer numbers surrounded by an opening and closing parentheses pair. Integers and reals can be mixed in a vector since all numbers will be promoted to reals. The following are valid vectors:(1 0 0 1) (1.0 1 0.0 1.0) | |
| Matrices | |
A matrix is a group of four vectors which combine to represent a 4x4 row-major matrix; that is, the first vector is the first row of the matrix, the second vector is the second row of the matrix, etc. | |
| Variable identifiers | |
A variable identifier always begins with the character `$' followed by a letter (`a' through `z' or `A' through `Z') then followed by zero or more letters, the digits 0 through 9, or the character `_'. The following are all valid identifiers:$foo $Foo $FOO $Bar1 $FoO_bArThe following are not identifiers:foo $1foo $foo-bar | |
| Strings | |
Strings are any number of characters between an opening and closing quote pair. | |
| Enumerated constants | |
An enumerated constant always begins with an uppercase letter (`A' through `Z') that may be followed by zero or more uppercase letters, the digits 0 through 9, or the character `_'. The following are all valid enumerated constants:ONE ZERO DST_COLOR LIGHT_1 | |
The shader description file format provides limited support for the declaration and use of variables. As in programming languages such as C, variables must be declared before they may be used. Variable declaration takes place in the shader header while variable use takes place in the definition of the shader passes. Currently, only textures may be represented with variables and once declared in the header, a texture variable may not be changed or assigned to another.
Texture declarations take may take on three different forms depending on the source of the texture. Textures may come from image files, they may be defined as a 1D texture within the shader description file, or they may be used as temporary storage for the pfShader. In the case of temporary storage textures, the data within them comes from, and is restored to, the framebuffer.
Textures loaded from files are declared in the following manner:
texture_def {
texture_id <identifier_type>
texture_src <enumerated_constant_type>
texture_file <string_type>
}
|
The enumerated constant data following the texture_src token must be equal to FILE.
When one of these texture declarations is found in a shader description file, a pfTexture object is created and the image file named by the string data is loaded. If the image file is successfully read from disk, an entry is added to the symbol table which maps the texture identifier to the corresponding pfTexture.
Textures specified as 1D textures within the shader description file are declared as follows:
texture_def {
texture_id <identifier_type>
texture_src <enumerated_constant_type>
texture_table {
texture_table_size <integer_type>
texture_table_data <vector_type>
.
.
.
texture_table_data <vector_type>
}
}
|
The enumerated constant data following the texture_src token must be equal to TABLE.
When one of these texture declarations is found in a shader description file, the entries of the table are loaded into an array and a new pfTexture object is created. The array of table entries is specified as the pfTexture image and the dimensions of the pfTexture are set to indicate a 1D texture. An entry is then added to the symbol table which maps the texture identifier to the corresponding pfTexture.
Textures used as temporary storage during execution of the shader passes are declared as follows:
texture_def {
texture_id <identifier_type>
texture_src <enumerated_constant_type>
}
|
The enumerated constant data following the texture_src token must be equal to TMP.
When one of these texture declarations is found in a shader description file, pfShaderAllocateTempTexture() is called and the returned integer value is entered in the symbol table with the texture identifier for the texture. To provide for some optimization of texture memory usage, temporary storage textures are shared between pfShaders. For this reason, pfTexture objects are not created at load time for temporary textures. Instead, integer IDs are used to indicate which temporary texture is used by a pass. Later, when pfShaders are applied to scene graph nodes, the pfShaderManager can determine the maximum number of pfTexture objects to allocate. Once the pfShaderManager has created the pfTextures, it can resolve the mapping between IDs and actual pfTexture objects in the passes of all pfShaders it finds.
![]() | Note: It is an error to declare the same texture identifier more than once. |
The shader file must contain all tokens and data within a block like the following:
# comments can live outside the shader{} block
shader {
# interesting shader description stuff
# should be inserted here
}
# comments can live outside the shader{} block
|
Note that comments are anything on a line following the `#' character. The `#' character need not be the first character on the line.
Beneath this highest level description, the shader file contains a shader header and a definition of the shader passes.
The shader header describes everything about the shader that will not vary from one pass to another. This includes the name of the shader, the shader file revision, variables local to the shader, and the default state of the shader. Ignoring the placement of whitespace and newlines, the shader header must always be formed in a block like the following:
shader_header {
shader_name <string_type>
shader_major_rev <integer_number_type>
shader_minor_rev <integer_number_type>
shader_locals {
.
.
.
}
shader_default_state {
.
.
.
}
}
|
Any textures which will be used by the shader must be declared within the shader_locals block. See section “Variables” for a description of texture declarations.
![]() | Note: The shader_locals block may be empty. |
The shader_default_state block contains all the state attributes which will not change on a per pass basis. See section “State Attributes”for a description of different state attributes.
![]() | Note: The shader_default_state block may be empty. |
The shader passes definition describes the type of each pass, the order in which the passes are executed, and which state should be enabled for each pass. While there can be a variable number of passes, the shader passes must always be enclosed in a block like the following:
shader_passes {
# pass definitions must be inserted here
}
|
As described earlier in the section “Shading Concepts”, there are four basic pass types:
Draw geometry
Draw quad
Copy pixels
Accumulation
However, since there are three flavors of the copy-pixels pass, we effectively have six different types, which are described as follows:
| Geometry drawing | |
Draws the geometry to which the shader is applied. | |
| Quadritateral drawing | |
Draws the screen space bounding rectangle of the geometry to which the shader is applied. | |
| Copy pixels | |
Copies from the pixels of the screen space bounding rectangle to the pixels of the screen space bounding rectangle. | |
| Copy to texture | |
Copies from the pixels of the screen space bounding rectangle to the currently applied texture. | |
| Copy from texture | |
Copies from the currently applied texture to the pixels of the screen space bounding rectangle. | |
| Accum | |
Applies the current accum operation to the pixels of the screen space bounding rectangle or to the accum buffer depending on the operation. | |
Shader passes are always specified in the following form:
geom_pass {
.
.
.
}
quad_geom_pass {
.
.
.
}
copy_pass {
.
.
.
}
copy_to_tex_pass {
.
.
.
}
copy_from_tex_pass {
.
.
.
}
accum_pass {
.
.
.
}
|
Per-pass attributes are specified within the body of a pass block. Depending upon the pass type, certain state attributes may or may not be valid. The mapping between pass types and state attributes is described in Table 10-8.
Table 10-8. Pass Types and Valid Attributes
Attribute | Geometry Drawing | Quadrilateral Drawing | Copy Pixels | Copy to Texture | Copy from Texture | Accumulation |
|---|---|---|---|---|---|---|
alpha test | valid | valid | valid |
| valid |
|
blend | valid | valid | valid |
| valid |
|
color | valid | valid |
|
|
|
|
color mask | valid | valid | valid |
| valid |
|
color matrix |
|
| valid | valid |
|
|
depth test | valid | valid | valid |
|
|
|
lights | valid | valid |
|
|
|
|
material | valid | valid |
|
|
|
|
pixel bias |
|
| valid | valid |
|
|
pixel scale |
|
| valid | valid |
|
|
pixmaps |
|
| valid | valid |
|
|
shade model | valid | valid |
|
|
|
|
stencil test | valid | valid | valid |
| valid |
|
texenv | valid | valid |
|
|
|
|
texgens | valid | valid |
|
|
|
|
texture | valid | valid |
|
| valid |
|
texture matrix | valid | valid |
|
|
|
|
The following section describes the individual attributes.
State attributes enable and set parameters for various rendering states that apply during execution of shader passes. The section “Shader Passes”describes which attributes are valid for the various pass types, but note that all state attributes are optional; that is, just because a state attribute is valid for a given pass type, it does not mean that it must be specified for that pass. Note also that it is an error to specify an attribute more than once per pass even if it is a valid attribute for that pass.
Each state attribute is described in detail in the following subsections.
The alpha test attribute is specified as follows:
alpha_test {
alpha_test_func <enumeranted_constant_type>
alpha_test_ref <real_number_type>
}
|
The enumerated constant data must be one of the following:
LESS LEQUAL GREATER GEQUAL EQUAL NOTEQUAL ALWAYS |
The real number data is the value to which the current pixel's alpha is compared using the aforementioned alpha test function.
To disable alpha test use this syntax:
alpha_test {
disable
}
|
For more information on the alpha test functions and meaning of the reference value, see the man page for glAlphaFunc().
The blend attribute is specified as follows:
blend {
blend_eqn <enumerated_constant_type>
blend_src <enumerated_constant_type>
blend_dst <enumerated_constant_type>
}
|
or:
blend {
blend_eqn <enumerated_constant_type>
blend_src <enumerated_constant_type>
blend_dst <enumerated_constant_type>
blend_color <vector_type>
}
|
blend_eqn must be followed by an enumerated constant that is one of the following:
ADD SUBTRACT REVERSE_SUBTRACT MIN MAX LOGIC_OP |
blend_src must be followed by an enumerated constant that is one of the following:
ZERO ONE DST_COLOR ONE_MINUS_DST_COLOR SRC_ALPHA ONE_MINUS_SRC_ALPHA DST_ALPHA ONE_MINUS_DST_ALPHA SRC_ALPHA_SATURATE CONSTANT_COLOR ONE_MINUS_CONSTANT_COLOR CONSTANT_ALPHA ONE_MINUS_CONSTANT_ALPHA |
blend_dst must be followed by an enumerated constant that is one of the following:
ZERO ONE SRC_COLOR ONE_MINUS_SRC_COLOR SRC_ALPHA ONE_MINUS_SRC_ALPHA DST_ALPHA ONE_MINUS_DST_ALPHA SRC_ALPHA_SATURATE CONSTANT_COLOR ONE_MINUS_CONSTANT_COLOR CONSTANT_ALPHA ONE_MINUS_CONSTANT_ALPHA |
The vector data following the blend_color token is the constant color that corresponds to the following source and destination constants:
CONSTANT_COLOR ONE_MINUS_CONSTANT_COLOR CONSTANT_ALPHA ONE_MINUS_CONSTANT_ALPHA |
To disable blend use this syntax:
blend {
disable
}
|
For more information on the meanings of the blend equation, source, and destination factors, see the man pages for glBlendEquationEXT() and glBlendFunc().
The color attribute is specified as follows:
color {
color_data <vector_type>
}
|
The vector data following the color_data token specifies the color with which all vertices of the geometry will be rendered. This color overrides any per-vertex color applied to the geometry.
The color mask attribute is specified as follows:
color_mask {
color_mask_data <integer_number_type>
}
|
The lowest four bits of the integer data following the color_mask_data token specify the masking of the red, green, blue, and alpha color components. Bit 3 corresponds to red, bit 2 to green, bit 1 to blue, and bit 0 to alpha. All other bits are ignored.
For more information on the color mask, see the man page for glColorMask().
The color matrix attribute is specified as follows:
color_matrix {
matrix_data <matrix_type>
}
|
The matrix data following the matrix_data token specifies the row-major matrix that will be pushed onto the color matrix stack.
The depth test attribute is specified as follows:
depth_test {
depth_test_func <enumerated_constant_type>
}
|
The enumerated constant data following the depth_test_func token must be one of the following:
LESS LEQUAL GREATER GEQUAL EQUAL NOTEQUAL ALWAYS |
To disable depth test use this syntax:
depth_test {
disable
}
|
For more information on depth testing, see the man page for glDepthFunc().
A variable number of lights may be specified in the shader description file. A group of individual light definitions must be enlosed within a lights block like the following:
lights {
light {
.
.
.
}
.
.
.
light {
.
.
.
}
}
|
Individual lights are defined with multiple, optional parameters, which include the position, ambient color, diffuse color, specular color, etc.
If present in the light definition, these parameters are specified as follows:
light {
light_position <vector_type>
spot_light_cone {
spot_light_cone_exponent <real_number_type>
spot_light_cone_cutoff <real_number_type>
}
light_ambient <vector_type>
light_diffuse <vector_type>
light_specular <vector_type>
light_attenuation {
light_constant_attenuation <real_number_type>
light_linear_attenuation <real_number_type>
light_quadratic_attenuation <real_number_type>
}
}
|
Each light parameter is optional and they may be specified in any order within the light block. However, if specifed in a light definition, a parameter may only be specified once. The following light definition would not be valid:
light {
light_position (1 0 0 1)
light_position (0 10 0 1)
}
|
Note also that the parameters within the spot_light_cone and light_attenuation blocks are optional as well. For instance, if the spot_light_cone block is specified, it is not required that both the spot_light_cone_exponent and spot_light_cone_cutoff parameters be specifed. If only one is specified, the other will take on a default value. The same applies to the light_attenuation block.
To disable lighting use this syntax:
lights {
disable
}
|
Individual lights may not be disabled.
For more information on lighting, see the man pages for glLight() and glLightModel().
Like the definition of individual lights, materials are defined using multiple, optional parameters, which specify emission color, ambient color, diffuse color, etc. If present in the material definition, these parameters are specified as follows:
material {
material_emission <vector_type>
material_ambient <vector_type>
material_diffuse <vector_type>
material_specular <vector_type>
material_shininess <real_number_type>
}
|
Each material parameter is optional and they may be specified in any order within the material block. However, if specified, a parameter may only be specified once. The following material definition would not be valid:
material {
material_emission (0.25 0 0 1)
material_diffuse (1 0 0 1)
material_emission (0.75 0 0.25 1)
}
|
The material alpha component is taken from the fourth element of the vector data following the material_diffuse token. The fourth element of all other vectors is ignored for material definition. This follows the semantics of standard OpenGL lighting.
For more information on materials, see the man page for glMaterial().
The pixel bias attribute is specified as follows:
pixel_bias {
pixel_bias_data <vector_type>
}
|
The vector data following the pixel_bias_data token specifies the per-component values by which pixel color components will be biased during execution of copy_pass and copy_to_tex_pass passes.
For more information on pixel bias, see the man page for glPixelTransfer().
The pixel scale attribute is specified as follows:
pixel_scale {
pixel_scale_data <vector_type>
}
|
The vector data following the pixel_scale_data token specifies the per-component values by which pixel color components will be scaled during execution of copy_pass and copy_to_tex_pass passes.
For more information on pixel scale, see the man page for glPixelTransfer().
As in the case of lights, multiple pixmaps can be defined for a pass. For this reason, pixmaps must grouped together in a pixmaps block like the following:
pixmaps {
pixmap {}
.
.
.
pixmap {}
}
|
An individual pixmap attribute is specified as follows:
pixmap {
pixmap_component <enumerated_constant_type>
pixmap_size <integer_number_rule>
pixmap_data <real_number_rule>
.
.
.
pixmap_data <real_number_rule>
}
|
The enumerated constant following the pixmap_component token specifies the applicable color component for the pixmap, and it must be one of the following:
RED GREEN BLUE ALPHA |
The integer number following the pixmap_size token specifies the number of entries in the pixmap table. This value must be greater than zero and must be a power of two. Following the size specification, the actual pixmap data is specified as a series of pixmap_data tokens, real number pairs. It is an error to supply a number of pixmap values which is greater than the size of the table. It is also an error to specify a pixmap for a component more than once per pass.
To disable pixmaps use this syntax:
pixmaps {
disable
}
|
Pixmaps may not be disabled individually.
For more information on pixmaps, see the man pages for glPixelTransfer() and glPixelMap().
The shade model attribute is specified as follows:
shade_model {
shade_model_mode <enumerated_constant_type>
}
|
The enumerated constant following the shade_model_mode token specifies which shade model will apply when rendering geometry and it must be either SMOOTH or FLAT.
For more information on the shade model, see the man page for glShadeModel().
The stencil test attribute is specified as follows:
stencil_test {
stencil_test_func <enumerated_constant_type>
stencil_test_ref <integer_number_type>
stencil_test_mask <integer_number_type>
stencil_test_sfail <enumerated_constant_type>
stencil_test_zfail <enumerated_constant_type>
stencil_test_zpass <enumerated_constant_type>
stencil_test_wmask <integer_number_type>
}
|
The enumerated constant following the stencil_test_func token specifies which stencil test will be performed and it must be one of the following:
LESS LEQUAL GREATER GEQUAL EQUAL NOTEQUAL ALWAYS |
When performing the stencil test, the stencil value for a pixel stored in the stencil buffer will be compared to the integer which follows the stencil_test_ref token. The integer reference value and the stored pixel stencil value will be bit-wise ANDed with the integer mask which follows the stencil_test_mask token. The outcome of the test between the masked reference value and the masked stored value determines which operation is performed on the stencil buffer. The operations are specified following the stencil_test_sfail, stencil_test_zfail and stencil_test_zpass tokens. The enumerated constant indicating the stencil operation must be one of the following:
KEEP ZERO REPLACE INCR DECR INVERT |
If the outcome of the stencil test operation results in a write to the stencil buffer, the stencil value to be written will first be masked by the integer value following the stencil_test_wmask token.
To disable stencil test use this syntax:
stencil_test {
disable
}
|
For more information on the stencil test, see the man pages for glStencilFunc() and glStencilOp().
The texture environment attribute is specified as follows:
texenv {
texenv_mode <enumerated_constant_type>
}
|
or:
texenv {
texenv_mode <enumerated_constant_type>
texenv_color <vector_type>
}
|
The enumerated constant following the texenv_mode token must be one of the following:
MODULATE BLEND DECAL REPLACE ADD ALPHA |
Certain texture environment modes depend on a constant color which is specified following the texenv_color token.
For more information on texture environment modes and the texture environment color, see the man page for glTexEnv().
As in the case of light definition, there can be multiple texgens specified in a shader description file. For this reason, the individual texgens must be specified in a texgens block like the following:
texgens {
texgen {}
.
.
.
texgen {}
}
|
Up to four texgens can be specified in the texgens block, one for each of the s, t, r, and q texture coordinates. It is an error to specify a texgen for a coordinate more than once. An individual texgen is specified as follows:
texgen {
texgen_coord <enumerated_constant_type>
texgen_mode <enumerated_constant_type>
}
|
or:
texgen {
texgen_coord <enumerated_constant_type>
texgen_mode <enumerated_constant_type>
texgen_plane <vector_type>
}
|
The enumerated constant following the texgen_coord token specifies the texture coordinate to which the texgen applies and it must be one of the following:
S T R Q |
The enumerated constant following the texgen_mode token specifies which texgen mode will be applied to the specified token and it must be one of the following:
EYE_LINEAR OBJECT_LINEAR SPHERE_MAP |
If specified, the vector data following the texgen_plane token specifies the reference plane to be used when computing values for the specified texture coordinate.
For more information on the different texgen modes and the use of the reference plane, see the man page glTexGen().
Note that it is an error to specify a texgen for a coordinate more than once per pass. For instance, the following is invalid:
texgens {
texgen {
texgen_coord S
texgen_mode EYE_LINEAR
}
texgen {
texgen_coord S
texgen_mode OBJECT_LINEAR
}
}
|
To disable texgens use this syntax:
texgens {
disable
}
|
The texture attribute is specified as follows:
texture {
texture_id <identifier_type>
}
|
The identifier following the texture_id token must match an identifier for a texture declared in the shader header segment of the file. It is an error to refer to a texture that has not been declared.
To disable texture use this syntax:
texture {
disable
}
|
Here is a very simple two-pass shader, which combines one pass of constant color with another pass using a table texture.
shader {
# one time shader setup is done here
shader_header {
shader_name “example shader”
shader_major_rev 1
shader_minor_rev 0
shader_locals {
texture_def {
texture_id $table
texture_src TABLE
texture_table {
texture_table_size 8
texture_table_data (0.00 0.00 0.00 1)
texture_table_data (0.14 0.14 0.14 1)
texture_table_data (0.29 0.29 0.29 1)
texture_table_data (0.43 0.43 0.43 1)
texture_table_data (0.57 0.57 0.57 1)
texture_table_data (0.71 0.71 0.71 1)
texture_table_data (0.86 0.86 0.86 1)
texture_table_data (1.00 1.00 1.00 1)
}
}
}
shader_default_state {
depth_test {
depth_test_func LEQUAL
}
}
}
# begin specification of shader passes
shader_passes {
geom_pass {
color {
color_data (1 1 0 1)
}
}
geom_pass {
blend {
blend_eqn ADD
blend_src DST_COLOR
blend_dst ZERO
}
texture {
texture_id $table
}
texture_matrix {
matrix_data (8 0 0 0)
(0 1 0 0)
(0 0 1 0)
(0 0 0 1)
}
}
}
}
|