Three.js 进阶之旅:神奇的粒子系统-迷失太空
本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究!
声明:本文涉及图文和模型素材仅用于个人学习、研究和欣赏,请勿二次修改、非法传播、转载、出版、商用、及进行其他获利行为。
摘要粒子particle和 精灵sprite是在三维开发中经常用到的网格模型,在Three.js中使用THREE.Points可以非常容易地创建很多细小的物体,可以用来模拟星辰、雨滴、雪花、烟雾、火焰和其他有趣的效果。本文将讨论关于Three.js中各种创建粒子的方式和以及如何优化粒子的样式和使用粒子,最终将结合本文所讲的粒子知识,制作一个充满趣味和创意的3D粒子页面——迷失太空。
通过阅读本文及配套对应代码,你将学到的内容包括:使用THREE.Sprite创建粒子集合、使用THREE.Points创建粒子集合、如何创建样式化的粒子、使用dat.GUI动态控制页面参数、使用Canvas样式化粒子、使用纹理贴图样式化粒子、从高级几何体创建粒子、给场景添加Fog和FogExp2雾化效果、使用正余弦函数给模型添加动画效果、鼠标移动动画等。
效果本文代码中包含7个粒子效果示例,解开代码中相应的注释即可查看每种粒子效果。迷失太空是本文中最后综合应用粒子知识实现的一个创意页面,下图就是它的实现效果。整个页面类似科幻电影《地心引力》的海报,宇航员不慎迷失太空,逃逸向无边无际的宇宙深处??。在屏幕上使用鼠标移动,宇航员?????和 星辰?都会根据鼠标的移动产生相反方向的位移动画。
打开以下链接中的任意一个,在线预览效果,大屏访问效果更佳。
?????在线预览地址1:https://dragonir.github.io/3d/#/gravity?????在线预览地址2:https://3d-eosin.vercel.app/#/gravity本专栏系列代码托管在Github仓库【threejs-odessey】,后续所有目录也都将在此仓库中更新。
??代码仓库地址:git@github.com:dragonir/threejs-odessey.git
《地心引力》海报
gravity.png码上掘金代码片段
实现本文中有7个关于Three.js粒子效果的示例,为了方便,全都写在一个文件中,下述步骤中资源引入和场景初始化都是通用的,其他步骤0~6都是一个个单独的示例。在代码中,只要解开每个示例的注释,即可查看对应粒子效果。
资源引入本示例中,除了引入样式表、Three.js、镜头轨道控制器OrbitControls、模型加载器GLTFLoader之外,还额外引入了dat.GUI,它是可以通过在页面上添加一个可视化控制器来修改代码参数的库,方便动态查看和调试页面在各种参数下的渲染效果,具体用法在本文后续内容中有详细的描述。
import'./style.css';
import*asTHREEfrom'three';
import*asdatfrom'dat.gui';
import{OrbitControls}from'three/examples/jsm/controls/OrbitControls';
import{GLTFLoader}from'three/examples/jsm/loaders/GLTFLoader.js';
场景初始化正式开发粒子效果之前,需要经过初始化渲染器、场景、相机、缩放事件监听、页面重绘动画调用等必备步骤。关于它们的具体原理本文不再赘述,需要了解的可前往本专栏前两章查看《Three.js 进阶之旅:基础入门(上)》、《Three.js 进阶之旅:基础入门(下)》。
//初始化渲染器
constcanvas=document.querySelector('canvas.webgl');
constrenderer=newTHREE.WebGLRenderer({canvas:canvas
renderer.setSize(sizes.width,sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));
//初始化场景
constscene=newTHREE.Scene();
//初始化相机
constcamera=newTHREE.PerspectiveCamera(45,sizes.width/sizes.height,0.1,1000)
camera.position.z=120
camera.lookAt(newTHREE.Vector3(0,0,0))
scene.add(camera);
//镜头控制器
constcontrols=newOrbitControls(camera,renderer.domElement);
controls.enableDamping=true;
//页面缩放事件监听
window.addEventListener('resize',()={
sizes.width=window.innerWidth;
sizes.height=window.innerHeight;
//更新渲染
renderer.setSize(sizes.width,sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio,2))
//更新相机
camera.aspect=sizes.width/sizes.height;
camera.updateProjectionMatrix();
});
//页面重绘动画
consttick=()={
controlscontrols.update();
//更新渲染器
renderer.render(scene,camera);
//页面重绘时调用自身
window.requestAnimationFrame(tick);
}
tick();
〇 使用THREE.Sprite创建粒子Three.js提供多种方法创建粒子,首先我们使用THREE.Sprite来通过如下的方式创建一个20 x 30的粒子系统。通过new THREE.Sprite()构造方法来创建粒子,给它传入唯一的参数材质,此时可选的材质类型只能是THREE.SpriteMaterial或THREE.SpriteCanvasMaterial。创建材质时将它的color属性值设置成了随机色。由于THREE.Sprite对象继承于THREE.Object3D,它的大多数属性和方法都可以直接使用。示例中使用了position方法对粒子进行定位设置。还可以使用scale属性进行缩放、使用translate属性进行位移设置等。
constcreateParticlesBySprite=()={
for(letx=-15;x15;x++){
for(lety=-10;y10;y++){
letmaterial=newTHREE.SpriteMaterial({
color:Math.random()*0xffffff
letsprite=newTHREE.Sprite(material);
sprite.position.set(x*4,y*4,0);
scene.add(sprite);
}
}
}
将创建的粒子系统添加到场景中,就能看到如下图所示的粒子方块点阵。它们是一个个的彩色方块构成的网格,如果使用鼠标或触控板在场景中移动,你会发现,无论从哪个角度观察,阵列中的彩色方块看起来都没有变化,每一个粒子都是永远面向摄像机的二维平面,如果在创建粒子的时候没有指定任何属性,那么它们将会被渲染成二维的白色小方块□。
method_0.gif知识点 ??精灵材质 THREE.SpriteMaterialTHREE.SpriteMatrial对象的一些可修改属性及其说明。
color:粒子的颜色。map:粒子所用的纹理,可以是一组sprite sheet。sizeAttenuation:如果该属性设置为false,那么距离摄像机的远近不影响粒子的大小,默认值为true。opacity:该属性设置粒子的不透明度。默认值为1,不透明。blending:该属性指定渲染粒子时所用的融合模式。fog:该属性决定粒子是否受场景中雾化效果影响。默认值为true。① 使用THREE.Points创建粒子通过THREE.Sprite你可以非常容易地创建一组对象并在场景中移动它们。当你使用少量的对象时,这会很有效,但是如果需要创建大量的粒子,如果这时候还是使用THREE.Sprite的话,就会产生性能问题,因为每个对象需要分别由Three.js进行管理。
Three.js提供了另一种方式来处理大量的粒子,就是使用THREE.Points,通过Three.Points,Three.js不需要管理大量THREE.Sprite对象,而只需要管理THREE.Points实例。使用这种方法创建粒子系统时,首先要创建粒子的网格THREE.BufferGeometry,然后创建粒子的材质THREE.PointsMaterial。然后创建两个数组veticsFloat32Array和veticsColors,用来管理粒子系统中每个粒子的位置和颜色,通过THREE.Float32BufferAttribute将它们设置为网格属性。最后使用THREE.Points将创建的网格和材质变为粒子系统添加到场景中。
constcreateParticlesByPoints=()={
constgeom=newTHREE.BufferGeometry();
constmaterial=newTHREE.PointsMaterial({
size:3,
vertexColors:true,
color:0xffffff
letveticsFloat32Array=[]
letveticsColors=[]
for(letx=-15;x15;x++){
for(lety=-10;y10;y++){
veticsFloat32Array.push(x*4,y*4,0);
constrandomColor=newTHREE.Color(Math.random()*0xffffff);
veticsColors.push(randomColor.r,randomColor.g,randomColor.b);
}
}
constvertices=newTHREE.Float32BufferAttribute(veticsFloat32Array,3);
constcolors=newTHREE.Float32BufferAttribute(veticsColors,3);
geom.attributes.position=vertices;
geom.attributes.color=colors;
constparticles=newTHREE.Points(geom,material);
scene.add(particles);
}
实现效果如下图所示,使用THREE.Points可以得到和使用THREE.Sprite相同的结果。
method_1.png知识点 ??点材质 THREE.PointsMaterial上述例子中使用THREE.PointsMaterial来样式化粒子,它是THREE.Points使用的默认材质,下面列举了THREE.PointsMaterial中所有可设置属性及其说明。
color: 粒子系统中所有粒子的颜色。将vertexColors属性设置为true,并且通过颜色属性指定了几何体的颜色来覆盖该属性。默认值为0xFFFFFF。map: 通过这个属性可以在粒子材质,比如可以使用canvas、贴图等。size:该属性指定粒子的大小,默认值为1。sizeAnnutation: 如果该属性设置为false,那么所有的粒子都将拥有相同的尺寸,无论它们距离相机有多远。如果设置为true,粒子的大小取决于其距离摄像机的距离的远近,默认值为true。vertexColors:通常THREE.Points中所有的粒子都拥有相同的颜色,如果该属性设置为THREE.VertexColors,并且几何体的颜色数组也有值,那就会使用颜色数组中的值,默认值为THREE.NoColors。opacity:该属性与transparent属性一起使用,用来设置粒子的不透明度。默认值为1(完全不透明)。transparent:如果该属性设置为true,那么粒子在渲染时会根据opacity属性的值来确定其透明度,默认值为false。blending:该属性指定渲染粒子时的融合模式。fog:该属性决定粒子是否受场景中雾化效果影响,默认值为true。② 创建样式化的粒子在上个例子的基础上,我们改造一下创建粒子的方法,通过给THREE.PointsMaterial动态传入参数的方式来修改粒子的样式。为了能够实时修改参数并同时能够在页面上查看到参数改变之后的效果,我们可以使用dat.GUI库来实现这一功能。首先,通过new dat.GUI()进行初始化,然后通过.add()及.addColor()等方法为它添加控制选项,并在控制选项发生改变时在.onChange()中调用我们预先写好的回调函数来更新粒子样式。回调函数ctrls也很简单,就是通过scene.getObjectByName("particles")找到场景中已经创建好的粒子将它删除,然后使用新的参数再次调用createStyledParticlesByPoints来创建新的粒子。
constcreateStyledParticlesByPoints=(ctrls)={
constgeom=newTHREE.BufferGeometry();
constmaterial=newTHREE.PointsMaterial({
size:ctrls.size,
transparent:ctrls.transparent,
opacity:ctrls.opacity,
color:newTHREE.Color(ctrls.color),
vertexColors:ctrls.vertexColors,
sizeAttenuation:ctrls.sizeAttenuation
letveticsFloat32Array=[]
letveticsColors=[]
for(letx=-15;x15;x++){
for(lety=-10;y10;y++){
veticsFloat32Array.push(x*4,y*4,0)
constrandomColor=newTHREE.Color(Math.random()*ctrls.vertexColor)
veticsColors.push(randomColor.r,randomColor.g,randomColor.b)
}
}
constvertices=newTHREE.Float32BufferAttribute(veticsFloat32Array,3)
constcolors=newTHREE.Float32BufferAttribute(veticsColors,3)
geom.attributes.position=vertices;
geom.attributes.color=colors;
constparticles=newTHREE.Points(geom,material);
particles.name='particles';
scene.add(particles)
}
//创建属性控制器
constctrls=newfunction(){
this.size=5;
this.transparent=true;
this.opacity=0.6;
this.vertexColors=true;
this.color=0xffffff;
this.vertexColor=0x00ff00;
this.sizeAttenuation=true;
this.rotate=true;
this.redraw=function(){
if(scene.getObjectByName("particles")){
scene.remove(scene.getObjectByName("particles"));
}
createStyledParticlesByPoints({
size:ctrls.size,
transparent:ctrls.transparent,
opacity:ctrls.opacity,
vertexColors:ctrls.vertexColors,
sizeAttenuation:ctrls.sizeAttenuation,
color:ctrls.color,
vertexColor:ctrls.vertexColor
}
constgui=newdat.GUI();
gui.add(ctrls,'size',0,10).onChange(ctrls.redraw);
gui.add(ctrls,'transparent').onChange(ctrls.redraw);
gui.add(ctrls,'opacity',0,1).onChange(ctrls.redraw);
gui.add(ctrls,'vertexColors').onChange(ctrls.redraw);
gui.addColor(ctrls,'color').onChange(ctrls.redraw);
gui.addColor(ctrls,'vertexColor').onChange(ctrls.redraw);
gui.add(ctrls,'sizeAttenuation').onChange(ctrls.redraw);
添加dat.GUI后,页面顶部就会出现一个对应参数可视化的控制器??,用鼠标在上面下拉或滑动修改参数,就能实时更新到页面中了。赶快动手试试,看看修改不同参数对粒子的影响有何不同吧??。
method_2.png知识点 ??dat.GUIdat.GUI是一个轻量级的JavaScript图形用户界面控制库,它可以轻松地即时操作变量和触发函数,通过设定好的控制器去快捷的修改设定的变量。下面是它的一些基本使用方法:
//初始化
constgui=newdat.GUI({name:'name'});
//初始化控件属性
constctrls={
name:'acqui',
speed:0.5,
color1:'#FF0000',
color2:[0,128,255],
color3:[0,128,255,0.3],
color4:{h:350,s:0.9,v:0.3},
test:',
test2:',
cb:()={},
gender:true
};
//gui.add(控件对象变量名,对象属性名,其它参数),控制字符类型或数字
gui.add(ctrls,'name');
//缩放区间[0,100],变化步长10
gui.add(ctrls,'speed',0,100,10);
//创建一个下拉选择
gui.add(ctrls,'test',{低速:0.005,中速:0.01,高速:0.1}).name('转速');
gui.add(ctrls,'test2',['低速','中速','高速']).name('转速2');
//创建按钮
gui.add(ctrls,'cb').name('按钮');
gui.add(ctrls,'gender').name('性别');
//控制颜色属性
gui.addColor(ctrls,'color1');
//通过name可设置别名
gui.addColor(ctrls,'color2').name('颜色2');
//创建一个Folder
constfolder=gui.addFolder('颜色组');
folder.addColor(ctrls,'color3');
folder.addColor(ctrls,'color4');
//可以通过onChange方法来监听改变的值,从而修改样式
gui.addColor(ctrls,'color2').onChange(callback);
??dat.GUI不仅能用到Three.js开发中,在其他需要实时修改参数查看效果的场景下都能用哦。 官网地址:https://github.com/dataarts/dat.gui。
③ 使用Canvas样式化粒子THREE.js提供了将HTML5画布Canvas转化为纹理的功能,利用这一特性,我们就可以创建个性化的Canvas来美化我们的粒子效果。在本文示例createParticlesByCanvas方法中,首先提供了一个createCanvasTexture的方法用来生成Canvas纹理,在该方法中,我们创建一个Canvas画布,然后在上面绘制一个彩色渐变圆环,最后使用THREE.CanvasTexture方法将其转化为可以在Three.js中渲染的纹理。然后使用该纹理,通过map属性将其传递给粒子材质THREE.PointsMaterial。这样粒子就具有了如下图所示的Canvas纹理效果了。
注意??,如果此时用鼠标旋转粒子时会发现粒子四周本该透明的地方是黑色的,而且前后粒子之间还存在穿透问题。此时需要给粒子材质设置depthTest: true、depthWrite: false设置这两个属性,以解决粒子显示正常的透明效果以及前后叠加层级问题。
constcreateParticlesByCanvas=()={
//使用canvas创建纹理
constcreateCanvasTexture=()={
constcanvas=document.createElement('canvas')
constctx=canvas.getContext('2d')
canvas.width=300
canvas.height=300
ctx.lineWidth=10;
ctx.beginPath();
ctx.moveTo(170,120);
vargrd=ctx.createLinearGradient(0,0,170,0);
grd.addColorStop('0','black');
grd.addColorStop('0.3','magenta');
grd.addColorStop('0.5','blue');
grd.addColorStop('0.6','green');
grd.addColorStop('0.8','yellow');
grd.addColorStop(1,'red');
ctx.strokeStyle=
ctx.arc(120,120,50,0,Math.PI*2);
ctx.stroke();
consttexture=newTHREE.CanvasTexture(canvas)
texture.needsUpdate=true
returntexture
}
//创建粒子系统
constcreateParticles=(size,transparent,opacity,sizeAttenuation,color)={
constgeom=newTHREE.BufferGeometry()
constmaterial=newTHREE.PointsMaterial({
size:size,
transparent:transparent,
opacity:opacity,
map:texture,
sizeAttenuation:sizeAttenuation,
color:color,
depthTest:true,
depthWrite:false
})
letveticsFloat32Array=[]
constrange=500
for(leti=0;i400;i++){
constparticle=newTHREE.Vector3(Math.random()*range-range/2,Math.random()*range-range/2,Math.random()*range-range/2)
veticsFloat32Array.push(particle.x,particle.y,particle.z)
}
constvertices=newTHREE.Float32BufferAttribute(veticsFloat32Array,3)
geom.attributes.position=vertices
constparticles=newTHREE.Points(geom,material)
scene.add(particles)
}
createParticles(40,true,1,true,0xffffff)
}
method_3.gif知识点 ??Canvas纹理CanvasTexture用于从Canvas元素中创建纹理贴图。
构造函数:
CanvasTexture(canvas:HTMLElement,mapping:Constant,wrapS:Constant,wrapT:Constant,magFilter:Constant,minFilter:Constant,format:Constant,type:Constant,anisotropy:Number)
canvas:将会被用于加载纹理贴图的Canvas元素。mapping:纹理贴图将被如何应用到物体上。wrapS:默认值是THREE.ClampToEdgeWrapping。wrapT:默认值是THREE.ClampToEdgeWrapping。magFilter:当一个纹素覆盖大于一个像素时贴图将如何采样,默认值为THREE.LinearFilter。minFilter:当一个纹素覆盖小于一个像素时贴图将如何采样,默认值为THREE.LinearMipmapLinearFilter。format:在纹理贴图中使用的格式。type:默认值是THREE.UnsignedByteType。anisotropy:沿着轴,通过具有最高纹素密度的像素的样本数。 默认情况下,这个值为1。设置一个较高的值将会产生比基本的mipmap更清晰的效果,代价是需要使用更多纹理样本。属性和方法:
.isCanvasTexture[Boolean]:检查是否是CanvasTexture类型纹理的只读属性。.needsUpdate[Boolean]:是否需要更新,默认值为true,以便使得Canvas中的数据能够载入。其他属性和方法继承于Texture。④ 使用纹理贴图样式化粒子自然,Three.js中粒子材质也可以直接使用THREE.TextureLoader加载图片作为纹理进行粒子样式个性化设置。createParticlesByTexture方法和createParticlesByCanvas除了在给THREE.PointsMaterial设置map属性之处有区别之外,其他地方完全相同,因此下面代码只保留了关键内容,详情可查看源码。
constcreateParticlesByTexture=()={
constcreateParticles=(size,transparent,opacity,sizeAttenuation,color)={
//...
constmaterial=newTHREE.PointsMaterial({
'size':size,
'transparent':transparent,
'opacity':opacity,
//加载自定义图片作为粒子纹理
'map':newTHREE.TextureLoader().load('/images/heart.png'),
'sizeAttenuation':sizeAttenuation,
'color':color,
depthTest:true,
depthWrite:false
})
//...
}
}
本文示例中采用了一张霓虹心形爱心??图片作为粒子纹理,效果如下图所示:
method_4.gif⑤ 从高级几何体创建粒子THREE.Points是基于几何体的顶点来渲染每个粒子的,利用这一特性我们就可以从高级几何体来创建几何体形状的粒子。下面示例中我们利用THREE.SphereGeometry来创建一个球形的粒子系统。为了营造出好看视觉效果效果,我们可以使用Canvas的渐变方法createRadialGradient创建出一种类似发光特效来作为粒子的纹理。
constcreateParticlesByGeometry=()={
//创建发光canvas纹理
constgenerateSprite=()={
constcanvas=document.createElement('canvas');
canvas.width=16;
canvas.height=16;
constcontext=canvas.getContext('2d');
constgradient=context.createRadialGradient(canvas.width/2,canvas.height/2,0,canvas.width/2,canvas.height/2,canvas.width/2);
gradient.addColorStop(0,'rgba(255,255,255,1)');
gradient.addColorStop(0.2,'rgba(0,255,0,1)');
gradient.addColorStop(0.4,'rgba(0,120,20,1)');
gradient.addColorStop(1,'rgba(0,0,0,1)');
context.fillStyle=gradient;
context.fillRect(0,0,canvas.width,canvas.height);
consttexture=newTHREE.Texture(canvas);
texture.needsUpdate=true;
returntexture;
}
//创建立方体
constsphereGeometry=newTHREE.SphereGeometry(15,32,16);
//创建粒子材质
constmaterial=newTHREE.PointsMaterial({
color:0xffffff,
size:3,
transparent:true,
blending:THREE.AdditiveBlending,
map:generateSprite(),
depthWrite:false
})
constparticles=newTHREE.Points(sphereGeometry,material)
scene.add(particles)
}
method_5.png⑥ 迷失太空最后,我们利用上述汇总介绍的的粒子系统知识,并结合本专栏前几期的内容进行实践,打造一个趣味的3D页面,在无边无际的宇宙,宇航员跌向无尽深渊。
创建宇航员 ?????首先使用GLTFLoader模型加载器加载宇航员?????模型到场景中,并调整好模型在场景中的初始大小和位置。
constloader=newGLTFLoader();
loader.load('/models/astronaut.glb',mesh={
astronaut=mesh.scene;
astronaut.material=newTHREE.MeshLambertMaterial();
astronaut.scale.set(.0005,.0005,.0005);
astronaut.position.z=-10;
scene.add(astronaut);
});
astronaut.png创建宇宙粒子 ??使用上述创建粒子系统的方法,创建1000个粒子作为宇宙星系,并为它们设置限定范围内随机的位置,星系粒子纹理采用了一张径向渐变的贴图,将其添加到场景中。
constgeom=newTHREE.BufferGeometry();
constmaterial=newTHREE.PointsMaterial({
color:0xffffff,
size:10,
alphaTest:.8,
map:newTHREE.TextureLoader().load('/images/particle.png')
});
letveticsFloat32Array=[]
letveticsColors=[]
for(letp=0;p1000;p++){
veticsFloat32Array.push(
rand(20,30)*Math.cos(p),
rand(20,30)*Math.sin(p),
rand(-1500,0)
constrandomColor=newTHREE.Color(Math.random()*0xffffff);
veticsColors.push(randomColor.r,randomColor.g,randomColor.b);
}
constvertices=newTHREE.Float32BufferAttribute(veticsFloat32Array,3);
constcolors=newTHREE.Float32BufferAttribute(veticsColors,3);
geom.attributes.position=vertices;
geom.attributes.color=colors;
constparticleSystem=newTHREE.Points(geom,material);
scene.add(particleSystem);
场景优化 ?根据场景内摄像机的位置以及宇宙星系粒子在Z轴方向的变化,添加合适参数的黑色雾化效果来营造出由近到远星系由亮到暗的变化效果,增强画面的真实性。接着,为了宇航员显示在场景中,根据实际参数增加一些光照效果。
//雾化效果
scene.fog=newTHREE.FogExp2(0x000000,0.005);
//设置光照
letlight=newTHREE.PointLight(0xFFFFFF,0.5);
light.position.x=-50;
light.position.y=-50;
light.position.z=75;
scene.add(light);
light=newTHREE.PointLight(0xFFFFFF,0.5);
light.position.x=50;
light.position.y=50;
light.position.z=75;
scene.add(light);
light=newTHREE.PointLight(0xFFFFFF,0.3);
light.position.x=25;
light.position.y=50;
light.position.z=200;
scene.add(light);
light=newTHREE.AmbientLight(0xFFFFFF,0.02);
scene.add(light);
知识点 ??雾化效果Fog和FogExp2为了增强场景的真实性,示例中使用了FogExp2雾化效果,那么THREE.Fog和THREE.FogExp2有什么不同呢?
雾Fog.color:表示雾的颜色,场景中远处物体为黑色,场景中最近处距离物体是自身颜色,最远和最近之间的物体颜色是物体本身颜色和雾颜色的混合效果。.near:表示应用雾化效果的最小距离,距离活动摄像机长度小于.near的物体将不会被雾所影响。.far: 表示应用雾化效果的最大距离,距离活动摄像机长度大于.far的物体将不会被雾所影响。定义:表示线性雾,雾的密度是随着距离线性增大的,即场景中物体雾化效果随着随距离线性变化。构造函数:Fog(color, near, far)指数雾FogExp2.color:表示雾的颜色。.density:表示雾的密度的增速,默认值为0.00025。定义:表示指数雾,即雾的密度随着距离指数而增大。构造函数:FogExp2(color, density)添加动画效果 ??整个场景的动画分为以下三个部分:
粒子系统动画由以下两部分构成:粒子系统旋转动画:使用余弦函数Math.cos(t)改变粒子系统的position来创建旋转运动轨迹,其中t指定了旋转速度。粒子系统由近到远动画:通过遍历更改构成粒子的向量数组中表示每个粒子的Z轴方向的值来更新粒子系统实现。宇航员模型动画:宇航员旋转动画:通过修改宇航员在x、y、z三个坐标轴上的位置参数实现。宇航员由近到远动画:使用正弦函数Math.sin(t)来生成宇航员远近循环动画。渲染器和相机的页面重绘更新动画。constupdateParticles=()={
//粒子系统旋转动画
particleSystem.position.x=0.2*Math.cos(t);
particleSystem.position.y=0.2*Math.cos(t);
particleSystem.rotation.z+=0.015;
camera.lookAt(particleSystem.position);
//粒子系统由近到远动画
for(leti=0;iveticsFloat32Array.length;i++){
//如果是Z轴值,则修改数值
if((i+1)%3===0){
constdist=veticsFloat32Array[i]-camera.position.z;
if(dist=0)veticsFloat32Array[i]=rand(-1000,-500);
veticsFloat32Array[i]+=2.5;
const_vertices=newTHREE.Float32BufferAttribute(veticsFloat32Array,3);
geom.attributes.position=_vertices;
}
}
particleSystem.geometry.verticesNeedUpdate=true;
}
constupdateMeshes=()={
if(astronaut){
//宇航员由近到远动画
astronaut.position.z=0.08*Math.sin(t)+(camera.position.z-0.2);
//宇航员旋转动画
astronaut.rotation.x+=0.015;
astronaut.rotation.y+=0.015;
astronaut.rotation.z+=0.01;
}
}
//场景和相机更新
constupdateRenderer=()={
constwidth=canvas.clientWidth;
constheight=canvas.clientHeight;
constneedResize=canvas.width!==width||canvas.height!==height;
if(needResize){
renderer.setSize(width,height,false);
camera.aspect=canvas.clientWidth/canvas.clientHeight;
camera.updateProjectionMatrix();
}
}
consttick=()={
updateParticles();
updateMeshes();
updateRenderer();
renderer.render(scene,camera);
requestAnimationFrame(tick);
t+=0.01;
}
其中rand方法用于生成两数之间的随机数。
constrand=(min,max)=min+Math.random()*(max-min);
鼠标移动交互 ??为了增强页面的可交互性,可以添加一些鼠标效果。在迷失太空这个示例中,当鼠标??在页面上进行移动时,场景中的宇航员?????以及相机??的位置会根据鼠标的相反方向发生部分偏移。
window.addEventListener('mousemove',e={
constcx=window.innerWidth/2;
constcy=window.innerHeight/2;
constdx=-1*((cx-e.clientX)/
constdy=-1*((cy-e.clientY)/
camera.position.x=dx*5;
camera.position.y=dy*5;
astronaut.position.x=dx*5;
astronaut.position.y=dy*5;
});
CSS样式优化 ??最后,使用CSS在页面上添加一些装饰性文案和图片,其中的GRAVITY字样使用了一种免费的字体,大家可以选择自己喜欢的样式风格进行页面修饰,提升页面的整体视觉效果。
lost_in_space.png到此,本文涉及Three.js粒子系统的全部内容就结束了,完整代码可访问以下地址查看。
源码地址:https://github.com/dragonir/threejs-odessey
总结本文中主要包含的知识点包括:
使用THREE.Sprite创建粒子。使用THREE.Points创建粒子。创建样式化的粒子。使用dat.GUI动态控制页面参数。使用Canvas样式化粒子。使用纹理贴图样式化粒子。从高级几何体创建粒子。给场景添加Fog和FogExp2雾化效果。使用正弦余弦函数添加模型动画、鼠标移动动画等。
附录[1].?? Three.js 打造缤纷夏日3D梦中情岛[2].?? Three.js 实现炫酷的赛博朋克风格3D数字地球大屏[3].?? Three.js 实现2022冬奥主题3D趣味页面,含冰墩墩[4].?? Three.js 实现3D开放世界小游戏:阿狸的多元宇宙[5].?? 掘金1000粉!使用Three.js实现一个创意纪念页面...【Three.js 进阶之旅】系列专栏访问 ??更多往期【3D】专栏访问 ??更多往期【前端】专栏访问 ??参考[1]. 《Three.js 开发指南——基于WebGL和HTML5在网页上渲染3D图形和动画》[2]. https://threejs.org
阅读原文	
            
          
             
  网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521     13245491521	,我们会详细为你一一解答你心中的疑难。 项目经理在线