JavaScript EditorFree JavaScript Editor     Ajax Editor 



Main Page
Previous Page
Next Page

16.8. Wobble

The previous three examples discussed animating the geometry of an object and used the vertex processor to achieve this animation (because the geometry of an object cannot be modified by the fragment processor). The fragment processor can also create animation effects. The main purpose of most fragment shaders is to compute the fragment color, and any of the factors that affect this computation can be varied over time. In this section, we look at a shader that perturbs the texture coordinates in a time-varying way to achieve an oscillating or wobbling effect. With the right texture, this effect can make it very simple to produce an animated effect to simulate a gelatinous surface or a "dancing" logo.

This shader was developed to mimic the wobbly 2D effects demonstrated in some of the real-time graphics demos that are available on the Web (see http://www.scene.org for some examples). Its author, Antonio Tejada, wanted to use the OpenGL Shading Language to create a similar effect.

The central premise of the shader is that a sine function is used in the fragment shader to perturb the texture coordinates before the texture lookup operation. The amount and frequency of the perturbation can be controlled through uniform variables sent by the application. Because the goal of the shader was to produce an effect that looked good, the accuracy of the sine computation was not critical. For this reason and because the sine function had not been implemented at the time he wrote this shader, Antonio chose to approximate the sine value by using the first two terms of the Taylor series for sine. The fragment shader would have been simpler if the built-in sin function had been used, but this approach demonstrates that numerical methods can be used as needed within a shader. (As to whether using two terms of the Taylor series would result in better performance than using the built-in sin function, it's hard to say. It probably varies from one graphics hardware vendor to the next, depending on how the sin function is implemented.)

For this shader to work properly, the application must provide the frequency and amplitude of the wobbles, as well as a light position. In addition, the application increments a uniform variable called StartRad at each frame. This value is used as the basis for the perturbation calculation in the fragment shader. By incrementing the value at each frame, we animate the wobble effect. The application must provide the vertex position, the surface normal, and the texture coordinate at each vertex of the object to be rendered.

The vertex shader for the wobble effect is responsible for a simple lighting computation based on the surface normal and the light position provided by the application. It passes along the texture coordinate without modification. This is exactly the same as the functionality of the Earth vertex shader described in Section 10.2.2, so we can simply use that vertex shader.

The fragment shader to achieve the wobbling effect is shown in Listing 16.8. It receives as input the varying variable LightIntensity as computed by the vertex shader. This variable is used at the very end to apply a lighting effect to the fragment. The uniform variable StartRad provides the starting point for the perturbation computation in radians, and it is incremented by the application at each frame to animate the wobble effect. We can make the wobble effect go faster by using a larger increment value, and we can make it go slower by using a smaller increment amount. We found that an increment value of about 1° gave visually pleasing results.

The frequency and amplitude of the wobbles can be adjusted by the application with the uniform variables Freq and Amplitude. These are defined as vec2 variables so that the x and y components can be adjusted independently. The final uniform variable defined by this fragment shader is WobbleTex, which specifies the texture unit to be used for accessing the 2D texture that is to be wobbled.

For the Taylor series approximation for sine to give more precise results, it is necessary to ensure that the value for which sine is computed is in the range [p/2,p/2]. The constants C_PI (p), C_2PI (2p), C_2PI_I (1/2p), and C_PI_2 (p/2) are defined to assist in this process.

The first half of the fragment shader computes a perturbation factor for the x direction. We want to end up with a perturbation factor that depends on both the s and the t components of the texture coordinate. To this end, the local variable rad is computed as a linear function of the s and t values of the texture coordinate. (A similar but different expression computes the y perturbation factor in the second half of the shader.) The current value of StartRad is added. Finally, the x component of Freq is used to scale the result.

The value for rad increases as the value for StartRad increases. As the scaling factor Freq.x increases, the frequency of the wobbles also increases. The scaling factor should be increased as the size of the texture increases on the screen to keep the apparent frequency of the wobbles the same at different scales. You can think of the Freq uniform variable as the Richter scale for wobbles. A value of 0 results in no wobbles whatsoever. A value of 1.0 results in gentle rocking, a value of 2.0 causes jiggling, a value of 4.0 results in wobbling, and a value of 8.0 results in magnitude 8.0 earthquake-like effects.

The next seven lines of the shader bring the value of rad into the range [p/2,p/2]. When this is accomplished, we can compute sin(rad) by using the first two terms of the Taylor series for sine, which is just x x3/3! The result of this computation is multiplied by the x component of Amplitude. The value for the computed sine value will be in the range [-1,1]. If we just add this value to the texture coordinate as the perturbation factor, it will really perturb the texture coordinate. We want a wobble, not an explosion! Multiplying the computed sine value by a value of 0.05 results in reasonably sized wobbles. Increasing this scale factor makes the wobbles bigger, and decreasing it makes them smaller. You can think of this as how far the texture coordinate is stretched from its original value. Using a value of 0.05 means that the perturbation alters the original texture coordinate by no more than ±0.05. A value of 0.5 means that the perturbation alters the original texture coordinate by no more than ±0.5.

With the x perturbation factor computed, the whole process is repeated to compute the y perturbation factor. This computation is also based on a linear function of the s and t texture coordinate values, but it differs from that used for the x perturbation factor. Computing the y perturbation value differently avoids symmetries between the x and y perturbation factors in the final wobbling effect, which doesn't look as good when animated.

With the perturbation factors computed, we can finally do our (perturbed) texture access. The color value that is retrieved from the texture map is multiplied by LightIntensity to compute the final color value for the fragment. Several frames from the animation produced by this shader are shown in Color Plate 29. These frames show the shader applied to a logo to illustrate the perturbation effects more clearly in static images. But the animation effect is also quite striking when the texture used looks like the surface of water, lava, slime, or even animal/monster skin.

Listing 16.8. Fragment shader for wobble effect

// Constants
const float C_PI    = 3.1415;
const float C_2PI   = 2.0 * C_PI;
const float C_2PI_I = 1.0 / (2.0 * C_PI);
const float C_PI_2  = C_PI / 2.0;

varying float LightIntensity;

uniform float StartRad;
uniform vec2  Freq;
uniform vec2  Amplitude;

uniform sampler2D WobbleTex;

void main()
{
    vec2  perturb;
    float rad;
    vec3  color;

    // Compute a perturbation factor for the x-direction
    rad = (gl_TexCoord[0].s + gl_TexCoord[0].t - 1.0 + StartRad) * Freq.x;

    // Wrap to -2.0*PI, 2*PI
    rad = rad * C_2PI_I;
    rad = fract(rad);
    rad = rad * C_2PI;

    // Center in -PI, PI
    if (rad >  C_PI) rad = rad - C_2PI;
    if (rad < -C_PI) rad = rad + C_2PI;

    // Center in -PI/2, PI/2
    if (rad >  C_PI_2) rad =  C_PI - rad;
    if (rad < -C_PI_2) rad = -C_PI - rad;

    perturb.x = (rad - (rad * rad * rad / 6.0)) * Amplitude.x;

    // Now compute a perturbation factor for the y-direction
    rad = (gl_TexCoord[0].s - gl_TexCoord[0].t + StartRad) * Freq.y;

    // Wrap to -2*PI, 2*PI
    rad = rad * C_2PI_I;
    rad = fract(rad);
    rad = rad * C_2PI;

    // Center in -PI, PI
    if (rad >  C_PI) rad = rad - C_2PI;
    if (rad < -C_PI) rad = rad + C_2PI;

    // Center in -PI/2, PI/2
    if (rad >  C_PI_2) rad =  C_PI - rad;
    if (rad < -C_PI_2) rad = -C_PI - rad;

    perturb.y = (rad - (rad * rad * rad / 6.0)) * Amplitude.y;

    color = vec3(texture2D(WobbleTex, perturb + gl_TexCoord[0].st));

    gl_FragColor = vec4(color * LightIntensity, 1.0);
}


Previous Page
Next Page




JavaScript EditorAjax Editor     JavaScript Editor