前言
本篇文章带着大家一起学习粒子网站的实现。
实现目标
- 随机大小颜色位置粒子
- 附着在模型附近的粒子
- 模型粒子随着滚动爆炸与聚拢
- 鼠标放上模型粒子出现扰流效果
实现页面滚动效果
我们要实现 3D 的页面滚动,其实就是添加多个 100vh 的页面
这里我们不用 Vue 和 React,就普通的 html 来进行效果实现,这里我使用 vite 的 Vanilla 进行快速创建
初始化以及调整目录和写基础样式
使用 vite 创建基础结构
1 | npm init vite |
然后按照我截图部分进行选择即可
依赖安装
然后就是简单的依赖安装,你多安装一个 three 即可
调整目录结构
接下来我把目录结构改成了这样
目录详情内容
main.js
暂时是空的index.html
1 |
|
- style.css
1 | * { |
此时我们的页面效果如下,但是是没有滚动效果的,默认显示第一页。
自己实现滚动效果
这里只需要在 main.js 中写上这样一段代码
1 | let currentPage = { value: 0 }; |
实现逻辑
上面的代码我根据滚轮的滚轮,修改了 dom 的transform
属性,因为我设置了三屏,也就是可以滚动 200vh,然后我设置了 20 页的totalPages
,所以每次滚动就修改currentPage.value * (200/20)
的距离,又因为在 css 中加了transition
的过渡动画,可以让我们的滚动,看起来更加的流畅,不然就会有一种突然滚了一段距离的体验,这样很不好。
效果:
生成随机粒子
在实现上面的效果之后,我们进行随机粒子的生成,这里我用 Threejs 的生成点来做。
初始化代码
我们现在 main.js 中初始化 Threejs 代码,这个很简单,我就复制一下
1 | import * as THREE from "three"; |
去除 css 背景色
因为我们这时候要显示 canvas 的内容了,所以需要将 css 中的背景色去除
1 | .page { |
随机点
我们这里创建一个随机点的类RandomSpacePoints.js
,这里也比较简单,就是利用了 Threejs 的点。我这里先创建了方向和速度是为了之后让点运动起来,这里暂时没用到,你也可以先不写。
1 | // 导入threejs |
导入并使用
在 main.js 中导入并使用
1 | import RandomSpacePoints from "./mesh/RandomSpacePoints.js"; |
此时我们页面中就已经有了我们的随机粒子
给粒子添加材质
这里我们的粒子并不是很好看,可以给粒子添加贴图。这里我使用了这张图片
贴图使用
在创建粒子的使用把材质修改一下,因为我选的这张图片本来就是黑白的,所以当纹理贴图的时候也可以用作透明度贴图。
1 | // 纹理贴图 |
此时的我们的点就稍微好看一点了。
添加模型点
接下来我们要实现生成给模型描边的点数据,首先我们要导入模型,这个比较简单,我不多阐述
模型导入
1 | // 导入gltf加载器 |
这里我们需要获取到模型的 mesh 用来生成粒子,创建一个生成模型粒子的类RandomModelPoints.js
创建模型点的类
搭建基本架构
这里的基本架构和之前随机生成空间中的点是一样的
1 | // 导入threejs |
ok,接下来我们着重写一下这个generatePoints
方法
generatePoints
首先定义一些参数
1 | // 根据模型生成随机点 |
得到纹理数据
这里通过绘制 canvas 来得到纹理数据
1 | // 如果有纹理贴图 |
获取三角面
我们都知道模型都是通过一个个的三角形搭建起来的,所以我们需要获取它的三角面
1 | // 循环生成随机点 |
根据三角形的三个点位置随机生成一个三角形内部的点数据
我们通过上面的方法已经得到了三角面的三个点,接下来我们要去三角面里面得到一个随机点
1 | // 在三角形内部生成一个随机点 |
getRandomPointInTriangle
1 | // 在三角形内部生成随机点 |
这里触及了几何知识,大概原因如下,理解不了就直接把这个函数记住,以后要用的话直接用,也不用刻意理解,我们学习还是很注重不求甚解的。
原理
- 随机数的作用:
r1
和r2
是两个在 0 到 1 之间的随机数。通过使用随机数,可以在不同的调用中产生不同的点,从而实现随机分布在三角形内。
对r1
取平方根得到sqrtR1
,这一步进一步增加了随机性的多样性。 - 计算公式分析:
对于三角形内任意一点P(x,y,z)
,可以通过线性插值的方式来表示。假设有三角形的三个顶点A(a.x,a.y,a.z)
、B(b.x,b.y,b.z)
和C(c.x,c.y,c.z)
。
首先,将三角形的一条边AB
看成由A
向B
的线性插值,即对于参数t
,有AB(t) = A + t*(B - A)
。
同样,将边AC
看成由A
向C
的线性插值,对于参数s
,有AC(s) = A + s*(C - A)
。
现在要在三角形ABC
内找到一点P
,可以将P
表示为在AB
和AC
上的线性插值,即P = AB(u) + v*(AC(u) - AB(u))
,其中u
和v
是两个参数。
令u = sqrtR1,v = r2
,代入上述公式进行推导:AB(u) = A + u*(B - A) = A + sqrtR1*(B - A) = A*(1 - sqrtR1) + sqrtR1*B
。AC(u) = A + u*(C - A) = A + sqrtR1*(C - A)
。P = AB(u) + v*(AC(u) - AB(u)) = A*(1 - sqrtR1) + sqrtR1*B + r2*(A*(sqrtR1 - 1) + sqrtR1*C - A*(1 - sqrtR1) - sqrtR1*B) = A*(1 - sqrtR1) + sqrtR1*(1 - r2)*B + sqrtR1*r2*C
。
这就得到了函数中的计算公式,即x = (1 - sqrtR1) * a.x + sqrtR1 * (1 - r2) * b.x + sqrtR1 * r2 * c.x
等。 - 数学性质保证:
由于sqrtR1
和r2
都在0
到1
之间,并且通过线性插值的方式组合了三角形的三个顶点坐标,所以生成的点必然在三角形内部。
如果sqrtR1
和r2
的取值范围超出了0
到1
,那么生成的点可能会在三角形外部。但由于随机数的性质和限制,使得生成的点在三角形内部的概率较高。
处理颜色
1 | // 处理颜色 |
getColorFromTexture
上面用到了这个方法,这里讲解一下这个方法
1 | getColorFromTexture(textureData, width, height, uv) { |
最后设置一下属性
1 | // 将随机点的位置属性设置为 pointsGeometry 的属性 |
完整代码
1 | // 导入threejs |
导入模型之后使用这个类
1 | import RandomSpacePoints from "./mesh/RandomSpacePoints.js"; |
使用 onBeforeCompile 修改着色器
此时其实我们的 size 属性是没有生效的,这里我们修改一下材质
1 | geometry.setAttribute("vSize", new THREE.BufferAttribute(sizes, 1)); |
添加多个模型
上面我们讲完了一个模型,接下来,我们不是有 300vh 的页面吗,给每个 100vh 加上一个模型,生成点位数据
生成点位数据
这里我把前面的let points
改成了let spacePoints
方便理解,然后就是添加模型即可,设置一下模型的位置
1 | // 创建空间粒子 |
修改相机位置
1 | // 持续渲染,让相机场景动起来 |
此时效果如下
添加粒子运动
空间随机粒子添加漂浮动画
我们先给空间中的随机粒子添加漂浮动画
定义基本结构
1 | // 导入threejs |
定义这样一个方法,将基本结构如下,这样我们就可以在 main.js 中调用
1 | import particlesAnimate from "./mesh/ParticlesAnimate.js"; |
添加动画
接下来,我们只需要对pointsGeometry
进行操作即可,这里我给偏移量做了区分,因为模型的点不用动太多。
1 | // 导入threejs |
此时效果如下
添加模型粒子炸裂动画
我们已经完成了漂浮动画的实现,接下来,我们不是还有一个randomPositions
的属性没有使用吗,在创建模型粒子的时候创建的,这个就是为了炸裂动画使用的,我们只需将代码简单修改,并传入当前页currentPage
1 | const randomPositions = pointsGeometry.attributes.randomPosition |
此时,我们即可得到对应的模型粒子炸裂动画,效果如下。
添加扰流动画
最后,我们给我们的模型粒子添加一个扰流动画,就是鼠标放上去,粒子会稍微扩散一点的动画
在 main.js 设置一些检测鼠标划过的参数
1 | // 创建射线 |
生成动画的时候将参数传进去
然后我们在生成动画的时候把参数传进去,如下:
1 | const points = particlesAnimate(new RandomModelPoints(child, 8000), ...params); |
对生成动画的方法进行修改
这里只写一下添加的代码
1 | export default function particlesAnimate( |
完整动画代码
1 | // 导入threejs |
最终效果
到这里,我们就已经完成了这个小案例了,让我们看看最终效果
结语
我把代码放到了我的gitee上面,大家如果需要的话可以去拉取一下。本篇文章就到这里了,更多内容敬请期待,债见。