前段时间领导要我作一个流体粒子的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