Using WebGL & GLSL Shaders to Create a Tunnel Effect
Posted by Dennis on Aug 10, 2010 in 3D, WebGL • 8 commentsNow here’s something that’s been done a thousand times before in a thousand different ways! Sometimes it’s good to re-invent the wheel so you can learn something new
My purpose for this demo was to experiment with vertex and fragment shaders. They’re very powerful tools that allow you to create the most amazing effects.
Click here to see the live demo. If you don’t have a WebGL-enabled browser you can click on the image below to see the YouTube video.
This Wikipedia page has a good explanation about shaders. Simply said, the vertex shader allows you to modify every vertex and the fragment shader (or pixel shader) allows you to play with the pixel colors. Here are the basic steps for this demo.
Create the geometry
I created a cylinder that consists of:
- vertices: the further away, the smaller the radius
- indices
- vertex colors: the vertices that are close to the camera are white, the vertices that are the furthest away are black. These colors are multiplied with the texture in a later stage to give the feeling of depth.
- texture coordinates
Modifying the geometry with a Vertex Shader
The vertex shader is executed every frame. The cylinder can be bended by using the sine and cosine functions. To animate this a time variable is passed on to the shader.
Creating the illusion of movement
To create the illusion of movement the texture coordinates are displaced in the vertex shader. The time variable is used for this as well. Only the V coordinate is displaced.
Creating the illusion of depth
When the geometry was created the color for each vertex was determined as well. The closest vertices are white and the ones further away change gradually to black. To create depth these colors are multiplied with the texture pixels in the fragment shader.
The GLSL code
Here’s what the code looks like. This time I only show the specific shader code for this demo. Everything else is explained in the brilliant Learning WebGL tutorials (the code for this demo is based on these).
The vertex shader:
<script id="shader-vs" type="x-shader/x-vertex">
// -- "attribute": read-only per-vertex data, available only within vertex shaders.
// -- the vertex position (x, y, z)
attribute vec3 aVertexPosition;
// -- the vertex color (r, g, b, a)
attribute vec4 aVertexColor;
// -- the texture coordinate for this vertex (u, v)
attribute vec2 aTextureCoord;
// -- "uniform": remains constant during each shader execution.
// -- model-view matrix
uniform mat4 uMVMatrix;
// -- projection matrix
uniform mat4 uPMatrix;
// -- the time value (changes every frame)
uniform float fTime;
// -- "varying": output of the vertex shader that corresponds to read-only interpolated input
// of the fragment shader
// -- the color
varying vec4 vColor;
// -- the texture coordinates
varying vec2 vTextureCoord;
void main(void) {
vec3 pos=aVertexPosition;
// -- displace the x coordinate based on the time and the z position
pos.x += cos(fTime + (aVertexPosition.z/4.0));
// -- displace the y coordinate based on the time and the z position
pos.y += sin(fTime + (aVertexPosition.z/4.0));
// -- transform the vertex
gl_Position = uPMatrix * uMVMatrix * vec4(pos, 1.0);
// -- copy the vertex color
vColor = aVertexColor;
// -- displace the texture's y (v) coordinate. This gives the illusion of movement.
vec2 texcoord=aTextureCoord;
texcoord.y = texcoord.y + (fTime);
// -- copy the texture coordinate
vTextureCoord = texcoord;
}
</script>
The fragment shader:
<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_ES
precision highp float;
#endif
uniform sampler2D uSampler;
varying vec4 vColor;
varying vec2 vTextureCoord;
void main(void) {
// -- get the pixel from the texture
vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
// -- multiply the texture pixel with the vertex color
gl_FragColor = vColor * textureColor;
}
</script>


Another great demo!
Thanks Paul
and thanks for the remark about shader validation
Hi. I didn’t know that it is so complicated to create a tunnel effect. Well, now i know. Thanks for making this blog post. Keep up the good work. Good Luck.
@web design manchester: it looks hard but shaders make things a lot easier actually. Especially now that they’re available through WebGL/JavaScript/the browser. There are some specifics that have to be learned first. Once you’ve learned these a whole world of possibilities opens up
[...] Android, Blackberry, Samsung Bada, Windows Phone 7, Palm WebOS, Symbian, MeeGo WebGL的隧道效果 – Blender to Actionscript的小工具就是他开发的。 JS1K competition [...]
Hello. Tunnel effects are really cool stuff. Thanks for sharing from your knowledge. Thanks a lot mate:)
[...] Today, I’m going to show you a simple mesh-based tunnel effect. The GeeXlab demo is based on this WebGL article. [...]
[...] is very solid in its support for GPU-accelerated graphics. Check out these GLSL shader demos. And see ChemDoodle as an example of user [...]