July 24, 2014
Press Releases
July 24, 2014
PR Newswire
View All
View All     Submit Event

If you enjoy reading this site, you might also want to check out these UBM Tech sites:

WebGL Terrain Rendering in Trigger Rally - Part 2
by Jasmine Kent on 09/08/13 12:00:00 am

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

Welcome to this series of posts about WebGL Terrain Rendering in Trigger Rally!

If you haven't yet, you should read Part 1 where I talk about the importance of minimizing CPU-GPU data transfer, and introduce the idea of combining static vertex buffers with height data stored in textures.

In this post, I'll discuss the vertex data format and morphing.

### Rings

Geoclipmap rendering uses a set of square “rings” around the viewpoint, where each ring is twice the size of the previous one, and so has half the spatial resolution. This results in approximately consistent screen space resolution of the terrain at all distances. The innermost (highest resolution) ring has its center filled in, becoming a simple square grid of triangles:

Geometry that repeats itself in a grid pattern has a nice property: we can translate it by exact multiples of the grid size without any visible change to the user, except that the edges appear to have moved:

We can use this property to move the geometry around, keeping it approximately centered under the camera, but without it being obvious that this movement is occurring.

Each ring has its own grid size, and since the translation distance depends on the geometry size, we will need to move the rings independently of each other. Thus the vertex shader needs to know which layer a vertex belongs to, both for translation and so that it can morph it correctly (we'll come back to morphing in a minute.)

So the vertex attributes we need are:

• Position X
• Position Y
• Layer index

In Trigger Rally's implementation, we use an [X,Y,Z] 3-vector and encode the layer index as Z, so that in our raw geometry the rings appear to be stacked.

### Filling in the gaps

Each ring is drawn at a different scale, and they are also translated by multiples of this scale. So there is a problem: when one ring is translated but its neighbor is not, a gap will appear:

One way of fixing this is to extend the edge of the ring with extra geometry, known as a skirt. In the geoclipmapping approached described in this paper, the skirt is carefully assembled from many smaller pieces, using multiple small vertex buffers and careful CPU logic. We don’t want that!

When implementing the terrain in Trigger Rally, I spent hours trying to find a clever way to design the skirt to be both seamless and entirely static, to no avail.

But then I met Florian Bösch at last year’s WebGL Camp Europe, and he suggested just making the rings bigger and letting them overlap.

Now, seasoned graphics programmers will probably be gasping “No! You can’t overlap geometry! It’s wasteful and you’ll get horrible depth fighting artifacts!” But other than a tiny bit of overdraw, it actually turns out to be an excellent solution provided that the geometry matches up exactly. Which brings us to...

### Morphing

At the boundary between rings we have geometry at one resolution next to geometry at half that resolution. We need to introduce transition regions at the edge of each ring, where the geometry gradually moves or “morphs” from high resolution to low, so that by the time you reach the edge of the ring it will match up perfectly with the next ring beyond.

Here’s how each vertex needs to move in order to match the next ring:

We need to perform this translation in the vertex shader. The simplest approach would be to include the morph direction vector as part of the vertex data format, but again Florian had a better suggestion: use modular arithmetic!

To show how this works, let’s tabulate the data:

 Vertex coordinate 0 1 2 3 4 MOD 2 0 1 0 1 0 MOD 4 0 1 2 3 0 Morph vector 0 -1 0 1 0

So we can compute the morph vector from the vertex position with this GLSL code:

`vec2 morphVector = mod(position.xy, 2.0) * (mod(position.xy, 4.0) - 2.0);`

No extra vertex attributes needed!

### Tune in next time

In the next post, I’ll talk about how multi-resolution height data is stored in Trigger Rally, and how it's processed in the vertex shader. After that we’ll look at surface shading in the fragment shader, and how to render scenery meshes efficiently.

@jareiko

Continue to Part 3...

### Related Jobs

Disney Consumer Products — Glendale, California, United States
[07.23.14]

Contract Game Programmer
Zindagi Games — Camarillo, California, United States
[07.23.14]

Software Engineer
Telltale Games — San Rafael, California, United States
[07.23.14]

Core Technology – Client Network Engineer
Gearbox Software — Plano, Texas, United States
[07.23.14]

Release Engineer

 Lindsay Kay
Real nice breakdown on a very topical topic - thanks for sharing!

 Jasmine Kent
 You're most welcome! :)

 Luke Quinn
Excellent article; Your solution reminds me a lot of dealing with parallax scrolling and multiple sprite entities in 2D.
Is the terrain in Trigger Rally procedurally generated?
I was messing about driving up and over the mountains looking to see how you dealt with the edge of the world, but didn't find it.
The viewable distance was amazing too; I would have creamed my pants seeing that on the original XBox and here you are making my browser do it.
Thanks for sharing :)

 Jasmine Kent
 Thank you! The terrain is from satellite data, and it does eventually repeat if you go far enough. More on this in part 3, coming soon! :)

 Kim Pallister
I did a terrain engine like this back in 2000 using DX fixed function pipeline (screenshot here: http://www.flipcode.com/archives/09-15-2000.jpg ).

We did the stitching where one "ring" met another and it really was not complicated at all. I'll have to look up if I can find the source to it. maybe would be useful to try in your demo application.

 Jasmine Kent
 Sounds interesting! Please let me know if you manage to dig up the source.

 none Comment: