From our monthly sponsor: Ship customer-facing metrics fast with Keen.io. Collect, store, query, & display stunning analytics.
Today we are going to explore a playful animated landscape with a psychedelic look. The idea is to show how an experimentation on art and design with a generative process can lead to interesting interactive visuals which can be used in a variety of mediums like web, print, illustration, VJing, installations, games and many others. We made 3 variants of the landscape to show you how small changes in parameters can change a lot in visuals.
The cool thing about doing this with WebGL is that it’s widely supported and with GLSL shaders we can animate thousands, even millions of vertices at 60 FPS on the major desktop and mobile web browsers.
Let’s go through the main build up of the demo.
Breaking down the demo
1. Creating terrain with a plane
Let’s make a basic three.js scene, place a plane with a nice amount of vertices, rotate it 90 degrees is the x-axis, and lift the camera a little bit:
Create custom vertex and fragment shaders and bind them to a ShaderMaterial. The objective is to displace vertices up in the vertex shader with a perlin noise and multiply it with a height value:
// pseudo-code for noise implementation vec3 coord = vec3(uv, 1.0)*10.0; float noise = 1 + pnoise( vec3( coord.x, coord.y + time, coord.z )); float height = h * noise; // we apply height to z because the plane is rotated on x-axis vec4 pos = vec4( position.x, position.y, height, 1.0 ); // output the final position gl_Position = projectionMatrix * modelViewMatrix * pos;
2. Create a road with some math
Now we’ll use a little bit of math. We’ll implement the formula below, where x is the vertex x-coordinate, h is the maximum height of terrain, c is the center of road and w is the width of road:
Playing with those variables, we can get different results, as we can see in the graphs:
Now, applied in vertex-shader code, multiplied by the previously calculated noise it looks as follows:
// pseudo-code for formula implementation float height = h * pow( abs( cos( uv.x + c ) ), w ) * noise; // we apply height to z because the plane is rotated on x-axis vec4 pos = vec4( position.x, position.y, height, 1.0 ); // output the final position gl_Position = projectionMatrix * modelViewMatrix * pos;
To make a curved road, we use uv.y as angle and take the sin of it to oscillate the center along the y-axis (the plane is rotated on the x-axis, remember?).
3. Adding color layers
Let’s colorize the terrain with a nice trick. First, create a color pallete image like this one:
And then we’ll use it as a lookup texture in the fragment shader, getting the color value from the height calculated in the vertex shader as texture uv.y coordinate:
// pseudo-code for getting the color vec2 coord = vec2( 0.0, normalize( height ) ); vec4 color = texture2D( palleteTexture, coord ); gl_FragColor = color
4. Having fun adding interactivity
Now we’ve done the heaviest part, it’s easy to use mouse, touch or whatever input you want, to control the formula’s variables and get interesting forms of interactivity:
// JS pseudo-code in the render loop for uniforms manipulation with mouse terrain.material.uniforms.c.value = (mouseX / window.innerWidth - 0.5) * 0.1; terrain.material.uniforms.w.value = (mouseY / window.innerHeight - 0.5) * 4;
5. Final touches
Let’s adjust the camera position, add a nice color pallete, fog, a sky background, and we are done!
We hope you enjoy this walk-through and find the experiment inspirational!