WhilseySoon
WhilseySoon

Reputation: 322

Disappearing particles in WebGL

I came across this WebGL demo:

(function(){
	/**
	 * Most of the WebGL-related code in this demo 
	 * comes from this tutorial by Dennis Ippel (thanks!) :
	 * http://www.rozengain.com/blog/2010/02/22/beginning-webgl-step-by-step-tutorial/
	 * 
	 */


	var offset = 0,
		deadTimeOut = 1000,
		i, n,
		connectDiv,
		canvas, gl,
		ratio,
		vertices,
		velocities,
		colorLoc,
		cw, 
		ch, 
		cr = 0, cg = 0, cb = 0,
		tr, tg, tb,
		rndX = 0,
		rndY = 0,
		rndOn = false,
		rndSX = 0,
		rndSY = 0,
		lastUpdate = 0,
		IDLE_DELAY = 6000,
		touches = [],
		totalLines = 60000,
		renderMode = 0,
		numLines = totalLines;

	// setup webGL
	loadScene();

	// add listeners
	window.addEventListener( "resize", onResize, false )
	document.addEventListener( "mousedown", onMouseDown, false );
	document.addEventListener( "keydown", onKey, false );
	onResize();

	// start animation
	animate();

	function onResize(e) {
		cw = window.innerWidth; 
		ch = window.innerHeight;
	}  

	function normalize(px, py){
		touches[0] = (px/cw-.5)*3;
		touches[1] = (py/ch-.5)*-2;
	}

	function onMouseDown(e){
		normalize(e.pageX,e.pageY);
		document.addEventListener( "mousemove", onMouseMove );
		document.addEventListener( "mouseup", onMouseUp );
		e.preventDefault();
	}

	function onMouseMove(e) {
		normalize(e.pageX,e.pageY);
	}

	function onMouseUp(e) {
		touches.length = 0;
		document.removeEventListener( "mousemove", onMouseMove );
		document.removeEventListener( "mouseup", onMouseUp );
	}

	function animate() {
		requestAnimationFrame( animate );
		redraw();
	}


	function redraw()
	{

		// declarations
		var player, dx, dy, d,
				tx, ty, bp, p, 
				i = 0, nt, j,
				now = new Date().getTime();
		
		nt = touches.length;
		
		// animate color
		cr = cr * .99 + tr * .01;
		cg = cg * .99 + tg * .01;
		cb = cb * .99 + tb * .01;
		gl.uniform4f( colorLoc, cr, cg, cb, .5 );
		
		// animate and attract particles
		for( i = 0; i < numLines; i+=2 )
		{
			bp = i*3;
			// copy old positions
			vertices[bp] = vertices[bp+3];
			vertices[bp+1] = vertices[bp+4];
			
			// inertia
			velocities[bp] *= velocities[bp+2];
			velocities[bp+1] *= velocities[bp+2];
			
			// horizontal
			p = vertices[bp+3];
			p += velocities[bp];
			if ( p < -ratio ) {
				p = -ratio;
				velocities[bp] = Math.abs(velocities[bp]);
			} else if ( p > ratio ) {
				p = ratio;
				velocities[bp] = -Math.abs(velocities[bp]);
			}
			vertices[bp+3] = p;
			
			// vertical
			p = vertices[bp+4];
			p += velocities[bp+1];
			if ( p < -1 ) {
				p = -1;
				velocities[bp+1] = Math.abs(velocities[bp+1]);
			} else if ( p > 1 ) {
				p = 1;
				velocities[bp+1] = -Math.abs(velocities[bp+1]);
				
			}
			vertices[bp+4] = p;
			
			if ( nt ) // attraction when touched
			{
				for( j=0; j<nt; j+=2 )
				{
					dx = touches[j] - vertices[bp];
					dy = touches[j+1] - vertices[bp+1];
					d = Math.sqrt(dx * dx + dy * dy);
					
					if ( d < 2 )
					{
						if ( d < .03 )
						{
							//vertices[bp] = vertices[bp+3] = (Math.random() * 2 - 1)*ratio;
							//vertices[bp+1] = vertices[bp+4] = Math.random() * 2 - 1;
							vertices[bp] = (Math.random() * 2 - 1)*ratio;
							vertices[bp+1] = Math.random() * 2 - 1;
							vertices[bp+3] = (vertices[bp+3] + vertices[bp]) * .5;
							vertices[bp+4] = (vertices[bp+4] + vertices[bp+1]) * .5;
							velocities[bp] = Math.random()*.4-.2;
							velocities[bp+1] = Math.random()*.4-.2;
						} else {
							dx /= d;
							dy /= d;
							d = ( 2 - d ) / 2;
							d *= d;
							velocities[bp] += dx * d * .01;
							velocities[bp+1] += dy * d * .01;
						}
					}
				}
			}
		}

		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
		gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
			
		switch( renderMode ) {
			case 0 :
				gl.lineWidth(1);
				gl.drawArrays( gl.LINES, 0, numLines );
				break;
				
			case 1:
				gl.drawArrays( gl.TRIANGLE_STRIP, 0, numLines ); 
				break;
				
			case 2 :
				gl.lineWidth(1);
				gl.drawArrays( gl.LINE_STRIP, 0, numLines );
				break;
				
			case 3:
				gl.drawArrays( gl.TRIANGLE_FAN, 0, numLines ); 
				break;
		}
		
		gl.flush();
	}

	var colorTimeout;

	function switchColor() {
		var a = .5,
			c1 = .3+Math.random()*.2,
			c2 = Math.random()*.06+0.01,
			c3 = Math.random()*.06+0.02;
			
		switch( Math.floor( Math.random() * 3 ) ) {
			case 0 :
				//gl.uniform4f( colorLoc, c1, c2, c3, a );
				tr = c1;
				tg = c2;
				tb = c3;
				break;
			case 1 :
				//gl.uniform4f( colorLoc, c2, c1, c3, a );
				tr = c2;
				tg = c1;
				tb = c3;
				break;
			case 2 :
				//gl.uniform4f( colorLoc, c3, c2, c1, a );
				tr = c3;
				tg = c2;
				tb = c1;
				break;
		}

		if ( colorTimeout ) clearTimeout( colorTimeout );
		colorTimeout = setTimeout( switchColor, 500 + Math.random() * 4000 );
	}

	function loadScene()
	{
		connectDiv = document.getElementById("connectImg");

		//    Get the canvas element
		canvas = document.getElementById("webGLCanvas");
		//    Get the WebGL context
		gl = canvas.getContext("experimental-webgl");
		//    Check whether the WebGL context is available or not
		//    if it's not available exit
		if(!gl)
		{
			alert("There's no WebGL context available.");
			return;
		}
		//    Set the viewport to the canvas width and height
		cw = window.innerWidth;
		ch = window.innerHeight;
		canvas.width = cw;
		canvas.height = ch;
		gl.viewport(0, 0, canvas.width, canvas.height);
		
		//    Load the vertex shader that's defined in a separate script
		//    block at the top of this page.
		//    More info about shaders: http://en.wikipedia.org/wiki/Shader_Model
		//    More info about GLSL: http://en.wikipedia.org/wiki/GLSL
		//    More info about vertex shaders: http://en.wikipedia.org/wiki/Vertex_shader
		
		//    Grab the script element
		var vertexShaderScript = document.getElementById("shader-vs");
		var vertexShader = gl.createShader(gl.VERTEX_SHADER);
		gl.shaderSource(vertexShader, vertexShaderScript.text);
		gl.compileShader(vertexShader);
		if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
			alert("Couldn't compile the vertex shader");
			gl.deleteShader(vertexShader);
			return;
		}
		
		//    Load the fragment shader that's defined in a separate script
		//    More info about fragment shaders: http://en.wikipedia.org/wiki/Fragment_shader
		//var fragmentShaderScript = document.getElementById("shader-fs");
		var fragmentShaderScript = document.getElementById("shader-fs");
		
		var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
		gl.shaderSource(fragmentShader, fragmentShaderScript.text);
		gl.compileShader(fragmentShader);
		if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
			alert("Couldn't compile the fragment shader");
			gl.deleteShader(fragmentShader);
			return;
		}

		//    Create a shader program. 
		gl.program = gl.createProgram();
		gl.attachShader(gl.program, vertexShader);
		gl.attachShader(gl.program, fragmentShader);
		gl.linkProgram(gl.program);
		if (!gl.getProgramParameter(gl.program, gl.LINK_STATUS)) {
			alert("Unable to initialise shaders");
			gl.deleteProgram(gl.program);
			gl.deleteProgram(vertexShader);
			gl.deleteProgram(fragmentShader);
			return;
		}
		//    Install the program as part of the current rendering state
		gl.useProgram(gl.program);
		
		
		// get the color uniform location
		colorLoc = gl.getUniformLocation( gl.program, "color" );
		gl.uniform4f( colorLoc, 0.4, 0.01, 0.08, 0.5 );
		
		
		//    Get the vertexPosition attribute from the linked shader program
		var vertexPosition = gl.getAttribLocation(gl.program, "vertexPosition");
		//    Enable the vertexPosition vertex attribute array. If enabled, the array
		//    will be accessed an used for rendering when calls are made to commands like
		//    gl.drawArrays, gl.drawElements, etc.
		gl.enableVertexAttribArray(vertexPosition);
		
		//    Clear the color buffer (r, g, b, a) with the specified color
		gl.clearColor(0.0, 0.0, 0.0, 1.0);
		//    Clear the depth buffer. The value specified is clamped to the range [0,1].
		//    More info about depth buffers: http://en.wikipedia.org/wiki/Depth_buffer
		gl.clearDepth(1.0);
		//    Enable depth testing. This is a technique used for hidden surface removal.
		//    It assigns a value (z) to each pixel that represents the distance from this
		//    pixel to the viewer. When another pixel is drawn at the same location the z
		//    values are compared in order to determine which pixel should be drawn.
		//gl.enable(gl.DEPTH_TEST);
		gl.enable(gl.BLEND);
		gl.disable(gl.DEPTH_TEST);
		gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
		//    Specify which function to use for depth buffer comparisons. It compares the
		//    value of the incoming pixel against the one stored in the depth buffer.
		//    Possible values are (from the OpenGL documentation):
		//    GL_NEVER - Never passes.
		//    GL_LESS - Passes if the incoming depth value is less than the stored depth value.
		//    GL_EQUAL - Passes if the incoming depth value is equal to the stored depth value.
		//    GL_LEQUAL - Passes if the incoming depth value is less than or equal to the stored depth value.
		//    GL_GREATER - Passes if the incoming depth value is greater than the stored depth value.
		//    GL_NOTEQUAL - Passes if the incoming depth value is not equal to the stored depth value.
		//    GL_GEQUAL - Passes if the incoming depth value is greater than or equal to the stored depth value.
		//    GL_ALWAYS - Always passes.                        
		//gl.depthFunc(gl.LEQUAL);
		
		//    Now create a shape.
		//    First create a vertex buffer in which we can store our data.
		var vertexBuffer = gl.createBuffer();
		//    Bind the buffer object to the ARRAY_BUFFER target.
		gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);


		//    
		vertices = [];
		ratio = cw / ch;
		velocities = [];
		for ( var i=0; i<totalLines; i++ )
		{
			vertices.push( 0, 0, 1.83 );
			velocities.push( (Math.random() * 2 - 1)*.05, (Math.random() * 2 - 1)*.05, .93 + Math.random()*.02 );
		}
		vertices = new Float32Array( vertices );
		velocities = new Float32Array( velocities );

		//    Creates a new data store for the vertices array which is bound to the ARRAY_BUFFER.
		//    The third paramater indicates the usage pattern of the data store. Possible values are
		//    (from the OpenGL documentation):
		//    The frequency of access may be one of these:       
		//    STREAM - The data store contents will be modified once and used at most a few times.
		//    STATIC - The data store contents will be modified once and used many times.
		//    DYNAMIC - The data store contents will be modified repeatedly and used many times.
		//    The nature of access may be one of these:
		//    DRAW - The data store contents are modified by the application, and used as the source for 
		//           GL drawing and image specification commands.
		//    READ - The data store contents are modified by reading data from the GL, and used to return 
		//           that data when queried by the application.
		//    COPY - The data store contents are modified by reading data from the GL, and used as the source 
		//           for GL drawing and image specification commands.                        
		gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
		
		//    Clear the color buffer and the depth buffer
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
		
		//    Define the viewing frustum parameters
		//    More info: http://en.wikipedia.org/wiki/Viewing_frustum
		//    More info: http://knol.google.com/k/view-frustum
		var fieldOfView = 30.0;
		var aspectRatio = canvas.width / canvas.height;
		var nearPlane = 1.0;
		var farPlane = 10000.0;
		var top = nearPlane * Math.tan(fieldOfView * Math.PI / 360.0);
		var bottom = -top;
		var right = top * aspectRatio;
		var left = -right;

		//     Create the perspective matrix. The OpenGL function that's normally used for this,
		//     glFrustum() is not included in the WebGL API. That's why we have to do it manually here.
		//     More info: http://www.cs.utk.edu/~vose/c-stuff/opengl/glFrustum.html
		var a = (right + left) / (right - left);
		var b = (top + bottom) / (top - bottom);
		var c = (farPlane + nearPlane) / (farPlane - nearPlane);
		var d = (2 * farPlane * nearPlane) / (farPlane - nearPlane);
		var x = (2 * nearPlane) / (right - left);
		var y = (2 * nearPlane) / (top - bottom);
		var perspectiveMatrix = [
			x, 0, a, 0,
			0, y, b, 0,
			0, 0, c, d,
			0, 0, -1, 0
		];
		
		//     Create the modelview matrix
		//     More info about the modelview matrix: http://3dengine.org/Modelview_matrix
		//     More info about the identity matrix: http://en.wikipedia.org/wiki/Identity_matrix
		var modelViewMatrix = [
			1, 0, 0, 0,
			0, 1, 0, 0,
			0, 0, 1, 0,
			0, 0, 0, 1
		];
		//     Get the vertex position attribute location from the shader program
		var vertexPosAttribLocation = gl.getAttribLocation(gl.program, "vertexPosition");
	//				colorLoc = gl.getVaryingLocation(gl.program, "vColor");
	//				alert("color loc : " + colorLoc );
		//     Specify the location and format of the vertex position attribute
		gl.vertexAttribPointer(vertexPosAttribLocation, 3.0, gl.FLOAT, false, 0, 0);
		//gl.vertexAttribPointer(colorLoc, 4.0, gl.FLOAT, false, 0, 0);
		//     Get the location of the "modelViewMatrix" uniform variable from the 
		//     shader program
		var uModelViewMatrix = gl.getUniformLocation(gl.program, "modelViewMatrix");
		//     Get the location of the "perspectiveMatrix" uniform variable from the 
		//     shader program
		var uPerspectiveMatrix = gl.getUniformLocation(gl.program, "perspectiveMatrix");
		//     Set the values
		gl.uniformMatrix4fv(uModelViewMatrix, false, new Float32Array(perspectiveMatrix));
		gl.uniformMatrix4fv(uPerspectiveMatrix, false, new Float32Array(modelViewMatrix));
	//	gl.varyingVector4fv( 
		//     Draw the triangles in the vertex buffer. The first parameter specifies what
		//     drawing mode to use. This can be GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, 
		//     GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_QUAD_STRIP, 
		//     GL_QUADS, and GL_POLYGON
		
		switchColor();
	}

	function onKey( e ) {
		setRenderMode( ++renderMode % 4 );
	}

	function setRenderMode( n ) {
		renderMode = n;
		switch(renderMode) {
			case 0: // lines
				numLines = totalLines;
				break;
			case 1: // triangle strip
				numLines = 600;
				break;
			case 2: // lines strip
				numLines = 7000;
				break;
			case 3: // quad strip
				numLines = 400;
				break;
		}
	}
}());
html, body { height: 100%; }
body { 
	margin: 0px; 
	padding: 0px; 
	overflow: hidden;
	background: #000;
}
canvas { 
	z-index: 2;
	width: 100%; 
	height: 100%; 
	position: absolute;
	-webkit-transform: translateZ(0); 
	cursor: crosshair;/*NW-resize;*/
}  

#connectImg {
	position: absolute;
	top: 0px;
	left: 0px;
	right: 0px;
	height: 44px;
	background: url("../img/join.png") no-repeat center;
	-webkit-transition: all .5s ease-in-out;
	z-index:  5;
}

#connectImg.closed {
	top: -45px;
}


#connect {
	position: absolute;
	top: 0px;
	left: 0px;
	right: 0px;
	padding: 10px;
	background: rgba(0,0,0,0.5);
	color: #FFF;
	text-align: center;
	font-family: "DaxCompact-Medium";
	font-size: 16px;
	-webkit-transition: all .5s ease-out;
	z-index:  5;
}

#connect img {
	vertical-align: middle;
	margin-right: 20px;
}

#connect.closed {
	top: -60px !important; 
}

.wifi, .url {
	font-weight: bold;
	background: #666;
	display: inline-block;
	padding: 3px 8px;
	-webkit-border-radius: 10px;
	-moz-border-radius: 10px;
	border-radius: 10px;
	
	background: #989898;
	background: -moz-linear-gradient(top, #989898 0%, #757575 36%, #282828 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#989898), color-stop(36%,#757575), color-stop(100%,#282828));
	background: -webkit-linear-gradient(top, #989898 0%,#757575 36%,#282828 100%);
	background: -o-linear-gradient(top, #989898 0%,#757575 36%,#282828 100%);
	background: -ms-linear-gradient(top, #989898 0%,#757575 36%,#282828 100%);
	filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#989898', endColorstr='#282828',GradientType=0 );
	background: linear-gradient(top, #989898 0%,#757575 36%,#282828 100%);
}

#instructions {
	position: absolute;
	left: 0px;
	right: 0px;
	bottom: 0px;
	padding: 5px;
	color: white;
	text-align: center;
	font-family: Helvetica;
	font-size: 14px;
	z-index:  5;
}
<title>FLUID - WebGL demo by boblemarin (https://github.com/boblemarin/FLU)</title> 
<script id="shader-fs" type="x-shader/x-fragment">
				#ifdef GL_ES
				  precision highp float;
				  #endif

				 uniform vec4 color;

    		void main(void) {
    		 gl_FragColor = color;
    		}
</script>
<script id="shader-vs" type="x-shader/x-vertex">
  			attribute vec3 vertexPosition;

			uniform mat4 modelViewMatrix;
			uniform mat4 perspectiveMatrix;

 			void main(void) {
				gl_Position = perspectiveMatrix * modelViewMatrix * vec4(vertexPosition, 1.0);
			}	
</script>
<canvas id="webGLCanvas"></canvas>
<div id="instructions">Click and drag to animate particles, press any key to switch between rendering modes</div>

I got interested by implementation and started explroing it. I understand everything except this particular moment: what causes particles to disapear?

At first I thought that in redraw() method, while cycling through all vertices, we delete the last one:

for( i = 0; i < numLines; i+=2 ){
            bp = i*3;
            // copy old positions
            vertices[bp] = vertices[bp+3];
            vertices[bp+1] = vertices[bp+4];
            ...
}

...and we replace it with another only if we are pressing a mouse. Now it seems to me that I was wrong, because all particles would not fade away smoothly in that case.

These particles fade away only if they're losing speed, so my second theory was that they gain Z-speed if we aren't pressing the mouse, but no - we don't change their Z-coordinates at all. Shaders are not linked to particles speed either.

Now I'm really confused with all this, could someone give me any hints?

Upvotes: 0

Views: 609

Answers (1)

Daniel Abrecht
Daniel Abrecht

Reputation: 163

The particles fade away because they are represented by lines, the distance between the start and the end of the line is reduced and antialiasing is turned on in webgl by default.

The first point of the line is the position which the second point had before. The second point is displaced using the corresponding velocity vector. When the velocity vector is zero, the point disappears, because the start and the end of the line are at the same position in that case. If the line doesn't fill a complete pixel, it may be displayed with reduced opacity.

However, neither the browser nor the graphic card are required to support antialaising. The algorithm which is used for antialaising could differ between different browsers, drivers and graphic cards too. This may not work as intended on some computers.

Upvotes: 1

Related Questions