这个是粒子系统的核心处理代码,主要的代码在GPUParticleSystem.js中,调用代码请参考:粒子(1)
这个脚本里主要有两个类:GPUParticleSystem 和 GPUParticleContainer
THREE.Object3D.apply( this, arguments );
每个函数都包含两个非继承而来的方法:call()方法和apply()方法, 这里使用apply改变 Object3D 默认构造函数里面的this,使其指向GPUParticleSystem,帮助 GPUParticleSystem实例生成一些存在于 Object3D 构造函数里的变量。
结合下方的:
THREE.GPUParticleSystem.prototype = Object.create( THREE.Object3D.prototype );
THREE.GPUParticleSystem.prototype.constructor = THREE.GPUParticleSystem;
可以继承 Object3D 里各式各样的函数们。
剩下的代码都是,options里面有参数传来就用options里的,没有就用默认的。
接下来的部分,在GPUParticleShader里面定义了两个shader的处理过程。
uniform
首先是顶点着色器,uniform代表不会在着色器里面被改变的值,实例材质的时候被传进去
attribute
attribute仅仅用在顶点着色器,作用类似于为每一个顶点除了位置配备了一些额外的信息,像是法线,贴图坐标,颜色啥的。
通过设置Geometry的attribute实现数据传递。像是下面这样:
varying
varying 可以实现顶点着色器和片段着色器的数据传递。定义的变量是顶点着色器的输出,片段着色器的输入,在两个着色器里,必须分别有相同的 varying 的声明。
向量一般要和矩阵进行计算,而矩阵一般都是4*4的,所以把一些 v3 的变量转换成 v4 ,
主要的时间维度的变化体现在这个函数里,timeElapsed 代表粒子的到目前的存活时间,lifeLeft 表示还剩的时间,根据这两个值,来控制响应时间段内点的大小,新的位置。
函数 mix(x,y,a)返回 x⋅(1−a)+y⋅a
理解基本逻辑问题不是太大,难点在于shader的调试和里面各类的数值的调试。这个有心得在总结。
fragmentShader 主要是根据lifeLeft的大小在获得颜色数据的基础上,进行透明度的改变,以达到根据时间变淡和消失的效果。
在system的初始化中,会进行container的实例化。
system更像一个管理者的角色,在 container 的初始化中,会进行THREE.Points的操作。
在spawnParticle中,进行位置,速度,时间,颜色等等的更新。在update的时候,进行时间的更新。
有几点不是很清楚。
第一个是:
this.particleSpriteTex = this.PARTICLE_SPRITE_TEXTURE || textureLoader.load( 'textures/particle2.png' );
this.particleSpriteTex.wrapS = this.particleSpriteTex.wrapT = THREE.RepeatWrapping;
wrapS和wrapT 这个概念本身没什么问题,主要是处理贴图和图像尺寸不匹配时,贴图要怎样配合图像的问题。
可以付给这个变量的值有三个:
THREE.ClampToEdgeWrapping:默认。 超过1.0的值被固定为1.0。 超过1.0的其它地方的纹理,沿用最后像素的纹理。
THREE.RepeatWrapping:平铺重复。超过1.0的值都被置为0.0。纹理被重复一次。 在渲染具有诸如砖墙之类纹理的物体时,比较有用。
THREE.MirroredRepeatWrapping:镜像重复。每到边界处纹理翻转,意思就是每到1.0的时候,u或者v处纹理被镜像翻转。
这个概念倒是没有什么问题,我也知道是用来平铺的,只是去掉他,也没看出来什么影响。没有像铺地砖那么明显的感觉,也可能是在很极端的时候才会有用?
还有一个是这边:
if ( this.offset + this.count < this.PARTICLE_COUNT ) {
positionStartAttribute.updateRange.offset = this.offset * positionStartAttribute.itemSize;
positionStartAttribute.updateRange.count = this.count * positionStartAttribute.itemSize;
} else {
positionStartAttribute.updateRange.offset = 0;
// Use -1 to update the entire buffer, see #11476
positionStartAttribute.updateRange.count = - 1;
}
positionStartAttribute.needsUpdate = true;
作者用了很大的篇幅,对每个Geometry的 updateRange 里的 offset 和 count 都进行了设置。
我的理解是,毕竟每一帧不需要更新所有的点的信息,而且点很多,全更新一遍的计算量也不小,所以这样写应该是为了局部数据的更新,文档上也是这样说的。
困惑的是,单独打出来这个 updateRange 是可以看见里面的值发生变化的。
如果打印Geometry级别的console就看不出来任何的变化。所以到底有没有用,有多大的用,我不是十分确定。
如果完全注释掉这部分的代码也没见明显的卡顿或者效率明显的下降。所以~ 等我哪天发现他的神奇作用,在来回更~ 留个坑~
07-17 新更
今天思考了一下,突然想到可以去查源码,脑子有时候就会有点短路,查了源码,发现上面的updateRange 确实是有用的。
我把webgl_gpu_particle_system.html里面的循环调成了3,循环太多还打了这么多的console,分分钟容易被卡死。
three里面的执行代码是这样的~
最后一个console被打印说明是更新了局部,然后又把count重置为-1,至于最开始的console为啥有的地方也是-1,我能想到的解释只有是 count 指的内存是同一块,别的地方改了所有的获取这个值都会跟着改。