Programming

Glowing graphics for that futuristic sci-fi feeling

Glow is a relatively simple effect that gives life to the graphics in Radial Blitz. It blends well into the sci-fi theme. Creating the glow requires a glow map, a second world rendering pass, and some blurring.

To help understand this article I suggest downloading the game for iPad or iPhone. Radial Blitz – A high paced 3d reaction game

Glow Map

In addition to the standard textures, like diffuse and normal, the Radial Blitz models have a glow map. The glow map is an RGBA image to allow for colored glow. They are quite sparse since only limited parts of the models should glow.

A 4-channel glow map is overkill for our models, as none of them really use more than one glow color. We could have instead adapted to a single-channel map and store a glow color along with the model and gotten similar results.

One difficulty we encountered was painting the diffuse textures. Since we were used to using textures without glow, we at first forgot to dim the glowing parts in the diffuse textures. This resulted in models with essentially extra bright glowing spots. Glow can’t just be added on, the other textures must be adapted to combine with it.

Rendering

The pipeline for having glowing objects is roughly as follows:

  1. Render the world with normal lighting to the target buffer
  2. Render the world with the glow maps to a glow buffer
  3. Blur the glow buffer
  4. Add the blurred glow buffer to the target buffer

The first step, primary rendering, I’ll describe in another article later. The glow stage of rendering is more interesting here.

Rendering the world with a glow texture unfortunately requires rendering all 3d objects a second time. The target OpenGL ES 2.0 doesn’t support multiple render targets, a feature that could eliminate this second pass. At least the second pass is cheaper than the the first one. It involves only one texture, the glow map, and there are no lighting calculations.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public float3 GlowColor: float3(0);

float4 GlowMapColorA:  req(TexCoord as float2) req(GlowMap as Texture2D)
    GlowMap != null ? sample( GlowMap, TexCoord, SamplerState.LinearWrap ) : float4(0);
public float3 GlowMapColor : GlowMapColorA.XYZ * GlowMapColorA.W;

public float3 MaterialGlow : GlowColor + GlowMapColor, GlowColor;

...

PixelColor: float4( MaterialGlow, 1 ), float4(0);

PixelColor is the final color for the pass, which simply uses the MaterialGlow here. Note the additional GlowColor, which allows a model to be rendered with a non-map glow color. This is used for the firing beams, fireball, and enemy shields.

These are shader fragments in Fuse’s delightful draw block form). Though this is a really useful feature, it isn’t part of the product’s primary feature set. Fuse is targeted toward app development, not games.

radial_glow_beam_shield

A boss with glowing energy beams, glowing satellites, a glowing shield, and some glow on the tunnel walls.

This glow is obviously quick and dirty. It could easily be improved by doing lighting/normal and distance calculations on the second pass as well. That proved too expensive however, so we stuck with the quick approach.

Blurring

The glow pass itself produces a rather sharp image. To get the soft glowing effect we pass it through a blur filter.

The filter is a gaussian blur — probably just a rough approximate after optimizations. It is done in two passes, one horizontal blur then one vertical blur. Each destination pixel is sampled across 9 input texels. A blurSize controls the spread of those source pixels.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
fixed float m[9] = { 0.05f, 0.09f, 0.12f, 0.15f, 0.18f, 0.15f, 0.12f, 0.09f, 0.05f };
draw Quad {
    Texture2D Source: glowBuffer.ColorBuffer;
    float BlurSize: blurSize / glowBuffer.Size.X;
    PixelColor: {
        float alphaSum = 0;
        float4 colorSum = float4(0);
        for( int i=0; i < 9; ++i ) {
            var c = sample( Source, float2( TexCoord.X - (i-4) * BlurSize, TexCoord.Y ) );
            c.W *= opacity;
            alphaSum += c.W * m[i];
            colorSum += c * c.W * m[i];
        }
        return alphaSum == 0 ? float4(0) : float4( colorSum.XYZ/alphaSum, colorSum.W * opacity );
    };
};

I’m assuming that loop is unrolled, but I haven’t actually verified that (the shader speed would indicate it is). The bigger performance issue here is bandwidth. Mobile devices can not move much memory around per frame. The good thing is that the glow doesn’t need to have the same fidelity as the primary rendering.

The resolution of the glow buffers is tied to the quality level of the game. On the low setting it’s only 1/8th the width/height of the primary buffer. This saves a lot of memory transfer and processing time. Sure, it’s not as pretty as high resolution blurring, but framerate is more important here.

Adding

Once the blurring is done the result is simply added to the target buffer.

1
2
3
4
5
6
7
8
draw Quad {
    float4 SourceColor: sample( blurFB.ColorBuffer, float2( TexCoord.X, 1-TexCoord.Y ),
        SamplerState.LinearClamp );
    PixelColor: SourceColor;
    BlendEnabled: true;
    BlendSrc: BlendOperand.One;
    BlendDst: BlendOperand.One;
};

This is made possible by having the glow buffer itself render against a black background, instead of transparency. The resulting blur pixels can be directly added to the target pixels. This is a standard GL blending mode that is much faster than doing the blending in another shader.

Drawbacks and result

The biggest issue with glow is speed. Having to rerender the scene, do the blurring, and copy it to the destination buffer is a big workload for mobile devices. On a typical desktop graphics cards this really doesn’t amount to much time at all, but on my test iPhone 4s however the glow drops the frame rate significantly.

This glow isn’t realistic in terms of how light behaves; it’s just a a nice effect. An artifact of this approach is glow popping. The glow pass can only render on non-occluded objects. Hidden glow sources cannot bleed into the visible area in this approach. The light will be completely hidden, then pop into view across it’s entire 9×9 pixels. It’s also flat lighting, meaning it has no volume to encompass nearby objects near in the Z-axis. I don’t think a normal player will spot these minor issues, but if you’re attentive you certainly can see them at times.

Overall I’m very happy with the result of the glow. It produces a nice atmosphere, adding volume to the scene.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s