I needed to generate a heightmap to feed to the terrain mesh in my procedural generation project. I started with a modified version of libnoise.js, a javascript port of libnoise, using just the Perlin noise module* generate my heightmap on the CPU. This was not a fast solution, taking approximately 2 seconds to generate a 512x512 heightmap directly in a 1D javascript array. Of course this is to be expected; javascript is single threaded, and is a rather slow (relatively speaking). The heightmap generation procedure is embarrassingly parallelizable, so I initially focused on replacing the single-threaded solution.
Enter Web Workers, a new javascript feature that allows concurrent execution of multiple javascript threads in the background. I changed the generation procedure to use multiple threads by splitting the workload into background threads. Each thread worked on its portion of the problem in isolation, and serialized/posted the result back to the main execution thread which joined results to create the final heightmap. Running this solution with three threads and a different algorithm reduced the generation time to under 400ms (excluding workup startup/cleanup time). Much better than the original, but I decided it would be useful to have the option to generate a new texture every frame and the new solution was still too slow for that (60fps ~ 15ms per frame).
The final answer lay right back where I started. My goal was to push the heightmap to the GPU for the terrain mesh to use, so why not use the GPU to generate the heightmap? I found Ashima Arts’ set of fast GLSL noise generators. I plugged his simplex noise GLSL code into a fragment shader, applied it to a fractal sum and put the resulting float into a vec4 for gl_FragColor to output, which gave a heightmap similar to that generated by the original javascript-CPU method. This was all done in a framebuffer object (conceptually a virtual screen), which was used as a texture for the terrain mesh. A 1x1 plane is setup to fill the entire framebuffer, and its rendered fragment coordinates are fed to the noise generator. This gives a texture with dimensions equal to that of the framebuffer object view port, in which every point has been passed through a noise generator to generate a black-and-white heightmap. The entire heightmap generation is done in parallel on the GPU very quickly. How fast? 0.75 milliseconds* for the same 512x512 heightmap that took 2000 milliseconds on the CPU in javascript.

Here’s the fragment shader. Note that I used the three dimensional version of the simplex noise generator, passing in the octave as the third dimension gave a nicer noise texture than using the two-dimensional version.
// *** Ashima Art's 3d simplex noise code would go here, omitted for brevity ***
// *** the 2D version with just position as parameters doesn't look as nice ***
void main(void) {
vec2 npos = gl_FragCoord.xy / scale;
const float persist = 0.5;
float persistence = 1.0;
float h = 0.0;
// This loop doesn't run for me in Chrome without --use-gl=desktop
// With that flag, it takes around 5 refreshes before it actually renders properly
// Also changing 11 to any number between 1 and 10 stops the entire loop from running again
// My real code has this loop manually unrolled at 9 iterations because that's too many WTFs for me.
for (int i = 0; i < 11; i++) {
h += snoise(vec3(npos,512.0*float(i + 1))) * persistence;
persistence *= persist;
npos *= 2.0;
}
h = clamp((1.1 + h) / 2.2, 0.0, 1.0); // adjust h to be in the 0.0 to 1.0 range instead of -1.0 to 1.0
gl_FragColor = vec4(h, h, h, h);
}
And a snippet of the Three.js code (not functional alone)
var terrain_size = 512;
cameraFB = new THREE.OrthographicCamera(-1 * terrain_size / 2,
terrain_size / 2,
terrain_size / 2,
-1 * terrain_size / 2,
-10000, 10000);
cameraFB.position.z = 100;
sceneFB = new THREE.Scene();
FBTexture = new THREE.WebGLRenderTarget(terrain_size, terrain_size, {
minFilter: THREE.LinearFilter,
magFilter: THREE.NearestFilter,
format: THREE.RGBFormat
});
var quad = new THREE.Mesh(new THREE.PlaneGeometry(terrain_size, terrain_size, 1, 1),
new THREE.ShaderMaterial({
uniforms: {
scale: {
type: "f",
value: 256.0
}
},
vertexShader: shaders['terrainGenerator'].vertex, // the next shader
fragmentShader: shaders['terrainGenerator'].fragment // the above shader
})
);
quad.position.z = -100;
sceneFB.add(quad);
renderer.render(sceneFB, cameraFB, FBTexture, true);
// FBTexture can now be provided as a texture uniform to another shader
simple vertex shader for the above
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
* If you read Perlin’s presentation, the module isn’t actually Perlin noise. Perlin’s noise algorithm is called gradientCoherentNoise3D in the library, while the “Perlin” module is a fractal sum of Perlin’s noise function. * Estimate based on the time it took to generate 10000 different heightmaps.