WebGL中消失的粒子

时间:2016-03-22 11:26:15

标签: javascript webgl shader

我遇到了这个WebGL演示:



(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;
		}
	}
}());
&#13;
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;
}
&#13;
<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>
&#13;
&#13;
&#13;

我对实施感兴趣并开始探索它。除了这个特殊时刻,我理解一切:导致粒子消失的原因是什么?

起初我认为在 redraw()方法中,在循环遍历所有顶点时,我们删除了最后一个:

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

......只有当我们按下鼠标时,我们才用另一个替换它。现在我觉得我错了,因为在这种情况下所有粒子都不会平滑消失。

这些粒子只有在失去速度时才会消失,所以我的第二个理论是,如果我们不按鼠标,它们会获得Z速度,但不是 - 我们不会改变它们完全是Z坐标。着色器也与粒子速度无关。

现在我真的很困惑这一切,有人可以给我任何提示吗?

1 个答案:

答案 0 :(得分:1)

粒子逐渐消失,因为它们由线条表示,线条的起点和终点之间的距离减小,默认情况下在webgl中打开抗锯齿。

该线的第一点是第二点之前的位置。使用相应的速度矢量移动第二点。当速度矢量为零时,该点消失,因为在该情况下该线的起点和终点处于相同位置。如果线条没有填满整个像素,则可能会显示不透明度降低。

但是,浏览器和图形卡都不需要支持反诽谤。用于反攻击的算法在不同的浏览器,驱动程序和图形卡之间也可能不同。这可能无法在某些计算机上按预期工作。