|
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;
}
}
I've noticed many Java-base math libraries use netlib-java to do fast linear algebra, but getting the native libraries takes a bit of work. Here I'll show how to to compile these JNI libraries from source. The ultimate aim is to produce 5 files:
Many scientific applications require optimized matrix and linear algebra operations. There are a two standard suites of routines. First, BLAS (basic linear algebra subprograms) includes matrix and vector algebra routines. Second is LAPACK (linear algebra package) which includes more sophisticated linear algebra routines such such as solving linear systems of equations, eigenvalue decomposition, singular value decomposition, QR decomposition, etc. The Netlib Repository contains the fortran reference implementations for these libraries, but you really want to use optimized versions for your specific architecture. AMD and Intel both have proprietary implementations: ACML for AMD and IMKL for intel. Alternatively the open source project ATLAS (Automatically Tuned Linear Algebra Package) provides BLAS and LAPACK libraries that are automatically optimized for you system (if you compile it yourself). However the process of tuning and compiling ATLAS takes several hours (at least when I tried it a few years ago in Gentoo). If you don't need the absolutely best performance you can instead download pre-compiled ATLAS libraries for your architecture.
There are many interfaces to these libraries: Matlab and R use optimized libraries by default. In C++ you can use Boost uBLAS and link to the proper libraries. There are quite a few math packages for Java that include bindings to BLAS/LAPACK such as COLT, Parallel Colt, JAMA, EJML, JBLAS,MTJ, and lastly the specific library I use (actually it's for Scala) is Scalala which is heavily based on MTJ. Almost all these libraries leverage netlib-java to actually perform the operations (JBLAS and maybe some others provide their own interfaces). Netlib-java includes both pure-java implementations of BLAS and LAPACK, as well as the option to make JNI calls to the optimized libraries (e.g. ACML, IMKL, or ATLAS). However, binary JNI libraries are not usually included with these packages and as a result the linear algebra operations will be slow. I've found countless complaints on forums comparing these Java libraries to Matlab/R/Python/C etc. and finding it is an order of magnitude slower but this is only because they are not using the bindings to optimized libraries.
sudo apt-get install atlas-base-devFirst, checkout the netlib-java source:
svn checkout http://netlib-java.googlecode.com/svn/trunk/ netlib-javaNow we need to build the netlib jar and the jni libraries. First build the jar:
ant clean generate compile package cp lib/f2j/arpack-combo-*.jar lib/f2j/arpack-combined.jar cp netlib*.jar netlib-java-dev.jarNow, to build the JNI libraries, we need to first find the JNI header directory on your system (try "locate jni.h"). On my machine this is:
JNI_DIR=/usr/lib/jvm/java-6-openjdk/include/ #FIND CORRECT LOCATIONNext add this JNI directory to include directories in the makefile:
cd jni
sh configure
jni_replacement=$(printf "%s\n" "$JNI_DIR" | sed 's/[\&/]/\\&/g')
sed -e "s/CPPFLAGS=/CPPLAGS= -I${jni_replacement} -I. /g" Makefile.incl -i
sed -e "s/CFLAGS=/CFLAGS= -I${jni_replacement} -I. /g" Makefile.incl -i
That's it! Finally, compile it:
makeThis should produce libjniarpack-linux-*.so, libjniblas-linux-*.so, and libjnilapack-linux-*.so. Put these somewhere that will be found by $JAVA_LIBRARY_PATH and put the netlib jar somewhere in your $CLASSPATH. In eclipse, after including the netlib.jar in your path, you can include the .so files by going to Build Path -> Libraries -> Expand netlib.jar -> select Native library location -> click Edit.
import org.netlib.blas.*; ... System.out.println(BLAS.getInstance().getClass().getName());Hopefully this is printed
import org.netlib.blas.*;
import java.lang.reflect.*;
...
//get java blas object and make it accessible
Class javaBlasClass = Class.forName("org.netlib.blas.JBLAS");
Field javaBlas = javaBlasClass.getDeclaredField("INSTANCE");
Field jInstance = javaBlas.getClass().getDeclaredField("modifiers");
jInstance.setAccessible(true);
jInstance.setInt(javaBlas,javaBlas.getModifiers() & ~Modifier.FINAL);
javaBlas.setAccessible(true);
//get native blas object and make it accessible
Class nativeBlasClass = Class.forName("org.netlib.blas.NativeBLAS");
Field nativeBlas = nativeBlasClass.getDeclaredField("INSTANCE");
Field nInstance = nativeBlas.getClass().getDeclaredField("modifiers");
nInstance.setAccessible(true);
nInstance.setInt(nativeBlas,nativeBlas.getModifiers() & ~Modifier.FINAL);
nativeBlas.setAccessible(true);
//get blas current object and make it accessible
Field blasCurrent = Class.forName("org.netlib.blas.BLAS").getDeclaredField("current");
Field bInstance = blasCurrent.getClass().getDeclaredField("modifiers");
bInstance.setAccessible(true);
bInstance.setInt(blasCurrent, blasCurrent.getModifiers() & ~Modifier.FINAL);
blasCurrent.setAccessible(true);
//SET TO NativeBLAS
blasCurrent.set(null, nativeBlas.get(null));
//SET TO JBLAS
blasCurrent.set(null, javaBlas.get(null));
Here's the full code to do a simple speed comparison: NetlibTest.java