|
Jeshua Bratman @Ann Arbor |
machine learning, programming, tech, linux,etc. |

this.canvas = document.getElementById("mainCanvas");
gl = this.canvas.getContext("experimental-webgl");
Next, compile the vertex and fragment shaders. I'll leave out the details (as there are many good tutorials out there describing this process), but the important bits for our purpose are specifying the position, color, and texture for the particles.
this.shaderProg.a_position = gl.getAttribLocation(this.shaderProg, "a_position"); gl.enableVertexAttribArray(this.shaderProg.a_position); this.shaderProg.a_color = gl.getAttribLocation(this.shaderProg, "a_color"); gl.enableVertexAttribArray(this.shaderProg.a_color); this.shaderProg.s_texture = gl.getUniformLocation(this.shaderProg, "s_texture");These correspond to attributes in the vertex shader:
attribute vec3 a_position; attribute vec4 a_color;and the texture sampler uniform in the fragment shader:
uniform sampler2D s_texture;Next we need to create data arrays for the particle positions and colors. Make sure to use GL_DYNAMIC_DRAW when creating the buffer. This is a hint to opengl that we plan to update the buffer data frequently (see opengl docs).
this.particleLocs = new Float32Array(3 * this.numParticles); this.particleColors = new Float32Array(4 * this.numParticles); this.particleBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.particleBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.particleLocs, gl.DYNAMIC_DRAW); this.particleColorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.particleColorBuffer) gl.bufferData(gl.ARRAY_BUFFER, this.particleColors, gl.DYNAMIC_DRAW);Now for the particle texture. In this example, we'll use this simple circular gradient where the intensity will indicate particle transparency:

this.particleTexture = loadPointTexture(gl,"blob.png");
...
function loadPointTexture(gl,imgURL) {
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
var img = new Image();
img.src = imgURL;
img.onload = function() {
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,gl.UNSIGNED_BYTE, img);
};
return tex;
};
To understand these texture properties see opengl docs on the subject.
Finally, here's the fragment shader in full which deals with drawing the particles.
precision lowp float;
varying vec4 v_color;
uniform sampler2D s_texture;
void main(void)
{
vec4 col;
col = texture2D(s_texture, gl_PointCoord);
col.a = col.r;
if(col.a == 0.) discard;
vec4 tex = v_color*col;
gl_FragColor = tex;
}
We use gl_PointCoord as the texture coordinate. This will center the texture at the particle location. Next, we set the alpha value equal to the red channel -- this works in this case because our image was greyscale. Finally, and importantly, we discard any pixels with 0 alpha so that no matter the blending mode, these pixels won't show up. Finally, we multiply the texture color by the particle's color. Notice there are lot's of options here to tweak the looks.
gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE);Second, we should turn off the depth mask so the particles aren't added to the depth-buffer, otherwise they will try to occlude each other. Consequently we should also draw the particles after all other geometry, otherwise they will be occluded no matter the relative z-values.
gl.depthMask(false); //... draw particles ... gl.depthMask(true);In my example each particle has an associated position, velocity, and color. Particles are emitted at the mouse location with some random initial velocity and color. On each step, particle locations are updated based on the velocity, and there is a slight upward acceleration to give a fire effect. When particles exit the visible area, they are removed by changing their position far off screen and marking them as 'dead' so they can be re-used later. All that's required is required of the update process is that the positions and colors are updated in the Float32 arrays so we can directly send the updated data to the opengl buffers. So, assume on each step we have updated this.particleLocs and this.particleColors. In conjunction with DYNAMIC_DRAW, when we can update the particles using subData:
gl.bindBuffer(gl.ARRAY_BUFFER, this.particleBuffer); gl.bufferSubData(gl.ARRAY_BUFFER, this.particleBuffer,this.particleLocs); gl.vertexAttribPointer(this.shaderProg.a_position,3, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, this.particleColorBuffer); gl.bufferSubData(this.gl.ARRAY_BUFFER, this.particleColorBuffer,this.particleColors); gl.vertexAttribPointer(this.shaderProg.a_color, 4, gl.FLOAT, false, 0, 0);That's it! Take a look at the full source demo.js and demo.html to get a more complete picture.
Here’s a Javascript gluUnproject variant for Webgl. Given a point on the screen: winx, winy, winz, and a model-view-projection matrix mat, this function returns the corresponding 3d coordinate. As in gluUnproject, the winz value has the following semantics: winz = 0 corresponds to nearPoint and winz = 1 corresponds to farPoint in the projection. See Unproject Explained for a nice explanation. The viewport parameter should be set to [x,y,width,height] of the canvas DOM object, and winx/winy should be screen coordinates.
This function can be used to convert mouse click (winx,winy) and a depth (winz) to a 3d coordinate. Call the function twice with winz=0 and winz=1 to get endpoints of the ray on which the point (winx,winy) lies.
/* unproject - convert screen coordinate to WebGL Coordinates
* winx, winy - point on the screen
* winz - winz=0 corresponds to newPoint and winzFar corresponds to farPoint
* mat - model-view-projection matrix
* viewport - array describing the canvas [x,y,width,height]
*/
function unproject(winx,winy,winz,mat,viewport){
winx = 2 * (winx - viewport[0])/viewport[2] - 1;
winy = 2 * (winy - viewport[1])/viewport[3] - 1;
winz = 2 * winz - 1;
var invMat = mat4.create();
mat4.inverse(mat,invMat);
var n = [winx,winy,winz,1]
mat4.multiplyVec4(invMat,n,n);
return [n[0]/n[3],n[1]/n[3],n[2]/n[3]]
}
First find the matlab root directory. Lets call it $MROOT/. Inside $MROOT/extern/include is the header files for example engine.h. The object files are in $MROOT/bin/glnxa64 (at least for 64 bit machines). However figuring out all the dependencies yourself is difficult. Instead run $MROOT/bin/mex -n file.c which will generate the compilation and linking options for gcc.
Strangely, Javascript doesn’t seem to have a generator for normally distributed random numbers. Here’s an implementation of Marsaglia's polar method
Math.normrnd = function(mean,std) {
if(this.extra == undefined){
var u,v;var s = 0;
while(s >= 1 || s == 0){
u = Math.random()*2 - 1; v = Math.random()*2 - 1;
s = u*u + v*v;
}
var n = Math.sqrt(-2 * Math.log(s)/s);
this.extra = v*n;
return mean+u*n*std;;
} else{
var r = mean+this.extra*std;;
this.extra = undefined;
return r;
}
}