前段時間領導要我作一個流體粒子的demo,正巧我也最近在看一些流體、煙霧的文章,接到任務興奮勁兒一下就上來了,由於沒有實踐過,並且效果看着很不錯,因而乎我腦海裏就在想整個流程,遇到其中一個硬性問題就是流體粒子每個都有本身的速度和座標,那我用js去運算且更新這些數據豈不是很耗性能,就在上次出差參加一個峯會中,(頭腦出差)一直在想怎樣解決,忽然想到能夠利用GPU生成圖片數據,而後解析數據傳遞給相應粒子上不就得了,作出來後性能沒得說,我這個激動啊~。。html
demo地址(線上我只上傳了6w的粒子)web
接下來的兩張性能對比圖是基於cpu 6倍降速所截的圖片,第一張圖流體粒子採用js運算粒子位置(用js運算每一個位置的話,粒子總數應該很少,不然會卡頓)
,性能分析時能夠看到每一幀得黃色(js運算)區域佔比很大,幀率不齊,第二張圖是我採用得方式,因爲運算轉移到GPU中,每一幀得黃色區域佔比很小,100w粒子的狀況下依然幀率很齊。typescript
在每一次繪製數據圖片時,b(速度)區域是結合兩個矢量場(c&d)以及他自身的速度運算生成的,a(位置)區域只須要自己座標結合速度生成新座標,c是根據時間進行的噪聲圖片,d則須要根據傳入的touch座標與速度進行計算。app
生成完數據圖片以後,纔是開始進行更新粒子位置的環節,其思路是根據傳入的粒子索引查找數據圖片中相應a區域的rgb值,這個rgb值就是當前索引粒子的位置,有了位置以後,接下來就是常規繪製點的步驟。dom
梳理到這兒,從新回看整個流程,js邏輯幾乎不多,只有建立粒子索引,每一幀傳入c區域所須要的時間,以及滑動時須要傳入touch的位置與速度向量,其餘的所有在GPU中完成,因此在性能測試中幾乎看不到黃色(js)區域,而且性能穩定高效。函數
js部分性能
const frameBuffer = this.gl.createFramebuffer();
const texture = this.gl.createTexture();
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,frameBuffer);
this.gl.activeTexture(this.gl.TEXTURE0);
this.gl.bindTexture(this.gl.TEXTURE_2D,texture);
this.gl.texImage2D(this.gl.TEXTURE_2D,0,this.gl.RGB,size,size,0,this.gl.RGB,this.halfFloat.HALF_FLOAT_OES,null);
this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MAG_FILTER,this.gl.NEAREST);
this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_MIN_FILTER,this.gl.NEAREST);
this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_S,this.gl.CLAMP_TO_EDGE);
this.gl.texParameteri(this.gl.TEXTURE_2D,this.gl.TEXTURE_WRAP_T,this.gl.CLAMP_TO_EDGE);
this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER,this.gl.COLOR_ATTACHMENT0,this.gl.TEXTURE_2D,texture,0);
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,null);
複製代碼
this.gl.useProgram(this.vectorData.program);
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,frameBuffer);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
...
this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4);
this.gl.bindFramebuffer(this.gl.FRAMEBUFFER,null);
複製代碼
this.gl.useProgram(this.particleData.program);
this.gl.activeTexture(this.gl.TEXTURE0);
//傳入數據圖片
this.gl.uniform1i(this.gl.getUniformLocation(this.particleData.program,'uTexture'),0);
this.gl.bindTexture(this.gl.TEXTURE_2D,this.vectorTexture);
//傳入粒子索引數據
this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.particleData.buffer);
this.gl.vertexAttribPointer(this.particleData.aPosition,2,this.gl.FLOAT,false,0,0);
this.gl.drawArrays(this.gl.POINTS,0,this.particleLength);
複製代碼
shader部分 數據圖片主要使用到了片元着色器去設置每一個像素的顏色測試
#ifdef GL_ES
precision highp float;
#endif
#define PI 3.14159265358979323846
#define OCTAVE_NUM 5
#define SIZE 128.
uniform sampler2D uTexture;//上次的數據圖片
uniform float uTime;//時間
uniform vec4 uPointer;//手指座標與速度向量
varying vec2 vPosition;//從頂點着色器傳入的位置
const float pixelSize = 1./SIZE;
vec2 random (in vec2 p) {
//僞隨機函數
return fract(
sin(
vec2(
dot(p, vec2(3.3,6.1)),
dot(p, vec2(5.7,4.7))
)
) * .5
);
}
float noise(in vec2 p){
//噪聲函數
vec2 i = floor(p);
vec2 f = fract(p);
float a = dot(random(i),f);
float b = dot(random(i + vec2(1., 0.)),f - vec2(1., 0.));
float c = dot(random(i + vec2(0., 1.)),f - vec2(0., 1.));
float d = dot(random(i + vec2(1., 1.)),f - vec2(1., 1.));
vec2 u = smoothstep(0.,1.,f);
return mix(mix(a,b,u.x),mix(c,d,u.x),u.y)+.5;
}
vec4 randomRate(in vec3 pos){
//計算c區域 vector field 速度
vec3 _pos = pos*vec3(.5,.5,1);
vec3 pixelPos = _pos*SIZE;
_pos.y += .5;
vec3 i = floor(pixelPos);
vec3 f = fract(pixelPos);
//偏移特徵的點選擇左下角 由於右下角在 ==1 時 有問題
vec3 a = texture2D(uTexture,_pos.xy).xyz;
vec3 b = texture2D(uTexture,_pos.xy+vec2(-pixelSize,0)).xyz;
vec3 c = texture2D(uTexture,_pos.xy+vec2(0,pixelSize)).xyz;
vec3 d = texture2D(uTexture,_pos.xy+vec2(-pixelSize,pixelSize)).xyz;
vec3 u = smoothstep(0.,1.,f);
vec3 _mix = mix(mix(a,b,u.x),mix(c,d,u.x),u.y);
_mix-=.5;
return vec4(_mix,1);
}
vec4 touchRate(in vec3 pos){
//計算d區域 vector field 速度
vec3 _pos = pos*vec3(.5,.5,1);
vec3 pixelPos = _pos*SIZE;
_pos.y += .5;
_pos.x += .5;
vec3 i = floor(pixelPos);
vec3 f = fract(pixelPos);
vec3 a = texture2D(uTexture,_pos.xy).xyz;
vec3 b = texture2D(uTexture,_pos.xy+vec2(pixelSize,0)).xyz;
vec3 c = texture2D(uTexture,_pos.xy+vec2(0,pixelSize)).xyz;
vec3 d = texture2D(uTexture,_pos.xy+vec2(pixelSize)).xyz;
vec3 u = smoothstep(0.,1.,f);
vec3 _mix = mix(mix(a,b,u.x),mix(c,d,u.x),u.y);
_mix-=.5;
return vec4(_mix,1);
}
void main(){
vec4 color = texture2D(uTexture,vPosition);
if(vPosition.y<.5){
if(vPosition.x<.5){
//a區域的計算 xyz = rgb
vec4 v = texture2D(uTexture,vPosition+vec2(.5,0))-.5;
v/=pow(2.,12.);
v.xy *= (1.-cos(color.z*PI*1.5))/3.;
if(color.z+v.z<=.0){
//重置位置 解決一段時間後 粒子重合得問題
gl_FragColor = vec4(vPosition*2.,1,1);
}else{
gl_FragColor = fract(color+v);
}
}else{
//b區域計算
//當前粒子座標獲取
vec3 currentPos = texture2D(uTexture,vPosition+vec2(-.5,0)).xyz;
gl_FragColor=color+(randomRate(currentPos)+touchRate(currentPos))*512.;
gl_FragColor = .5+(gl_FragColor-.5)/5.;
}
}else{
vec2 offset = vec2(sin(uTime/27.)*sin(uTime),cos(uTime/17.)*cos(uTime)*.5);
gl_FragColor=vec4(0.5,0.5,0.5,1);
if(vPosition.x<.5){
//c區域計算
gl_FragColor.rg=vec2(noise(vPosition*4.+offset*2.3),noise(vPosition*3.+offset.yx));
gl_FragColor.b = .4;
}else{
//d區域計算
vec2 _point = uPointer.xy/2.+vec2(.5);
float _len = distance(_point,vPosition);
vec2 _color = (color.xy-vec2(.5))*.99;
if(length(_color)<.04){
_color = vec2(0);
}
gl_FragColor.rg = _color;
if(uPointer.x>=.0&&_len<.05){
gl_FragColor.rg+=(1.-_len/.05)*uPointer.zw;
}
gl_FragColor.rg += vec2(.5);
}
}
gl_FragColor.a = 1.;
}
複製代碼
感受代碼好多~。。看不懂的話 我再分細點。。,其實主要是講的思路,利用texture能夠把邏輯放入GPU中實現流暢特效。ui