文字与气泡与粒子特效——玩转 three.js 的表面采样

呀哈喽!这里是 alphardex。

MeshSurfaceSampler,是 three.js 的表面采样器,通过它,我们可以在一个Mesh的表面拾取一定数量的随机点位,从而实现一些炫酷的粒子特效,请看以下的效果(全屏最佳):

https://code.juejin.cn/pen/7164008029766025223

本文将简要地介绍一下实现这个效果的思路

PS:本文标题 neta 了《孤独摇滚》的 live 曲《吉他与孤独与蓝色星球》

表面采样

首先用 kokomi.js 的Text3D类来创建 3D 文字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const font = await kokomi.loadFont();

const text = "J U E J I N";

const t3d = new kokomi.Text3D(
this,
text,
font,
{
size: 0.6,
height: 0.2,
curveSegments: 120,
bevelEnabled: true,
bevelThickness: 0.03,
bevelSize: 0.02,
bevelOffset: 0,
bevelSegments: 5,
},
{
baseMaterial: new THREE.ShaderMaterial(),
vertexShader,
fragmentShader,
materialParams: {
side: THREE.DoubleSide,
},
}
);
t3d.mesh.geometry.center();
t3d.addExisting();

1.png

利用 kokomi.js 封装好的函数sampleParticlesPositionFromMesh来采样文字表面的点位数据

1
2
3
4
const sampledPos = kokomi.sampleParticlesPositionFromMesh(
t3d.mesh.geometry,
8000
);

同时也获取下每个点位的下标

1
const pIndexs = kokomi.makeBuffer(sampledPos.length / 3, (v, k) => v);

将点位位置数据和下标数据存储至BufferGeometry

1
2
3
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.BufferAttribute(sampledPos, 3));
geometry.setAttribute("pIndex", new THREE.BufferAttribute(pIndexs, 1));

新建一个微粒对象THREE.Points,并定义好ShaderMaterial

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const uj = new kokomi.UniformInjector(this);

const material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
side: THREE.DoubleSide,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
uniforms: {
...uj.shadertoyUniforms,
uColor: {
value: new THREE.Color("#d1a657"),
},
uPointSize: {
value: 4,
},
},
});

this.update(() => {
uj.injectShadertoyUniforms(material.uniforms);
});

const im = new THREE.Points(geometry, material);
this.scene.add(im);

顶点着色器里传入微粒大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
uniform float iTime;
uniform vec2 iResolution;
uniform vec2 iMouse;

varying vec2 vUv;

uniform float uPointSize;

attribute float pIndex;

varying float vOpacity;
varying vec3 vPosition;

void main(){
vec3 p=position;
gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.);

vUv=uv;
gl_PointSize=uPointSize;
}

片元着色器里传入微粒颜色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
uniform float iTime;
uniform vec2 iResolution;
uniform vec2 iMouse;

varying vec2 vUv;

uniform vec3 uColor;

varying float vOpacity;
varying vec3 vPosition;

void main(){
vec2 p=gl_PointCoord;

vec3 col=uColor;

gl_FragColor=vec4(col,1.);
}

2.png

可以看到被采样的点均匀地分布在了文字之上,cool!让它动起来吧!

气泡效果

首先编写片元着色器,利用spot函数来把微粒变成气泡状

1
2
3
float shape=spot(p,.1,2.5);

gl_FragColor=vec4(col,shape);

将 uPointSize 微粒大小略微调大点,我们会看到下面的效果

3.png

接下来我们要在顶点着色器中变换顶点的 y 轴坐标,用到了随机函数和噪声函数,这里贴一下随机函数的实现吧,噪声函数请自行查看Github 链接

1
2
3
float random(float n){
return fract(sin(n)*43758.5453123);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vec3 distort(vec3 p){
float t=iTime*.25;
float rndz=(random(pIndex)+cnoise(vec2(pIndex*.1,t*.5)));
p.y+=fract((t+rndz)*.5);
return p;
}

void main(){
vec3 p=position;
p=distort(p);
gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.);

vOpacity=random(pIndex);
vPosition=p;
}

4.gif

在片元着色器中能够根据顶点着色器传递的vPositionvOpacity来实现气泡的随机大小和透明度效果

1
2
3
4
5
6
7
8
9
10
11
12
float saturate(float a){
return clamp(a,0.,1.);
}

void main(){
...
float alpha=1.-saturate(abs(vPosition.y*1.2));
shape*=alpha;

col*=vOpacity;
...
}

5.gif

如此,气泡效果就基本实现了,有几点可以自行优化:

  1. 将气泡动画和文字有机结合在一起
  2. 给气泡赋予随机的颜色
  3. 让文字变成可编辑的(提示:contenteditable

最后

希望本文能给你创作新特效的灵感,keep creating~

Author: alphardex
Link: https://alphardex.github.io/mygo/posts/8412/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.