WebGLで大量のパーティクルを
WebGLでパーティクルを大量に使いたい時
javascriptで全部の点の座標を指定していたら重くて数を増やせないはず。そんなときに使える技。
頂点が沢山あるVBOを作って、点の位置を全部シェーダで決定させればいい
OpenGLのPointSpriteを使えたら良いけど見つからなかったから正三角形のビルボード(常に視点を向くように調整した平板なポリゴン)を使う事にする。
サンプル
WebGLの使えるブラウザでどうぞhttp://www.geocities.jp/flyinpng/particle20120417/sample.html
on/off切り替えてから回転させて見てみると分かりやすいかも
VertexShader
uniform mat4 modelViewMatrix; uniform mat4 projectionMatrix; uniform float size,time; attribute vec2 delta; attribute vec3 random0,random1,random2,random3; varying vec2 texcoord; varying vec3 color; void main(){ texcoord=delta; //3頂点で共通の乱数を使いパーティクルの位置を決定 vec3 pos=random0*time+random1*time*time+random2*time*time*time; //正三角形になるようdeltaだけ座標をずらす gl_Position=projectionMatrix*(modelViewMatrix*vec4(pos,1)+vec4(delta,0,0)*size); color=(vec3(1,1,1)+random3)/2.; }
FragmentShader
#ifdef GL_ES precision mediump float; #endif varying vec2 texcoord; varying vec3 color; void main(void){ float r2=dot(texcoord,texcoord); if(r2>1.)discard;//正三角形の内接円外部は描画しない gl_FragColor=vec4(color*(1.-r2)*(1.-r2),1); }
VBO初期化
var numParticle=1000; var arrayBufferDelta,arrayBufferRandom=[]; function initArrayBuffers(){ var deltaArr=[],sqrt3=Math.sqrt(3); //正三角形(ビルボードの形)の頂点座標 for(var i=0;i<numParticle;i++){deltaArr.push(-1.7320508,-1);deltaArr.push(1.7320508,-1);deltaArr.push(0,2);} gl.bindBuffer(gl.ARRAY_BUFFER,arrayBufferDelta=gl.createBuffer()); gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(deltaArr),gl.STATIC_DRAW); for(var i=0;i<5;i++){ var rndArr=[]; for(var j=0;j<numParticle;j++){ var s=2*Math.random()-1,t=2*Math.PI*Math.random(),r=Math.sqrt(1-s*s); var x=r*Math.cos(t),y=r*Math.sin(t),z=s; //3頂点で共通の乱数値 これを使ってパーティクルの位置をVertexShaderで決定する rndArr.push(x,y,z);rndArr.push(x,y,z);rndArr.push(x,y,z); } gl.bindBuffer(gl.ARRAY_BUFFER,arrayBufferRandom[i]=gl.createBuffer()); gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(rndArr),gl.STATIC_DRAW); } }
描画
function render(size,time){ gl.clear(gl.COLOR_BUFFER_BIT); gl.enable(gl.BLEND); gl.disable(gl.DEPTH_TEST); gl.blendFunc(gl.ONE, gl.ONE); gl.uniformMatrix4fv(gl.getUniformLocation(gl.program,"modelViewMatrix"),modelViewMatrix); gl.uniformMatrix4fv(gl.getUniformLocation(gl.program,"projectionMatrix"),projectionMatrix); gl.uniform1f(gl.getUniformLocation(gl.program,"size"),size); gl.uniform1f(gl.getUniformLocation(gl.program,"time"),time); gl.enableVertexAttribArray(0); gl.bindBuffer(gl.ARRAY_BUFFER,arrayBufferDelta); gl.vertexAttribPointer(0,2,gl.FLOAT,false,0,0); for(var i=0;i<4;i++){ gl.enableVertexAttribArray(i+1); gl.bindBuffer(gl.ARRAY_BUFFER,arrayBufferRandom[i]); gl.vertexAttribPointer(i+1,3,gl.FLOAT,false,0,0); } gl.drawArrays(gl.TRIANGLES,0,3*numParticle); }
javascript側では描画時に時間を指定しているだけで重い処理は無い。
つまりGPUが十分速ければパーティクルが100万個あっても動く!!
2段階で動きを変化させた花火っぽいサンプル
http://www.geocities.jp/flyinpng/particle20120417/fireworks.html#N=100000
うちのPCだと8万粒くらいが60fps出せる限界
線で軌跡を描画するサンプル
http://www.geocities.jp/flyinpng/particle20120417/sample2.html#N=10000