Jeff here. Fair warning – this is a technical post (but there’s video!)
We’ve all been hard at work on Rustclad lately. The world is coming along famously, and we’re at the point now where we’re looking for those final touches to really make it all come alive. You may have been able to tell from the screenshots, but we’ve been embracing a sort of retro high-fantasy look, and loving it. Rae had the great idea of bringing in some old-school special FX to really tie it all up.
We all love Big Trouble in Little China and the other great fantasy films from our childhood, with their over the top hand-painted cel animation effects. Rae especially was interested in seeing if we could faithfully reproduce a lighting effect. Our inspiration was this scene:
We had a great conversation where we debated whether we wanted our lighting bolts to look more like Big Trouble in Little China or Ghostbusters – one of those moments where I realize how much I love what I do. Anyways, there’s a lot going on in a lightning bolt effect. The energy crackles with life and takes on an intelligence of its own, with forking tendrils caressing the surfaces in its path. A simple scrolling texture or particle effect won’t do.
NVIDIA has a great library of effects with code samples and well-written papers describing them in detail. They had a lightning effect which looked promising, so I started there.
It’s an interesting effect, but I quickly realized that it wasn’t quite what I wanted. The result was a big forky and erratic, and didn’t quite have the look I was going for. Also, the effect relies on the use of the DirectX 10 geometry shader technology (though it could probably be written to work on DX9), and rendering to an offscreen buffer and compositing back to the scene, which seemed a bit costly for a single effect.
So I started thinking about other options, and considered what our engine already excels at. One advantage of working with your own engine is that you can design it to do a few things extremely well, and one of those things for us is splines and spline-based tesselation and animation. We use splines for many things in Rustclad, like directing the flow of particles, laying down paths, deforming terrain, and even animating characters. Splines are wonderful tools because they allow you to generate what appears to be complex behavior while only having to manipulate a few control points. And as such, they seemed like they might be a great fit for a lightning effect. And it turns out that they are.
So to cut to the chase, here’s what our current lighting effect looks like.
To create the effect, a hand-placed central spline is used, which comprises the central bolt of lighting. A series of smaller secondary bolts are randomly placed along it, each which use their own spline which is procedurally generated from the central spline. A pair of wave effects (one random and jagged, one smoother) are used to deform the spline, and finally, a jagged lightning bolt texture is scrolled along the shaft of the spline to enhance the feeling of energetic movement. The geometry is then billboarded so that the surface of each bolt always faces the viewer.
A big plus to implementing the effect this way is that all of the heavy lifting is done in the vertex shader and there’s no scene compositing and offscreen rendering, making the effect extremely cheap and usable on a wide variety of graphics hardware. Plus, relying on the engine’s existing spline tech allows us to easily create a lot of cool effects. For example, the end of a lighting bolt spline could be attached to a character that’s moving around the scene, and the lightning bolt would easily arc from its source to the animated object with no additional fx work needed.
Let’s breakdown the effect even more.
Create a series of quad strips for each bolt. The vertex data is pretty simple, containing four floats per vertex.
- Interval: A value from 0-1 representing where along the primary spline to place the vertex. Note that for secondary bolts, this value is the same for all vertices and gets scrolled based on time in the shader to give the bolt the effect of forward movement.
- Subinterval: For secondary bolts, this is a value from 0-1 representing where the vertex resides along the secondary procedurally generated spline.
- Speed: A multiplier, consistent across all vertices for a given bolt, that indicates how fast the bolt is moving. This creates some nice randomness across the entire effect. This value is always 0 for the primary bolt.
- Offset: The view-space horizontal vertex offset from the center of the bolt, used when billboarding the geometry. Either -1 or 1.
Generate lightning bolt segments. All the heavy lifting happens here. For our implementation, the primary and secondary lightning bolts use the same vertex shader. The primary bolt could be drawn with a much cheaper shader by rendering the primary and secondary bolts in separate passes, but combining them into a single shader and saving a draw call will likely perform better.
- Scroll the vertex interval based on time, modified by the speed value.
- Generate a ‘jagged’ wave for each vertex. This wave is intended to break up repeating patterns and add jagginess to the effect. It does not scroll across the bolt with time.
- Generate a ‘smooth’ wave for each vertex, and combine the two waves. This wave is intended to give the bolt a sense of forward motion, as if it’s projecting out from the source.
- Randomly jitter the end position of the base spline, discretized and modified over time. This gives the bolt the effect of jumping around. NOTE: This step is possibly better suited for the CPU.
- Generate a point on the spline, offset in view space by the vertex offset and combined wave function. If this is the primary bolt, then we’re done.
- If this is a secondary bolt, then we need to generate a new spline. The point generated in step 5 becomes the start position for the secondary spline. The end position for the spline is generated by taking the end position of the base spline, and then applying a random offset. We use the interval as the seed for a pseudorandom number generator, so all vertices in the bolt get the same value. For the control points of the spline, we use the base spline’s control points, adjusted so that they fall roughly between the new start and end points.
- Generate a point on the new spline, offset in view space by the vertex offset and a new set of waves.
- Scroll the v texture coordinate along the interval (secondary interval, for secondary waves) and fade out the bolt’s opacity at the ends.
The easy part!
- Sample from the bolt texture to get opacity, and combine with the vertex opacity.
- Compare the pixel depth to the scene depth to fade out opacity near objects. This isn’t strictly necessary, but adds really nice soft edges to the effect.
- Output color and opacity.
And that’s it! Hope you enjoyed this little write-up, and I look forward to sharing more details on future effects!
Until next time.