CS 381 Fall 2013  >  Lecture Notes for Monday, November 4, 2013

CS 381 Fall 2013
Lecture Notes for Monday, November 4, 2013

Billboarding

Introduction

Billboarding means to turn a polygon so that it always faces the camera. There are two (or three) ways we might do this.

Cylindrical billboarding
Turn about a single (vertical?) axis, to face the camera as nearly as possible.
Spherical billboarding
Turn about an arbitrary axis, to face the camera exactly.

In addition, if we do spherical billboarding, we might be interested in rotating the polygon so that its top edge stays on top.

Note: Here, an axis is a line that we rotate about. This line is not necessarily the \(x\)-, \(y\)-, or \(z\)-axis.

Billboarding has some surprising applications. In particular we can simulate objects that look the same from different directions by texture mapping a picture of the object on a polygon, and then billboarding the polygon.

We will use this idea to draw a fuzzy blob as a translucent, textured, billboarded polygon. We will use spherical billboarding without worrying about which way is up.

The Math

We consider spherical billboarding. Say we have vectors \(\mathbf{u}\), \(\mathbf{v}\). To rotate \(\mathbf{u}\) so that it points in the direction of \(\mathbf{v}\), we want to rotate through the angle between the vectors, about an axis perpendicular to both. The angle between the vectors is the angle with cosine equal to \(\frac{\mathbf{u}\cdot\mathbf{v}}{||u||\,||v||}\). An axis perpendicular to both is \(\mathbf{u}\times\mathbf{v}\).

When we do billboarding, \(\mathbf{u}\) is the normal vector for our polygon, and \(\mathbf{v}\) is the vector that points from the center of the polygon to the camera. If we draw the polygon parallel to the \(x\),\(y\)-plane, then \(\mathbf{u} = \langle 0,0,1 \rangle\).

Now suppose \(\mathbf{u}\) is as above, and \(\mathbf{v} = \langle a, b, c \rangle.\) Then

\[ \begin{aligned} \frac{\mathbf{u}\cdot\mathbf{v}}{||u||\,||v||} &= \frac{c}{\sqrt{a^2+b^2+c^2}};\\ \mathbf{u}\times\mathbf{v} &= \langle -b, a, 0 \rangle. \end{aligned} \]

So the rotation that takes \(\langle 0,0,1 \rangle\) and makes it point toward the camera from \(\langle a,b,c \rangle\) is the rotation through the angle with measure \(\arccos\frac{c}{\sqrt{a^2+b^2+c^2}}\) about the axis \(\langle -b, a, 0 \rangle\).

That does not work if \(a = b = 0\). In that case, if \(c ≥ 0\), then vector \(u\) already points directly at the camera, and no rotation is necessary. If \(c < 0\), then \(u\) points directly away from the camera, then a \(180^\circ\) rotation about the \(y\)-axis will do the job.

We will not be doing cylindrical billboarding, but it is easy to figure out now. Suppose we want to rotate about the \(y\)-axis. Just do the above, but first set \(b = 0\).

The Code

To billboard, we wrote a function sphericalBillboard, which takes two positions, and does a glRotate* command that turns the vector \(\langle 0,0,1 \rangle\) so that it points in the direction from one position to the other. Thus, we can pass the center of our polygon and the camera position (determined using whereAmI).

[C++]

void sphericalBillboard(const vector<GLdouble> & campos,
                        const vector<GLdouble> & objpos)
{
    // Compute vector from object to camera
    vector<GLdouble> v(3);
    for (int i = 0; i < 3; ++i)
        v[i] = campos[i] - objpos[i];

    // Degenerate cases
    if (v[0] == 0.f && v[1] == 0.f)
    {
        if (v[2] < 0.f)
            glRotated(180., 0.,1.,0.);
        return;
    }

    // Non-degenerate case
    const double pi = 3.1415926535898;
    double dot = v[2] / sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);
    double ang = acos(dot) * 180./pi;  // Radians
    glRotated(ang, -v[1],v[0],0.);
}

Then we can call function sphericalBillboard at the place where we would typically do a glRotate*.

Blending

We can draw a fuzzy blob as an image that is a solid color, but fades from opaque to transparent as we go from the center to the edge of the image.

Translucency can be encoded using alpha, the fourth component of a color. By convention, an alpha of \(1.0\) represents opaque, while \(0.0\) represents transparent.

If a scene contains translucent objects, then, to draw it correctly, we render the polygons in back-to-front order (recall our discussion of HSR methods) and we use blending: combining the fragment color and the existing color in the framebuffer.

Blending occurs at the end of the Fragment Processing section of the pipeline; in particular, it is done after (and thus outside) the fragment shader. We enable blending with glEnable. Disable it again with a similar call to glDisable.

[C++]

glEnable(GL_BLEND);

We control how the fragment and framebuffer colors are combined using glBlendFunc. This takes two parameters, both of which should be prededined constants. The first parameter is the fraction of the fragment color (the source color) that we use; the second is the fraction of the framebuffer color (the destination color). Then the two are added to obtain the final color, which is stored in the framebuffer.

The ordinary way to blend is to use the alpha component of the source color as a linear interpolation parameter between the two colors:

[C++]

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

In this particular application, it does not matter so much whether the polygons are drawn back-to-front. Therefore, we will not worry about drawing order. (We do need to disable the depth test, however.) The result is slightly incorrect, but not noticeably so.

We can create our blob as a texture in which every texel has the same R, G, B values, and alpha ranges from \(1.0\) at the center to \(0.0\) at the edges. I will use a Gaussian blur: set each texel’s alpha component to \(e^{-kd^2}\), where \(d\) is the distance of the texel from the center of the texture, and \(k\) is some appropriate constant.

The texture is easily generated, but it does need a shader to use it. Our basic-texture-mapping shaders (basic_tex_...) already do what we need.

See blast.cpp for an application that demonstrates billboarding and blending. The application uses the same shaders as usetextures.cpp; however, the basic_tex shaders are recommended.


CS 381 Fall 2013: Lecture Notes for Monday, November 4, 2013 / Updated: 4 Nov 2013 / Glenn G. Chappell