Using WebGL & GLSL Shaders to Create a Tunnel Effect

Posted by on Aug 10, 2010 in 3D, WebGL12 comments

Now 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>


Tags: , , , , , ,


12 comments

» Comments RSS Feed
  1. Another great demo!

  2. Thanks Paul :-) and thanks for the remark about shader validation ;-)

  3. 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. :)

  4. @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 :-)

  5. [...] Android, Blackberry, Samsung Bada, Windows Phone 7, Palm WebOS, Symbian, MeeGo WebGL的隧道效果 – Blender to Actionscript的小工具就是他开发的。 JS1K competition [...]

  6. Hello. Tunnel effects are really cool stuff. Thanks for sharing from your knowledge. Thanks a lot mate:)

  7. [...] Today, I’m going to show you a simple mesh-based tunnel effect. The GeeXlab demo is based on this WebGL article. [...]

  8. [...] is very solid in its support for GPU-accelerated graphics. Check out these GLSL shader demos. And see ChemDoodle as an example of user [...]

  9. [...] Podemos ver como funciona esta geometría viendo la demo. Está hecho en WebGl y GLS shaders. Desde esta página también nos cuenta como lo ha hecho paso a paso y nos dá el código [...]

  10. Hello!

    Very nice, without GLSL, simply in opengl es 1.x can you show some source to make it works? /how to calculate vertex coords, and texture coords/ (without shaders)

    Thank,
    Leslie

  11. I was wondering this too. The shaders are very well commented and useful, but I’d love to learn more about procedurally generating shapes. I did follow all of the WebGL tutorials, but most of them only go over cubes and other hardcoded shapes. Is there any tutorials out there for actually generating shapes like blobs, water waves, etc?

  12. First of all, thanks for your job.
    I have wegGL functioning in my desktop, since your demonstration works properly. Nevertheless, I tried to reproduce your example locally without sucess. Although I have a copy of all the files needed in the same directory, the webGL windows is empty.
    Is there some details I forgot to follow? Could you give me some help?
    Thanks in advanced.

Leave a comment