Chapter 6. Creating Visual Effects

This chapter describes how to use environmental, atmospheric, lighting, and other visual effects to enhance the realism of your application.

Using pfEarthSky

A pfEarthSky is a special set of functions that clears a pfChannel's viewport efficiently and implements various atmospheric effects. A pfEarthSky is attached to a pfChannel with pfChanESky(). Several pfEarthSky definitions can be created, but only one can be in effect for any given channel at a time.

A pfEarthSky can be used to draw a sky and horizon, to draw sky, horizon, and ground, or just to clear the entire screen to a specific color and depth. The colors of the sky, horizon, and ground can be changed in real time to simulate a specific time of day. At the horizon boundary, the ground and sky share a common color, so that there is a smooth transition from sky to horizon color. The width of the horizon band can be defined in degrees.

A pfChannel's earth-sky model is automatically drawn by OpenGL Performer before the scene is drawn unless the pfChannel has a draw callback set with pfChanTravFunc(). In this case it is the application's responsibility to clear the viewport. Within the callback pfClearChan() draws the channel's pfEarthSky.

Example 6-1 shows how to set up a pfEarthSky().

Example 6-1. How to Configure a pfEarthSky

pfEarthSky  *esky;
pfChannel *chan;
 
sky = pfNewESky();
pfESkyMode(esky, PFES_BUFFER_CLEAR, PFES_SKY_GRND);
pfESkyAttr(esky, PFES_GRND_HT, -1.0f);
pfESkyColor(esky, PFES_GRND_FAR, 0.3f, 0.1f, 0.0f, 1.0f);
pfESkyColor(esky, PFES_GRND_NEAR, 0.5f, 0.3f, 0.1f,1.0f);
pfChanESky(chan, esky);


Atmospheric Effects

The complexities of atmospheric effects on visibility are approximated within OpenGL Performer using a multiple-layer sky model, set up as part of the pfEarthSky function. In this design, individual layers are used to represent the effects of ground fog, clear sky, and clouds. Figure 6-1 shows the identity and arrangement of these layers.

Figure 6-1. Layered Atmosphere Model


The lowest layer consists of ground fog, extending from the ground up to a user-selected altitude. The fog thins out with increasing altitude, disappearing entirely at the bottom of the general visibility layer. This layer extends from the top of the ground fog layer to the bottom of the cloud layer's lower transition zone, if such a zone exists. The transition zone provides a smooth transition between general visibility and the cloud layer. (If there is no cloud layer, then general visibility extends upward forever.) The cloud layer is defined as an opaque region of near-zero visibility; you can set its upper and lower boundaries. You can also place another transition zone above the cloud layer to make the clouds gradually thin out into clear air.

Set up the atmospheric simulation with the commands listed in Table 6-1

Table 6-1. pfEarthSky Functions

Function

Action

pfNewESky()

Create a pfEarthSky.

pfESkyMode()

Set the render mode.

pfESkyAttr()

Set the attributes of the earth and sky models.

pfESkyColor()

Set the colors for earth and sky and clear.

pfESkyFog()

Set the fog functions.

You can set any pfEarthSky attribute, mode, or color in real time. Selecting the active pfFog definition can also be done in real time. However, changing the parameters of a pfFog once they are set is not advised when in multiprocessing mode.

The default characteristics of a pfEarthSky are listed in Table 6-2.

Table 6-2. pfEarthSky Attributes

Attribute

Default

Clear method

PFES_FAST (full screen clear)

Clear color

0.0 0.0 0.0

Sky top color

0.0 0.0 0.44

Sky bottom color

0.0 0.4 0.7

Ground near color

0.5 0.3 0.0

Ground far color

0.4 0.2 0.0

Horizon color

0.8 0.8 1.0

Ground fog

NULL (no fog)

General visibility

NULL (no fog)

Cloud top

20000.0

Cloud bottom

20000.0

Cloud bottom color

0.8 0.8 0.8

Cloud top color

0.8 0.8 0.8

Transition zone bottom

15000.0

Transition zone top

25000.0

Ground height

0

Horizon angle

10 degrees

By default, an earth-sky model is not drawn. Instead, the channel is simply cleared to black and the Z-buffer is set to its maximum value. This default action also disables all other atmospheric attributes. To enable atmospheric effects, select PFES_SKY, PFES_SKY_GRND, or PFES_SKY_CLEAR when turning on the earth-sky model.

Clouds are disabled when the cloud top is less than or equal to the cloud bottom. Cloud transition zones are disabled when clouds are disabled.

Fog is enabled when either the general or ground fog is set to a valid pfFog. If ground fog is not enabled, no ground fog layer will be present and fog will be used to support general visibility. Setting a fog attribute to NULL disables it. See “Atmospheric Effects” for further information on fog parameters and operation.

The earth-sky model is an attribute of the channel and thus accesses information about the viewer's position, current field of view, and other pertinent information directly from pfChannel. To set the pfEarthSky in a channel, use pfChanESky().

Patchy Fog and Layered Fog

A pfVolFog is a class that uses a multi-pass algorithm to draw the scene with a fog that has different densities at different locations. It extends the basic layered fog provided by pfEarthSky and introduces a new type of fog: a patchy fog. A patchy fog has a constant density in a given area. The boundaries of this area can be defined by an arbitrary three-dimensional object or by a set of objects.

A layered fog changes only with elevation; its density and color is uniform at a given height. It is defined by a set of elevation points, each specifying a fog density and, optionally, also a fog color at the point's elevation. The density and the color between two neighboring points is linearly interpolated.

Figure 6-2 illustrates the basic difference between patchy fog and layered fog.

Figure 6-2. Patchy Fog Versus Layered Fog


Compared to a layered fog in pfEarthSky, a layered fog in pfVolFog has distinct advantages:

  • It can be specified by an arbitrary number of elevation points.

  • Each elevation point can have a different color associated with it.

  • A layered fog in pfVolFog is not dependent on an InfiniteReality-specific texgen. It can also be drawn using only 2D textures to simulate the 3D texture. Thus, a layered fog in pfVolFog can virtually be used on any machine.

Creating Layered Fog

A pfVolFog is not part of the scene graph; it is created separately by the application process. Once created, elevation points of a layered fog can be specified by calling pfVolFogAddPoint() or pfVolFogAddColoredPoint() repeatedly. The fog initialization is completed by calling pfApplyVolFog().

Example 6-2. Fog initialization Using pfVolFogAddPoint()


pfVolFog *lfog;
lfog = pfNewVolFog(arena);
pfVolFogAddPoint(lfog, elev1, density1);
pfVolFogAddPoint(lfog, elev2, density2);
pfVolFogAddPoint(lfog, elev2, density2);

pfApplyVolFog(lfog); 


Creating Patchy Fog

The boundary of a patchy fog is specified by pfVolFogAddNode(pfog,node),where node contains the surfaces enclosing the foggy areas. It is possible to define several disjoint areas in the same tree or by adding several different nodes. Note that each area has to be completely enclosed, and the vertices of the surfaces have to be ordered so that the front face of each surface faces outside the foggy area. The node has to be part of the scene graph for the rendering to work properly.

Example 6-3. Specifying Patchy Fog Boundaries Using pfVolFogAddNode()


pfVolFog *pfog;
pfNode   *fogNode;
pfog = pfNewVolFog(arena);
fogNode = pfdLoadFile(filename);
pfVolFogAddNode(pfog, fogNode);
pfAddChild(scene, fogNode);
pfApplyVolFog(pfog); 

Patchy and layered fog can be combined but only if layered fog has a uniform color; that is, it is specified using pfVolFogAddPoint() only.

Initializing a pfVolFog

The function pfApplyVolFog() initializes a pfVolFog. If at least two elevation points were defined, it initializes data structures necessary for rendering of a layered fog, including a 3D texture. Any control points defined afterward are ignored. If a node containing patchy fog boundaries has been added prior to calling pfApplyVolFog(), a patchy fog is initialized. Since function pfVolFogAddNode() only marks the parts of the scene graph that specifies the texture, it is possible to add additional patchy fog nodes, even after pfApplyVolFog() has been called.

Table 6-3 summarizes routines for initialization and drawing of a pfVolFog.

Table 6-3. pfVolFog Functions

Function

Action

pfNewVolFog()

Create a pfVolFog.

pfVolFogAddChannel()

Add a channel on which pfVolFog is used.

pfVolFogAddPoint()

Add a point specifying fog density at a certain elevation.

pfVolFogAddColoredPoint()

Add a point specifying fog density and color at a certain elevation.

pfVolFogAddNode()

Add a node defining the boundary of a patchy fog.

pfVolFogSetColor()

Set color of a layered fog or patchy fog.

pfVolFogSetDensity()

Set density of a patchy fog.

pfVolFogSetFlags()

Set binary flags.

pfVolFogSetVal()

Set a single attribute.

pfVolFogSetAttr()

Set an array of attributes.

pfApplyVolFog()

Initialize data structures necessary for rendering fog.

pfVolFogAddChannel()

Add a channel on which pfVolFog is used.

pfVolFogUpdateView()

Update the current view for all stored channels.

pfDrawVolFog()

Draw the scene with fog.

pfGetVolFogTexture()

Return the texture used by layered fog.

The attributes of a pfVolFog are listed in Table 6-4.

Table 6-4. pfVolFog Attributes

Attribute

Identifier

Default

General

 

 

 Color

PFVFOG_COLOR

0.9, 0.9, 1

 Density

PFVFOG_DENSITY

1.0

 Density bias

PFVFOG_DENSITY_BIAS

0

 Maximum distance

PFVFOG_MAX_DISTANCE

2000

 Mode

PFVFOG_MODE

PFVFOG_LINEAR

Layered fog

 

 

 Layered fog mode

PFVFOG_LAYERED_MODE

PFVFOG_LINEAR

 Texture size

PFVFOG_3D_TEX_SIZE

64 x 64 x 64

Patchy fog

 

 

 Resolution

PFVFOG_RESOLUTION

0.2

 Patchy fog mode

PFVFOG_PATCHY_MODE

PFVFOG_LINEAR

 Texture bottom

PFVFOG_PATCHY_TEXTURE_BOTTOM

0.3

 Texture top

PFVFOG_PATCHY_TEXTURE_TOP

0.1.5

Layered patchy fog

 

 

 Rotation matrix

PFVFOG_ROTATE_NODE

Identity

Light shafts

 

 

 Attenuation scale

PFVFOG_LIGHT_SHAFT_ATTEN_SCALE

0.04

 Attenuation shift

PFVFOG_LIGHT_SHAFT_ATTEN_TRANSLATE

6

 Darken factor

PFVFOG_LIGHT_SHAFT_DARKEN_FACTOR

0.3

The flags of a pfVolFog are listed in Table 6-5.

Table 6-5. pfVolFog Flags

Flag

Identifier

Default

General

 

 Close surfaces

PFVFOG_FLAG_CLOSE_SURFACES

1

 Use 2D texture

PFVFOG_FLAG_FORCE_2D_TEXTURE

0

 Force patchy fog passes

PFVFOG_FLAG_FORCE_PATCHY_PASS

0

Layered fog

 

 Self-shadowing

PFVFOG_FLAG_SELF_SHADOWING

0

 Darken objects

PFVFOG_FLAG_DARKEN_OBJECTS

0

 Filter color

PFVFOG_FLAG_FOG_FILTER

0

Patchy fog

 

 Faster patchy fog

PFVFOG_FLAG_FASTER_PATCHY_FOG

0

 No object in fog

PFVFOG_FLAG_NO_OBJECT_IN_FOG

0

 1D texture on surface

PFVFOG_FLAG_PATCHY_FOG_1DTEXTURE

0

 Separate node bins

PFVFOG_FLAG_SEPARATE_NODE_BINS

0

 Screen-bounding rectangle

PFVFOG_FLAG_SCREEN_BOUNDING_RECT

1

 Draw nodes separately

PFVFOG_FLAG_DRAW_NODES_SEPARATELY

0

 User-defined texture

PFVFOG_FLAG_USER_PATCHY_FOG_TEXTURE

0

 Use cull programs

PFVFOG_FLAG_USE_CULL_PROGRAM

0

 

 

 

Layered patchy fog

 

 Use layered patchy fog

PFVFOG_FLAG_LAYERED_PATCHY_FOG

0

Light shafts

 

 Light shaft

PFVFOG_FLAG_LIGHT_SHAFT

0


Updating the View

A pfVolFog needs information about the current eye position and view direction. Since this information is not directly accessible in a draw process, it is necessary to call pfVolFogAddChannel() for each channel at the beginning of the application. Whenever the view changes, the application process has to call pfVolFogUpdateView(). See programs in /usr/share/Performer/src/sample/apps/C/fogfly or /usr/share/Performer/src/sample/apps/C++/volfog for an example. If you do not update the view, the fog will not be rendered.

If the application changes the position of the patchy fog boundaries (for example, by inserting a pfSCS, pfDCS, or pfFCS node above the fog node) or the orientation of the whole scene with respect to the up vector (for example, the use of a trackball in Perfly), the fog may not be drawn correctly.

Drawing a Scene with Fog

To draw the scene with a fog, the draw process has to call pfDrawVolFog() instead of pfDraw(). This function takes care of drawing the whole scene graph with the specified fog. Expect the draw time to increase because the scene is drawn twice (three times if both patchy and layered fog are specified). In case of a patchy fog there may also be several full-screen polygons being drawn. You can easily disable the fog by not calling pfDrawVolFog().

Since boundaries of patchy fog are in the scene graph, do not use pfDraw() to draw the scene without fog; instead, use pfDrawBin() with PFSORT_DEFAULT_BIN, PFSORT_OPAQUE_BIN, and PFSORT_TRANSP_BIN.

A patchy fog needs as deep a color buffer as possible (optimally 12 bits per color component) and a stencil buffer. Use at least a 4-bit stencil buffer (1-bit is sufficient only for very simple fog objects). It may be necessary to modify your application so that it asks for such a visual.

Deleting a pfVolFog

A pfVolFog can be deleted using pfDelete(). In case of a layered fog it is necessary to delete the texture handle in a draw process. The texture is returned by pfGetVolFogTexture(). See the example in /usr/share/Performer/src/sample/apps/C/fogfly.

Specifying Fog Parameters

This section describes how to manage the various parameters for both layered and patchy fog.

Layered Fog

As mentioned earlier, a layered fog of a uniform color is specified by function pfVolFogAddPoint(), which sets the fog density at a given elevation. The density is scaled so that if the fog has a density of 1, the nearest object inside the fog that has full fog color is at a distance equal to 1/10 of the diagonal of the scene bounding box. The layered fog color is set by function pfVolFogSetColor() or by calling pfVolFogSetAttr() with parameter PFVFOG_COLOR and a pointer to an array of three floats.

A layered fog of nonuniform color is specified by function pfVolFogAddColoredPoint(), which sets the fog density and the fog color at a given elevation. The color set by pfVolFogSetColor() is then ignored.

The layered fog mode is set by function pfVolFogSetVal() with parameter PFVFOG_LAYERED_MODE and one of PFVFOG_LINEAR, PFVFOG_EXP, or PFVFOG_EXP2.

It is also possible to set the mode both for a layered and patchy fog at once by using parameter PFVFOG_MODE. The default mode is PFVFOG_LINEAR. The function of the mode parameter is equivalent to the function of the fog mode parameter of the OpenGL function glFog().

The size of a 3D texture used by a layered fog can be modified by calling pfVolFogSetAttr() with parameter PFVFOG_3D_TEX_SIZE and an array of three integer values. The default texture size is 64x64x64, but reasonable results can be achieved with even smaller sizes. The sizes are automatically rounded up to the closest power of 2. The second value should be equal to or greater than the third value. If 3D textures are not supported, a set of 2D textures is used instead of a 3D texture (the number of 2D textures is equal to the third dimension of the 3D texture). Every time the r coordinate changes more than 0.1, a new texture is computed by interpolating between two neighboring slices, and the texture is reloaded. The use of 2D textures can be forced by calling: pfVolFogSetFlags() with flag PFVFOG_FLAG_FORCE_2D_TEXTURE set to 1.


Note: Once a layered fog is initialized by calling the pfApplyVolFog(), changing any of the parameters described here will not affect rendering of the layered fog.


Patchy Fog

The density of a patchy fog is controlled by function pfVolFogSetDensity() or by using pfVolFogSetVal() with parameter PFVFOG_FOG_DENSITY. As in the case of a layered fog, the density of a patchy fog is scaled by 1/10 of the diagonal of the scene bounding box.

You can specify an additional density value that is added to every pixel inside or behind a patchy fog boundary using the function pfVolFogSetVal() with parameter PFVFOG_FOG_DENSITY_BIAS. This value makes a patchy fog appear denser but it may create unrealistically sharp boundaries.

The patchy fog color is set by function pfVolFogSetColor() or by calling pfVolFogSetAttr() with parameter PFVFOG_COLOR and a pointer to an array of three floats. If the blend_color extension is not available, patchy fog will be white.

The patchy fog mode is set by function pfVolFogSetVal() with parameter PFVFOG_PATCHY_MODE and one of PFVFOG_LINEAR, PFVFOG_EXP, or PFVFOG_EXP2.

It is also possible to set the mode both for a patchy and layered fog at once by using parameter PFVFOG_MODE. The default mode is PFVFOG_LINEAR.


Note: The parameters of a patchy fog can be modified at any time and they will affect the rendering of the subsequent frame.


Advanced Features of Layered Fog and Patchy Fog

This section describes the following topics:

The example in /usr/share/Performer/src/sample/C++/volfog illustrates the use of all these advanced features.

Enabling Self-Shadowing of a Layered Fog and Scene Darkening

A layered fog can be self-shadowed—that is, the lower parts of a dense fog appear darker. Self-shadowing is enabled by setting the flag PFVFOG_FLAG_SELF_SHADOWING to 1. The fog mode should be set to PFVFOG_EXP.

When the fog has different colors at different elevations and the flag PFVFOG_FLAG_FOG_FILTER is set to 1, a secondary scattering is approximated. In this case, the color of a higher layer may affect the color of a lower layer.

If the flag PFVFOG_FLAG_DARKEN_OBJECTS is set, even the objects below a dense fog become darker. The light is assumed to come from the top.

Animating Patchy Fog

A patchy fog can be animated by modifying the geometry of the fog nodes. When changing the content of geosets specifying the fog boundary, make sure that the geosets are fluxed and that the bounding box of each geoset is updated. In addition, function pfVolFogAddNode() has to be called every time the fog bounding box changes.

Selecting a Different Type of Patchy Fog Algorithm

It is possible to use a different algorithm for rendering patchy fog that can handle semi-transparent surfaces better. To use this algorithm, set the flag PFVFOG_FASTER_PATCHY_FOG to 1. Some advanced features of patchy fog described in the following subsections are supported only in one of the two algorithms. In such cases, this limitation is noted.

Simulating Self-Shadowing in Patchy Fog

If the flag PFVFOG_FASTER_PATCHY_FOG is set to 1, the algorithm also allows the color of the patchy fog boundary to be modified using a texture. Either a built-in 1D texture expressing the attenuation between two elevations is used or you can provide a 1D or a 3D texture for each volume object. This can be used to simulate self-shadowing of dense gases, such as clouds.

The built-in 1D texture is enabled by setting the flag PFVFOG_FLAG_PATCHY_FOG_1DTEXTURE. The texture is mapped to the range of elevations between the bottom and top of the fog bounding box. The texture value at the bottom (default of 0.3) can be modified by calling pfVolFogSetVal() with parameter PFVFOG_PATCHY_TEXTURE_BOTTOM and the value at the top (default of 1.5) using parameter PFVFOG_PATCHY_TEXTURE_TOP.

To use a different scale for objects of different sizes, you must specify the fog objects separately. When the flag PFVFOG_FLAG_SEPARATE_NODE_BINS is set, all calls to pfVolFogAddNode() define fog nodes that are drawn separately, and the predefined texture is scaled according to the bounding box of each node.

If both the flag PFVFOG_FLAG_PATCHY_FOG_1DTEXTURE and the flag PFVFOG_FLAG_USER_PATCHY_FOG_TEXTURE are set, textures associated with the fog nodes are used to modify the surface color of a patchy fog.

To avoid artifacts on overlapping colored patchy fog objects the flag PFVFOG_FLAG_DRAW_NODES_SEPARATELY forces the algorithm to be applied to each node separately in the back-to-front order with respect to the viewpoint. Currently, this mode does not work well when scene objects intersect fog objects.

Layered Patchy Fog

 If the flag PFVFOG_FLAG_LAYERED_PATCHY_FOG is set, the layered fog is used to define the density of a patchy fog. The layered fog is then present only in areas enclosed by the patchy fog boundaries. Since layered fog is computed for the whole scene, it is important to set fog parameter PFVFOG_MAX_DISTANCE to a value that corresponds to the size of the patchy fog area (for example, a diameter of its bounding sphere). Use function pfVolFogSetVal() to modify the maximum distance parameter.

Layered patchy fog nodes can be moved and rotated by specifying a matrix for each fog node, identified by its index (the order in which nodes were specified). The function pfVolFogSetAttr() with three parameters specified can be used for this purpose. The first parameter is PFVFOG_ROTATE_NODE, the second parameter specifies the node index, and the last one is a pointer to a pfMatrix.

Light Shafts

Light shafts are a special application of a layered patchy fog. The fog boundary specifies a cone of light with decreasing intensity (density) along the cone axis. Additional rendering passes darken the objects outside the cone of light and lighten the objects inside the light shaft based on their distance from the light. To enable these additional passes, set flag PFVFOG_FLAG_LIGHT_SHAFT to 1. To ensure that these passes are applied even if the light shaft is not in the field of view, you must also set flag PFVFOG_FLAG_FORCE_PATCHY_PASS to 1.

To control the additional passes, the parameter PFVFOG_LIGHT_SHAFT_DARKEN_FACTOR (set using pfVolFogSetAttr()) can change the factor by which all objects outside the light shaft are darkened. The default value is 0.3.

Parameters PFVFOG_LIGHT_SHAFT_ATTEN_SCALE and PFVFOG_LIGHT_SHAFT_ATTEN_TRANSLATE set the translate and scaling of a built-in, one-dimensional texture that is used to reduce the color of objects lit by the light. Set the translate to a small value—for example, 10 to 20% of the shaft length—and the scale to the inverse of the shaft length.

Performance Considerations and Limitations

The quality and speed of patchy fog rendering can be controlled by calling pfVolFogSetVal() with the parameter PFVFOG_RESOLUTION. The resolution is a value between 0 and 1. Higher values will reduce banding and speed up the drawing. On the other hand, high values may cause corruption in areas of many overlapping fog surfaces. The default value is 0.2, but you may use values higher than that if your fog boundaries do not overlap much.

The following are other performance considerations:

  • The multipass algorithms used for rendering layered and patchy fog may produce incorrect results if the scene graph contains polygons that have equal depth values. To avoid such problems, a stencil buffer is used during rendering of the second pass. You can disable this function by setting the flag PFVFOG_FLAG_CLOSE_SURFACES to 0.

  • By default, the multipass algorithm is applied only when boundaries of a patchy fog are visible. This may cause undesirable changes of semi-transparent edges of scene objects when fog objects move into or away from the view. To force the use of the multipass algorithm, set the flag PFVFOG_FLAG_FORCE_PATCHY_PASS to 1.

  • Cull programs (see “Cull Programs” in Chapter 4) can speed up rendering of patchy fog because in some draw passes only the part of the scene intersecting the fog boundary is rendered. To enable cull programs, set the flag PFVFOG_FLAG_USE_CULL_PROGRAM to 1.

  • A layered fog is faster to render than a patchy fog; use a layered fog instead of a patchy fog whenever possible. Rendering of both types of fog together is even slower; so, you may try to define only one type.

  • Changing the fog mode does not affect the rendering speed in the case of a layered fog but rendering of a patchy fog is slower for fog modes PFVFOG_EXP and PFVFOG_EXP2. If you prefer using non-linear modes, try to use them only for layered fog and not for patchy fog.

  • You can speed up drawing of a patchy fog by reducing the size of the fog boundaries. In case of several disjoint fog areas, the size of a bounding box containing all boundaries will affect the draw time and quality. Try to avoid defining a patchy fog in two opposite parts of your scene. Try also to increase the value of resolution (if there are not too many overlapping fog boundaries) or reduce the patchy fog density.

  • If there is a lot of banding visible in the fog, try to choose a visual with as many bits per color component as possible. Keep in mind that a patchy fog needs a stencil buffer. You can also try to apply all techniques mentioned in the previous item—reducing the size of patchy fog boundaries, increasing resolution, or decreasing density.

  • If a patchy fog looks incorrect (the fog appears outside the specified boundaries) make sure that the vertices of the fog boundaries are specified in the correct order so that front faces always face outside the foggy area.

  • If you see a darker band in a layered fog at eye level, make sure the texture size is set so that the second value is equal to or greater than the third value.

  • Since light shafts are using a combination of layered and patchy fog and the density is decreasing to 0 at the end of the light cone, the quality of results is very sensitive to the depth of color buffers. 12-bit visuals are required and the light shaft should not be too large. Also, ensure that PFVFOG_MAX_DISTANCE is set as small as possible.

OpenGL Performer has the following limitations in regards to fog management:

Layered fog

  • The values of a layered fog are determined at each vertex and interpolated across a polygon. Consequently, an object located on top of a large ground polygon may be fogged a bit more or less than the part of the polygon just under the object.

  • A layered fog works fast with a 3D texture. Reloading of 2D textures during the animation can be slow.

Patchy fog

  • The method does not work well for semitransparent surfaces. If your scene contains objects that are semitransparent or that have semitransparent edges, (for example, tree billboards or mountains in Performer Town), these objects or edges may be cut or may be fogged more than the neighboring pixels. Even if a semitransparent edge of a billboard is outside the fog, it will not be smooth.

  • A layered patchy fog is extremely sensitive to the size of the fog area and the density of the layered fog. Specifically, the fog values accumulated along an arbitrary line crossing the bounding box of the fog area should not reach 1.

  • A patchy fog needs a stencil buffer and the deepest color buffers possible.The rendering quality on a visual with less than 12 bits per color component is low unless the fogged area is very small compared to the size of the whole scene.

  • If the blend_color extension is not available, the patchy fog color will be white.

Real-Time Shadows

You can create real-time shadows using the class pfShadow. You specify a set of light sources and a set of objects that cast shadows on all other objects in the scene. The class manages the drawing and renders shadows for each combination of a shadow caster and a light source. Shadows are rendered by projecting the objects as seen from the light source into a texture and projecting the texture onto a scene. To avoid computing the texture for each frame, a set of textures is precomputed at the first frame, then for each frame the best representative is chosen and warped to approximate the correct shadow.

The following sections further describe real-time shadows:

Creating a pfShadow

A pfShadow is not part of the scene graph; it is created separately by the application process. Once the pfShadow is created, you can specify the number of shadow casters by calling function pfShadowNumCasters() and then set each caster using the function pfShadowShadowCasters(). Each shadow caster is specified by a scene graph node and a matrix that contains the transformation of the node with respect to the scene graph root. Shadow casters are indexed from 0 to the number of casters minus 1.

Similarly, the number of light sources is set by function pfShadowNumSources(). A light source is defined by its position or direction, set by pfShadowSourcePos() or pfShadowLight().

A pfShadow needs information about the current eye position and view direction. Since this information is not directly accessible in a draw process, it is necessary to call pfShadowAddChannel() for each channel at the beginning of the application. Whenever the view changes, the application process has to call pfShadowUpdateView(). Even if the view does not change, this function must be called at least once in single-process mode or as many times as the number of buffers in a pfFlux in multiprocess mode. Without updating the view, the shadow is not rendered correctly.

The class initialization is completed by calling the function pfShadowApply() as shown in the following creation example:

pfShadow *shd = pfNewShadow();

pfShadowNumCasters(shd, 2);
pfShadowShadowCaster(shd, 0, node1, matrix1);
pfShadowShadowCaster(shd, 1, node2, matrix2);

pfShadowNumSources(shd, 1);
pfShadowSourcePos(shd, 0, x1, y1, z1, w1);

pfShadowAddChannel(channel);

pfShadowApply(shd);

Table 6-6 summarizes the functions for the initialization and drawing of a pfShadow.

Table 6-6. pfShadow Functions

Function

Action

pfNewShadow()

Create a pfShadow.

pfShadowNumCasters()

Set number of shadow casters.

pfShadowShadowCaster()

Set a shadow caster and its rotation matrix.

pfShadowAdjustCasterCenter()

Specify the translation of caster's center.

pfShadowNumSources()

Set number of light sources.

pfShadowSourcePos()

Specify light source position.

pfShadowLight()

Specify light source.

pfShadowAmbientFactor()

Set ambient factor.

pfShadowShadowTexture()

Set a user-defined shadow texture for a given caster and light source.

pfShadowTextureBlendFunc()

Set a function used when blending closest shadows.

pfShadowAddChannel()

Add a channel on which pfShadow is used.

pfShadowUpdateView()

Update the current view for all stored channels.

pfShadowUpdateCaster()

Update rotation matrix of a caster.

pfShadowFlags()

Set binary flags.

pfShadowVal()

Set a single attribute.

pfGetShadowDirData()

Get a pfDirData associated with the pfShadow.

pfShadowApply()

Initialize a pfShadow.

pfShadowDraw()

Draw the scene and shadows.

The attributes of a pfShadow are listed in Table 6-7.

Table 6-7. pfShadow Attributes

Attribute

Identifier

Default

Size of shadow texture

PFSHD_PARAM_TEXTURE_SIZE

512 x 512

Number of shadow textures

PFSHD_PARAM_NUM_TEXTURES

1

There is only one pfShadow flag, PFSHD_BLEND_TEXTURES. This blend-textures flag has a default of 0.

Drawing a Scene with Shadows

To draw a scene with real-time shadows, the draw process has to call the draw function provided by the pfShadow class: pfShadowDraw(). Before the first frame is rendered, all required shadow textures are precomputed. A warning is printed if the window size is smaller than the texture dimensions. Ensure that the window is not obscured; otherwise, the textures will not be correct.

By default, only the closest shadow texture is selected for any direction and it is skewed so that it approximates the correct shadow. Optionally, the flag PFSHD_BLEND_TEXTURES can be set using the function pfShadowFlags(). In this case, the two closest textures are selected and blended together, resulting in smoother transitions. Also, instead of a linear blend between the textures, you can define a blend function, mapping values 0–1 to the interval 0–1. The blend function can be set using the function pfShadowTextureBlendFunc().

Every time the caster changes its position or orientation with respect to the light source, it is necessary to update its matrix using pfShadowUpdateCaster() (the caster is identified by its index). When the caster's matrix changes, the shadow of the caster changes as well. In this case, the set of precomputed shadow textures is searched to find the one or two closest representatives.

Specifying Shadow Parameters

The shadow texture is used to darken the scene pixels when the texture texel is set to 1. The amount by which the scene pixel is darkened can be set by the function pfShadowAmbientFactor(). The default value is 0.6

As the caster is projected into a shadow texture, the center of the projection corresponds with the center of the bounding box of the caster's node. When the shadow texture is skewed to approximate shadows from a slightly different direction, it is best if the center of the projection corresponds with the center of the object. The bounding box center may not coincide with the center of the object (in the case of some long protruding parts) and you can use the function pfShadowAdjustCasterCenter() to shift the bounding box center toward the center of the object.

For each combination of a shadow caster and a light source, it is possible to specify the number of shadow textures used, their sizes, and a set of directions for which the textures are precomputed. The number of textures and their sizes can be set by the function pfShadowVal(), where the first parameter is PFSHD_PARAM_TEXTURE_SIZE or PFSHD_PARAM_NUM_TEXTURES.

The set of directions can be controlled by using the function pfGetShadowDirData() to get the pointer to the corresponding pfDirData, a class that stores data associated with a set of directions. Then you can either select the default mode or specify the directions directly. See following section “Assigning Data with Directions” for more details. By default, there is one texture of size 512 x 512 and the direction corresponds to the light direction (or a vector from a point light source to the object's center). If there are more textures, the original light direction is rotated around a horizontal direction, assuming that the object will primarily keep its horizontal position (for example, a helicopter or a plane).

A sample implementation of shadows is in the file perf/samples/pguide/libpf/C++/shadowsNew.

Assigning Data with Directions

The pfDirData class is used to store directional data—that is, data that depend on direction. A pfDirData stores an array of directions and an array of (void *) pointers representing the data associated with each direction.

The directions and data can be set using the function pfDirDataData(). Optionally, you can set only the directions using the function pfDirDataDirections() in the case that the associated data are defined later or generated internally by another OpenGL Performer class (such as pfShadow).

You can also generate directions automatically using the function pfDirDataGenerateDirections(). The first parameter defines one of the default sets of directions and the second parameter is used to specify additional values. At present only type PFDD_2D_ROTATE_AROUND_UP is supported, in which case the second parameter points to a 3D vector that is rotated around the up vector, creating a number of directions.

The data can be queried using the pfDirDataFindData() or pfDirDataFindData2() function. In the first case, the function finds the closest direction to the direction specified as the first parameter, copies it to the second parameter, and returns the pointer to the data associated with it. The input direction has to be normalized. The second function finds the two closest directions to the specified direction. It copies the two directions to the second parameter (which should point to an array of two vectors). The two pointers to the data associated with the two directions are copied to the array of two (void *) pointers specified as the third parameter. In addition, two weights associated with each direction are copied to the array of two floats. These weights are determined based on the distance of the end point of the input direction and each of the two closest directions.

Limitations of Real-Time Shadows

The following are limitations of real-time shadows in OpenGL Performer:

  • When projecting a caster into a shadow texture, pfSwitch children are selected according to switch value. In the case of pfLOD, the finest level is chosen. Also, pfSequences are ignored—which can be useful in the case of helicopter rotors, for example.

  • The pfShadow class uses cull programs to cull out geometry that is not affected by the shadow to make the multipass drawing more efficient. At present, though, the cull program used by the pfShadow class overwrites any other cull program you specify.


    Note: Ensure that you do not overwrite TravMode in your application by setting it to PFCULL_ALL. The mode is set by pfShadow when pfShadowApply() is called.


  • When projecting a caster into a shadow texture, pfSwitch and pfLOD may not be handled properly. Also, pfSequences are ignored—which can be useful in case of helicopter rotors, for example.

Image-Based Rendering

The image-based rendering approach is used for very complex objects. Such an object is represented by a set of images taken from many directions around it. When the object is rendered for each view direction, several closest views are blended together.

In OpenGL Performer, you can use the pfIBRnode class to represent complex objects. Unlike a pfBillboard, a parent class of pfIBRnode, the texture on pfGeoSets of a pfIBRnode is not static, but it changes based on the view direction for each pfGeoSet.

The following sections further describe image-based rendering:

Creating a pfIBRnode

A pfIBRnode is a child class of pfBillboard. You create a pfIRRnode in a fashion similar to that of a pfBillboard. Compared to a pfBillboard, a pfIBRnode has two additional parameters: a pfIBRtexture and an array of angles defining the initial rotation of the objects.

Each pfIBRnode has associated with it a single pfIBRtexture, which stores a set of images of the complex object as viewed from different directions. Each pfGeoSet is then rendered with a texture representing the view of the object from the given direction. A pfIBRtexture is specified using the function pfIBRnodeIBRtexture().

Using the function pfIBRnodeAngles(), you control the initial orientation of the complex object by specifying the rotation from the horizontal and vertical planes for each pfGeoSet. These angles are very useful in case of trees, for example, because you can use a different vertical angle for each instance of the tree. The trees then appear different, although they all use the same pfIBRtexture. The first value is ignored in the case that only one ring of views around the object is used.

You must set up a pfIBRnode so that the pfIBRtexture applied to it can modify properly the image at each frame. You do so in the following manner:

  1. Set the texture of the pfGeoState associated with each pfGeoSet of the pfIBRnode to the texture returned by the function pfGetIBRtextureDefaultTexture().

  2. If the pfIBRtexture has the flag PFIBR_USE_REG_COMBINERS set, enable multitexturing and specify texture coordinates for additional texture units.

  3. If the pfIBRtexture has the flag PFIBR_3D_VIEWS enabled, set the billboard rotation (PFBB_ROT) to PFBB_AXIAL_ROT.

See the file sample/pguide/C++/IBRnode.C for an example.

Creating a pfIBRtexture

A pfIBRtexture stores a set of images of a complex object as viewed from different directions. The images are loaded using the function pfIBRtextureLoadIBRtexture(). The first parameter specifies the path in which the images are stored as well as how they are indexed—for example, images/view%03d.rgb. The other two parameters specify the number of images and the increment between two loaded images. The increment specification is useful when the texture memory is limited; for instance, specifying step=2 causes every second image to be skipped. Optionally, you can specify the views using the function pfIBRtextureIBRtextures(). The parameters are an array of pointers to the textures containing the views and the number of the textures in this array.

There are two ways to organize the views. By default, the textures represent views around the object, all perpendicular to the vertical axis. In this case, specified textures form a single ring of views that are evenly spaced. If the flag PFIBR_3D_VIEWS is specified by the function pfIBRtextureFlags(), the textures form a set of rings. Each ring contains an array of evenly spaced views that have the same angle from the horizontal plane.

If the flag PFIBR_3D_VIEWS is not set, both functions pfIBRtextureLoadIBRtexture() and pfIBRtextureIBRtextures() will set one ring with the specified number of textures and a horizontal angle of 0. If the flag PFIBR_3D_VIEWS is set, the class checks whether a file info is present in the image directory. If it is, the information about rings is loaded from that file. The file contains two values on each line: the horizontal angle and the number of textures at each ring. If the file is not present in the image directory, you must specify the rings before the images are loaded by calling the functions pfIBRtextureNumRings() and pfIBRtextureRing(). Rings are indexed from 0 and should be ordered by the horizontal angle, with the lowest angle at index 0. Each ring can have a different number of textures associated with it.

When 3D views are used, the image files read by function pfIBRtextureLoadIBRtexture() should be indexed by the ring index and the index of the image in a given ring. Specify the format string in the manner shown in the following example:

images/view%02d_%03d.rgb

If you specify the textures using the function pfIBRtextureIBRtextures(), the texture pointers are all stored in a single array, starting with textures of the first ring, followed by textures of the second ring, and so on.

It is assumed that the views in each ring are uniformly spaced and they are ordered clockwise with respect to the vertical axis. If the views are ordered in the opposite direction, use the function pfIBRtextureDirection() to set the direction to –1.

See the pfIBRnode man page and the program sample/pguide/C++/IBRnode for more details about associating a pfIBRtexture with a pfIBRnode.

Parameters Controlling Drawing of a pfIBRnode

At present, the pfIBRtexture class is used only by the pfIBRnode class. The pfIBRtexture class provides a draw function for pfGeoSets that belong to the pfIBRnode, but the draw process is transparent to you. You can control the drawing by setting flags using the function pfIBRtextureFlags(). If the flag PFIBR_NEAREST is set, the closest view from the closest ring is selected and applied as a texture of the pfGeoSet. This approach is fast on all platforms, but it results in visible jumps when the texture is changed. Thus, by default, the flag PFIBR_NEAREST is not set and the two or, in case of 3D views, four closest views are blended together. If the graphics hardware supports register combiners, flags PFIBR_USE_REG_COMBINERS and PFIBR_USE_2D_TEXTURES are automatically set by the class constructor and blending of textures can be done in one pass.

By default on IRIX, the flag PFIBR_USE_2D_TEXTURES is not set and a 3D texture is used for fast blending between the two closest views. This may result in flickering when the object is viewed from a distance due to the lack of mipmapping. Thus, set the flag PFIBR_USE_2D_TEXTURES to avoid this problem, although this causes each pfGeoSet to be rendered multiple times.

Limitations

The following are current limitations of image-based rendering in OpenGL Performer:

  • A pfIBRtexture applied to a pfIBRnode is not properly rotated when the pfIBRnode is viewed from the top. This may result in visible rotation of the texture with respect to the ground.

  • When the flag PFIBR_3D_VIEWS is set in a pfIBRtexture, do not use 3D textures. This mode is not implemented.

A Tool for Creating Images of an Object

You can use the program makeIBRimages from the directory /usr/share/Performer/src/conv/ to create images (views) of a specified object from a set of directions. The input is a file that can be read by OpenGL Performer and the output is a set of images of that object that can be directly used as an input for a pfIBRtexture. The images are stored in a directory specified using the option -f.

If a text file info is present in the output directory, a set of 3D views is rendered. The file has the same syntax as described in section “Creating a pfIBRtexture”. Each line of the file info contains two values: the angle from the horizontal plane and how many views are created for that angle. The images are then indexed by two integer values that are appended to the name specified by the option -f. The first value is the ring index of the views and the second one indexes the views within the ring.

If the file info is not present, a set of N views (set by the option -n) is computed around the object using the horizontal angle of 0. In this case, only one index is appended to the image name.

If you specify the option -pfb, the program outputs a pfb file in the specified directory. The file contains a single pfIBRnode that uses the created images.


Note:
Before loading perfly, ensure that PFPATH is set to the directory that contains the images.

If your machine does not support a single-buffered visual with at least 8 bits per red, green, blue, and alpha component, the images may be missing the alpha channel. Note the number of alpha bits printed when makeIBRimages begins.

When using pfIBRnodes and pfIBRtextures in perfly, you also need an alpha buffer. If the pfIBRnode is rendered as a full rectangle, try the command-line parameter –9, in which case perfly requests a visual with alpha.

To obtain the full set of command-line options, run the program makeIBRimages without any parameters.