PART II: Design and Architecture of Parsec
10.1 Specifying how primitives should be rendered 70
One of the most important concepts of the ITER interface is the specification of how primitives should be rendered, described independently of any underlying graphics API. Most importantly, it specifies how to shade a primitive. This shading specification is far from being as complicated and powerful as, for example, specifying shaders in RenderMan [Ups90], but it encapsulates what current hardware is able to perform in real-time in order to shade primitives.
The ITER interface 71 This mostly means iterating some specified attributes over the area of the primitive, that is, linearly interpolating them. Real-time shading languages have generated a lot of research interest recently, see [Pee00, Ola00], for instance. See also the next chapter on a more powerful version of the shading specification used by the ITER interface, Parsec’s shading language.
The ITER interface is inherently oriented towards what practically all currently available hardware is able to provide. Therefore, at this time it knows nothing about lighting, or surface properties like ambient and diffuse color. It simply takes attribute values like RGBA and UVW for each vertex of a primitive and iterates these to derive attribute values for their interior. Thus, a shading specification as used by the ITER interface conceptually resides at the level of rasterization.
Note that the current version of the ITER interface supports only single-pass rendering, although it is already prepared to be extended for rendering with multiple passes on the one hand, and single-pass multi-texturing on the other hand in the future. Right now, however, it is constrained to the description of a single rendering pass, also at most using a single texture. For multi-pass rendering, the interface’s functionality has to be explicitly used multiple times. That is, each pass has to be described separately by the application programmer.
A multi-pass extension would allow shading specifications employing multiple textures and passes for shading a surface at the same time, where the implementation would then automatically decide how many actual passes are needed at run-time, and how many of them can be folded into a single pass using single-pass multi-texturing, for instance.
As already mentioned, in addition to the very basic shading specifications used by the ITER interface, the Parsec engine also offers a more powerful shading language, which can be used to describe how surfaces of an object should be shaded and animated. This shading language is the subject matter of chapter 11.
Iteration and composition modes
Figure 10.2 shows the enumeration type that the ITER interface uses to specify the mode of iteration, as well as composition.
enum itertype_t {
iter_constrgb, // constant [rgb] attributes iter_constrgba, // constant [rgba] attributes iter_rgb, // iterate (rgb) attributes iter_rgba, // iterate (rgba) attributes iter_texonly, // apply texture without shading iter_texconsta, // modulate texture with constant [a]
iter_texconstrgb, // modulate texture with constant [rgb]
iter_texconstrgba, // modulate texture with constant [rgba]
iter_texa, // modulate texture with iterated (a) iter_texrgb, // modulate texture with iterated (rgb) iter_texrgba, // modulate texture with iterated (rgba) iter_constrgbtexa, // constant [rgb] with alpha-only texture iter_constrgbatexa, // constant [rgba] with alpha-only texture iter_rgbtexa, // iterated (rgb) with alpha-only texture iter_rgbatexa, // iterated (rgba) with alpha-only texture iter_texblendrgba, // blend texture with iterated (rgba) iter_texspeca, // add (a) to texture (specular) iter_texspecrgb, // add (rgb) to texture (specular) iter_texconstaspecrgb, // add (rgb) to faded texture iter_texconstrgbspeca, // add (a) to flat-shaded texture iter_texaspecrgb, // add (rgb) to texture, modulate (a) iter_texrgbspeca, // add (a) to texture, modulate (rgb) iter_overwrite, // overwrite destination
iter_alphablend, // alpha blend with destination iter_modulate, // modulate destination (multiply) iter_specularadd, // add to destination (emissive/specular) iter_premulblend, // blend with premultiplied alpha
};
Figure 10.2: Specification of vertex attributes iteration and fragment combination
The following section describes how each of these iteration and composition modes works in detail.
The ITER interface 72 Basically, each iteration mode describes how color sources like vertex color and color sampled from a texture should be used and combined to determine the final fragment color. An example for this would be gouraud- shading a texture-mapped triangle.
The composition modes describe how a final fragment color should be combined with the color of a pixel already contained in the frame buffer. An example for this would be alpha-blending.
In order to submit the desired iteration and composition modes to the ITER interface, they simply have to be OR’ed together and stored in theitertypefield that is part of every data structure describing a primitive. (See the section on ITER rendering primitives below.) When rendering the primitive, the specified iteration and composition modes will be used. (See the section on ITER rendering functions below.)
Restrictions of abstract rendering
When designing an abstract rendering API like the ITER interface several trade-offs are involved. An important consideration is whether the user of the interface should be guaranteed that the implementations of the abstract API create absolutely identical results for all configurations.
This is certainly convenient and desirable for the application programmer, but may have a significant negative performance impact, or restrict the functionality of the API. This is similar to the design of the C programming language, for instance. C is very portable, although the programmer has to obey certain rules in order to actually achieve portability, since some facets of the language are implementation-dependent.
In the case of the ITER interface, for reasons of performance and features offered, some aspects differ slightly between actual implementations. Thus, the user also has to obey certain rules. Most importantly, testing on all platforms whether the developed code produces the same results everywhere.
One example of this behavior is the property of core OpenGL that it depends on the format of a texture whether an alpha channel is used or not. If the texture format specifies an alpha channel (GL_RGBA, for instance) then it will usually also be used. If no alpha channel is present (GL_RGB, for instance), then the alpha information will be ignored, or assumed to be 1.0 everywhere, respectively. In Glide, however, it is possible to specify that an alpha channel should be ignored in any case, even if the texture contains alpha information. This can be very convenient.
So, if an iteration mode has been specified that usually ignores the alpha component of the texture, this might not be possible if the underlying graphics API is OpenGL and a texture with an alpha channel – in this case in the wrong format – is being used.
Thus, when OpenGL is one of the desired target graphics APIs of portable code using the ITER interface, one has to be careful that the texture format also matches the desired configuration. This is simply a result of using an abstract API, where not all actual implementations are able to guarantee the same result in every detail, without the application programmer being aware of the issues involved. So, like in the development of all portable code, it is crucial to test on all desired target platforms, in order to insure consistent behavior everywhere.
Otherwise, the abstract functionality would entirely be constrained to the least common denominator of all targets, which most of the time is unnecessary. With a little care on the side of the user it is not hard to achieve the desired results without compromising performance or the feature set of an API too much.
Iteration modes
In the following, formulas for deriving the output color and alpha values for a fragment in a given iteration mode are given.
The ITER interface 73 The following color sources are possible:
color_constant constant color specified for an entire primitive.
color_iterated color specified for each of a primitive’s vertices, and iterated over its interior.
color_texture color sampled from a texture at the iterated texture coordinates.
The following sources are possible for alpha values:
alpha_constant constant alpha value specified for an entire primitive.
alpha_iterated alpha value specified for each of a primitive’s vertices, and iterated over its interior.
alpha_texture alpha value sampled from the alpha-component of a texture.
The goal of an iteration mode specification is to describe how the inputs listed above should be combined to derive the outputscolor_outandalpha_out, which constitute the final fragment and can then be combined with the contents of the frame buffer.
With the preliminaries having been discussed, we will now continue with a detailed description of all available iteration modes:
iter_constrgb: constant [rgb] attributes
color_out = color_constant;
alpha_out = undefined;
This mode is used for flat-shaded polygons, where the alpha component is irrelevant (don’t-care).
iter_constrgba: constant [rgba] attributes
color_out = color_constant;
alpha_out = alpha_constant;
This mode is used for flat-shaded polygons, where the alpha component is also constant for the entire polygon.
iter_rgb: iterated (rgb) attributes
color_out = color_iterated;
alpha_out = undefined;
This mode is used for gouraud-shaded (diffusely lit) polygons, where vertex colors are specified and the alpha component is irrelevant (don’t-care).
iter_rgba: iterated (rgba) attributes
color_out = color_iterated;
alpha_out = alpha_iterated;
This mode is used for gouraud-shaded (diffusely lit) polygons, where colors and alpha values are specified for each vertex.
iter_texonly: apply texture without shading color_out = color_texture;
alpha_out = alpha_texture;
The ITER interface 74 This mode is used for polygons whose color and alpha information is derived directly from a texture, without using any further color or alpha information. That is, when this iteration mode is used, lighting polygons is not possible. At least not without changing the texture itself.
iter_texconsta: modulate texture with constant [a]
color_out = color_texture;
alpha_out = alpha_texture * alpha_constant;
This mode is used for polygons whose color information is derived directly from a texture, without using any further color information. The alpha component derived from the texture, however, will be multiplied by a constant alpha value.
iter_texconstrgb: modulate texture with constant [rgb]
color_out = color_texture * color_constant;
alpha_out = alpha_texture;
This mode is used for flat-shading polygons while also applying a texture. The alpha value is directly read from the texture and used as is.
iter_texconstrgba: modulate texture with constant [rgba]
color_out = color_texture * color_constant;
alpha_out = alpha_texture * alpha_constant;
This mode is used for flat-shading polygons while also applying a texture. The alpha value read from the texture will be multiplied by the constant alpha value before being used (“scaled texture alpha”).
iter_texa: modulate texture with iterated (a)
color_out = color_texture;
alpha_out = alpha_texture * alpha_iterated;
This mode is used for unshaded polygons using only color information from a texture, where the alpha component read from the texture will be multiplied by an iterated vertex alpha value, however (“modulated texture alpha”).
iter_texrgb: modulate texture with iterated (rgb) color_out = color_texture * color_iterated;
alpha_out = alpha_texture;
This mode is used for gouraud-shaded (diffusely lit) polygons, whose alpha values are read directly from a texture.
iter_texrgba: modulate texture with iterated (rgba)
color_out = color_texture * color_iterated;
alpha_out = alpha_texture * alpha_iterated;
This mode is used for gouraud-shaded (diffusely lit) polygons, whose alpha values are also modulated by iterated alpha values.
The ITER interface 75 iter_constrgbtexa: constant [rgb] with alpha-texture
color_out = color_constant;
alpha_out = alpha_texture;
This mode can be used for flat-shaded polygons that use alpha-information from a texture (“alpha textures”). No texture colors are used, however.
iter_constrgbatexa: constant [rgba] with alpha-texture color_out = color_constant;
alpha_out = alpha_texture * alpha_constant;
This mode can be used for flat-shaded polygons that use alpha-information from a texture, scaled by a constant alpha value (“scaled alpha textures”). No texture colors are used, however.
iter_rgbtexa: iterated (rgb) with alpha-texture
color_out = color_iterated;
alpha_out = alpha_texture;
This mode can be used for gouraud-shaded (diffusely lit) polygons that use alpha-information from a texture (“alpha textures”). No texture colors are used, however.
iter_rgbatexa: iterated (rgba) with alpha-texture
color_out = color_iterated;
alpha_out = alpha_texture * alpha_iterated;
This mode can be used for gouraud-shaded (diffusely lit) polygons that use alpha-information from a texture multiplied by iterated alpha values (“modulated alpha textures”). No texture colors are used, however.
iter_texblendrgba: blend texture with iterated (rgba)
color_out = color_texture*alpha_iterated+color_iterated*(1–alpha_iterated);
alpha_out = alpha_texture;
This mode can be used if blending between the iterated color information and the texture is desired, instead of simply multiplying them together. An application of this would be blending with a fog color which, together with its intensity, is specified per vertex.
iter_texspeca: add (a) to texture (specular)
color_out = color_texture + alpha_iterated;
alpha_out = alpha_texture;
This mode can be used for applying specular highlights that have been calculated per vertex and stored in the respective alpha components. This implies that the highlights can only be white, like on metallic surfaces.
iter_texspecrgb: add (rgb) to texture (specular) color_out = color_texture + color_iterated;
alpha_out = alpha_texture;
The ITER interface 76 This mode can be used for applying specular highlights that have been calculated per vertex and stored in the respective vertex colors. This implies that the highlights can be colored, which would be needed for plastic surfaces.
iter_texconstaspecrgb: add (rgb) to faded texture
color_out = color_texture + color_iterated;
alpha_out = alpha_texture * alpha_constant;
Similar toiter_texspecrgb, but with the added ability of fading the entire surface using a constant fading value.
iter_texconstrgbspeca: add (a) to flat-shaded texture
color_out = color_texture * color_constant + alpha_iterated;
alpha_out = alpha_texture;
Similar toiter_texspeca, but additionally allowing to apply flat-shading onto the surface at the same time.
iter_texaspecrgb: add (rgb) to texture, modulate (a)
color_out = color_texture + color_iterated;
alpha_out = alpha_texture * alpha_iterated;
Similar toiter_texspecrgb, but with the added ability of fading the entire surface using an iterated fading value that has been specified per vertex.
iter_texrgbspeca: add (a) to texture, modulate (rgb)
color_out = color_texture * color_iterated + alpha_iterated;
alpha_out = alpha_texture;
Similar toiter_constrgbspeca, but with gouraud-shading instead of flat-shading.
Composition modes
In the following, formulas for combining a fragment color with the previous contents of the frame buffer are given, thus deriving the new contents of the frame buffer, similarly to the work introduced in [Por84].
The following color sources are possible:
color_src fragment color (denoted ascolor_outin the iteration mode formulas).
color_dst color currently stored in the frame buffer.
The following sources are possible for alpha values:
alpha_src fragment alpha (denoted asalpha_outin the iteration mode formulas).
alpha_dst alpha value currently stored in the frame buffer (if the frame buffer is RGBA).
The goal of a composition mode specification is to describe how the inputs listed above should be combined to derive the outputscolor_outandalpha_out, which constitute the final color and alpha values that will be stored in the frame buffer.
The ITER interface 77 Note that these notations should not be confused with those used for iteration modes. What is an output in an iteration mode is an input in a composition mode, since they are applied back-to-back to derive the final color stored in the frame buffer; first iteration, then composition.
Now we are going to describe all composition modes in detail. Note also thatalpha_dstis currently not used in any of these modes, since such modes require a frame buffer with destination alpha (an RGBA frame buffer, instead of just RGB), which is not widely supported by current hardware. Such modes are required, however, if operations like performing alpha-blending from front-to-back instead of back-to-front are desired, for instance.
For such configurations, further composition modes could easily be added.
iter_overwrite: overwrite destination
color_out = color_src;
alpha_out = alpha_src;
This mode is used for using the fragment color and alpha without using the previous frame buffer contents at all (overwriting it). This is usually the case for most rendering operations.
iter_alphablend: alpha blend with destination
color_out = color_src * alpha_src + color_dst * ( 1 - alpha_src );
alpha_out = alpha_src;
This mode is used for blending the frame buffer contents with the fragment color, using the fragment’s alpha as the blend factor.
iter_modulate: modulate destination (multiply) color_out = color_src * color_dst;
alpha_out = alpha_src;
This mode is used for multiplying the fragment color with the color already stored in the frame buffer. A well- known use for such a mode is the combination of a lightmap with a base texture, where the lightmap has already been rendered into the frame buffer and the base texture is multiplied onto it in a second rendering pass.
iter_specularadd: add to destination (emissive/specular add)
color_out = color_src + color_dst;
alpha_out = alpha_src;
This mode is used for adding the fragment color to the color already stored in the frame buffer. It is usually used for achieving the appearance of emissive materials (particle light flares, for instance), or an additional specular pass, where the specular highlights are added onto the surface already shaded using the base material and diffuse lighting information.
iter_premulblend: blend with premultiplied alpha
color_out = color_src + color_dst * ( 1 - alpha_src );
alpha_out = alpha_src;
This mode is very similar toiter_alphablend. However, the source color is assumed to be already scaled byalpha_src, and therefore used without any weighting. This is usually used for textures that have the alpha channel already multiplied into their color information, like most translucent textures generated by rendering programs.
The ITER interface 78 Such textures can be viewed as being blended onto an entirely black background, while still retaining their alpha (translucency) information, which is needed to blend with a different background later on. The run-time blending pass then only adds the information of the changed background.
Rasterizer configuration
In addition to the iteration and composition modes, the ITER interface allows the configuration of certain modes of operation concerning the rasterizer. Examples for this would be whether depth buffering should be turned on or off while rendering a primitive, texture filtering should be enabled or disabled, and the like.
Figure 10.3 lists all available rasterizer modes, which can be OR’ed together to specify several of them at once.
For most modes there are three flags. One that enables the mode, one that disables the mode, and one that masks it, which denotes that the current configuration of the rasterizer in this regard should not be touched. For example, if depth buffering is enabled or disabled globally for an entire section of rendering code, rast_mask_zbufferwould be specified to leave depth buffering in its current state.
Otherwise it would not easily be possible to control the rasterizer for entire sections, instead of per primitive. Of course, global variables could be used, but it is often convenient and good style to leave certain states untouched.
enum raststate_t {
rast_nozcompare, // no depth compare rast_zcompare, // do depth compare
rast_mask_zcompare, // don't change depth compare rast_nozwrite, // no depth-buffer write rast_zwrite, // do depth-buffer write
rast_mask_zwrite, // don't change depth-buffer write rast_nozbuffer, // no standard depth-buffering rast_zbuffer, // do standard depth-buffering rast_mask_zbuffer, // don't change depth-buffering rast_texwrap, // no texture coordinate clamping rast_texclamp, // do texture coordinate clamping
rast_mask_texclamp, // don't change coordinate clamp/wrap mode rast_mask_texwrap, // don't change coordinate clamp/wrap mode rast_chromakeyoff, // no chroma key compare
rast_chromakeyon, // do chroma key compare rast_mask_chromakey, // don't change chroma keying rast_mask_mipmap, // don't change mip-mapping rast_mask_texfilter, // don't change texture filtering };
Figure 10.3: Type for specification of desired rasterizer state
In addition to theitertypefield discussed in detail in the previous section, all data structures describing ITER primitives contain raststate and rastmask fields, which are used to specify the desired rasterizer configuration and mask, respectively.
For example, rast_zbuffer would be stored in raststate to enable depth buffering, whereas rast_mask_zbuffer would be stored in rastmask to leave the current mode of depth buffering untouched.
rast_nozcompare / rast_zcompare
Selects whether the depth buffer should be read for comparisons whether the current fragment must be discarded, or should overwrite the previous contents of the frame buffer. This is usually turned off when rendering surfaces that are visible in any case, or when rendering from back to front.