Rendering dunes terrain in WebGL
Tags: webgl, javascript, 3d, webdev
Published: 2021-01-19 09:41:23

We’ve released a new [live wallpaper for Android](https://play.google.com/store/apps/details?id=org.androidworks.livewallpaperdunes) and simultaneously published a live demo page showcasing all features of an app. You can check the [webpage here](https://keaukraine.github.io/webgl-dunes/index.html).

Historically, the web demo was created first — it was used as a prototyping playground to compose a scene and to fine-tune shaders. Also, this really helps in sharing work between a team of two people without necessity to learn Android Studio for both. And when everything was polished and looked good enough, an Android app was created quite fast based on the web demo code. Porting code to Android is a quite straightforward and easy process because our [WebGL framework](https://github.com/keaukraine/webgl-framework) has the same method signatures as the framework used in Android apps.

## Scene composition

Scene is quite simple and contains just six objects — terrain, sky, dust particles, sun, birds, and palm trees.

To examine how objects are rendered, you can take a look at `drawScene()` method in [DunesRenderer.ts](https://github.com/keaukraine/webgl-dunes/blob/master/src/DunesRenderer.ts) — first we render depth map to texture (this is needed for soft particles), then render on-screen objects in front-to-back order (first closest and largest objects, then distant) to efficiently utilize z-buffer culling.
Terrain in the scene is represented as a single square tile. The base for terrain is [this model purchased on CGTrader](https://www.cgtrader.com/3d-models/exterior/landscape/desert-fa02d784-9991-4458-9a01-95cf91ec0178). Its polycount is reduced to 31k faces in order not to split geometry and to draw it with a single draw call. This polycount produces a reasonably good quality. However, its area is not quite large enough to create a feel of infinite sand desert — when the camera is placed slightly above terrain boundaries of square terrain its limits are clearly visible:
![Clearly visible boundaries of terrain
](https://dev-to-uploads.s3.amazonaws.com/i/5vgef4ym63by6zrkh360.jpg)

Apparently this reduces the range of camera movement and creates an unwanted feeling of terrain “floating” in space. To eliminate this effect and improve the immersiveness of the scene we use a technique called “terrain skirt”. We learned about it from this great [GDC talk about terrain in Halo Wars](https://youtu.be/In1wzUDopLM?t=2586). You should definitely watch the whole video as it explains a lot of other interesting and unique techniques which might come in handy. The idea behind this terrain skirt is to render the same tile at the edges of tile but mirrored away from the center of the scene. This significantly expands the area of terrain. This screenshot shows all 8 additional tiles rendered (with additional gaps to separate tiles):
![Additional skirt tiles
](https://dev-to-uploads.s3.amazonaws.com/i/5usuy4twg5eri7d82nyy.jpg)

You can see a mirroring of tiles at the edges where duplicate tiles connect with the main one but it is not noticeable in the final app because the camera is placed only within the main tile avoiding looking at those edges directly. We render additional tiles 1.5 times larger than original ones, effectively increasing perceived dimensions of terrain 4 times. This short clip showcases how final extended terrain looks with and without skirt:
![Terrain with and without skirt
](https://dev-to-uploads.s3.amazonaws.com/i/6fqssrhdcu0sa56p19qa.gif)

As you can see, this simple trick creates a vast, seemingly endless terrain stretching up to horizon with very little effort and reuses existing geometries.

## Dust particles

For dust effect soft particles are used. You can read more about this technique in our previous article — https://dev.to/keaukraine/implementing-soft-particles-in-webgl-and-opengl-es-3l6e.
The only object rendered to a depth texture for soft particles is the main terrain tile because that’s the only geometry particles intersect with. To make this rendering faster, the simplest fragment shader is used to render this object instead of the complex one used to render the on-screen terrain.

## Dunes shader

To simulate the effect of wind creating sand waves on the dunes surface we’ve developed a quite complex shader. Let’s take a look inside of it. Please note that while we will explain GLSL code of shader, the generic techniques and approaches used in it can also be applied to recreate similar material in Unity/Unreal engines.
The code of the shader can be found in [DunesShader.ts](https://github.com/keaukraine/webgl-dunes/blob/master/src/shaders/DunesShader.ts). Let’s analyze it.

### Diffuse color and lightmaps

Terrain uses a quite large texture — 2048x2048 for web demo, and up to 4096x4096 in Android app. Obviously it takes quite some memory so to efficiently use it, some tricks were used. The main diffuse color for dunes is actually stored as a single-channel grayscale value in the red channel of terrain texture. Actual color of sand is specified by `uColor` uniform which is multiplied by grayscale diffuse value. The other 2 channels contain lightmaps for high sun (day and night) and low sun (sunrise and sunset). Since it is not possible to use uniforms for accessing texture data, two versions of shader are compiled for two lightmaps. Final diffuse color is multiplied with shadows color.

### Moving sand effect
Next, let’s take a look at how the moving wind effect is created. You may notice that it is different for windward and leeward slopes of dunes. To determine which effect to apply to which slope, we calculate blending coefficients from surface normal. These coefficients are calculated per vertex and are passed into the fragment shader via `vSlopeCoeff` and `vSlopeCoeff2` varyings. You can uncomment corresponding lines in fragment shader to visualize windward and leeward parts with different colors:
![Slopes visualized
](https://dev-to-uploads.s3.amazonaws.com/i/sirwl5qaar5hguobc1sy.jpg)

Both slopes use the same texture applied to them but windward one is more stretched. Texture coordinates for both slopes are also calculated in vertex shader to prevent dependent texture reads. Wind movement is done by adding offset to texture coordinates from `uTime` uniform.

### Fog
The next important thing to get a realistic result is to apply atmospheric fog. For performance reasons, we use a simple linear fog which is calculated in the vertex shader. Fog range is controlled by two uniforms — `fogStartDistance` and `fogDistance` and value to be used in the fragment shader is calculated and stored in `vFogAmount` varying. Fragment shader applies fog color from `uFogColor` uniform based on the value of this varying.
Fog color is adjusted for far terrain edges to blend with sky texture. And the sky texture is also edited to have a distant haze of the same fog color in places where it should blend with the terrain.

### Detail texture

Even though the overall terrain texture is quite large, it covers a large area and therefore still not detailed enough for close-ups. To make dunes less blurry and more realistic when observed from the ground we apply a detail texture to it. It is a small 256x256 texture which has 2 different sand ripples patterns in 2 channels for different slopes. Detail texture can either darken or lighten diffuse color. To achieve this, first we subtract 0.5 from the detail color so it can have negative value, and then this value is added to the final color. This way, 50% gray color in detail texture doesn’t affect diffuse color, darker values will darken it and brighter values will brighten color. Detail texture is applied the similar way as the fog — it has two uniforms to adjust cutoff distance where detail texture is not needed. You can uncomment a line in fragment shader to visualize detail texture range in red channel:
![Detail texture range](https://dev-to-uploads.s3.amazonaws.com/i/h5numt4jcyjcxkomomtx.jpg)

## Result
You can see a live demo page [here](https://keaukraine.github.io/webgl-dunes/index.html). It is interactive — you can click to change time of day. And on desktop to examine the scene from any arbitrary position you can go into free flight mode by pressing the Enter key. In this mode, to rotate the camera hold the right mouse button and to move use WASD keys, Space to go up and C to go down. Hold Shift while moving to accelerate.
Full source code is available on [GitHub](https://github.com/keaukraine/webgl-dunes/blob/master/src/shaders/DunesShader.ts), if you are interested in recreating similar effects you can clone and use it for your needs — it is licensed under permissive MIT license.