腾讯UP2017 3D粒子效果在网页端实现

这段时间加班累成狗,终于尾声了,水一篇提升一下百度活跃度。

点击 预览 先看一下效果。

获取模型

使用常规的 3D 建模软件即可。拿到 3D 模型之后再进行处理,处理的软件使用的是Blender兼容性比较好的是 fbx 格式,然后通过给 Blender 安装插件之后,将模型转换为便于 three.js 读取的 JSON 格式。当然我并不会建模,所以直接从UP2017 腾讯互动娱乐年度发布会 - 腾讯互动娱乐 上直接把粒子模型“偷”下来:

json模型

初始工作

首先,初始化 threejs 三大元素:场景,相机,渲染器。我们需要一个用于切换的载体粒子体系和多个环境粒子体系(为了简单,在这只初始化了一个上下转动的环境粒子体系)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
camera = new THREE.PerspectiveCamera(
105,
window.innerWidth / window.innerHeight,
300,
10000
);
camera.position.z = 750;
// 初始化场景
scene = new THREE.Scene();
//雾化
scene.fog = new THREE.FogExp2(0x05050c, 0.0005);
//初始化renderer
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
// 初始化geometry
geometry = new THREE.Geometry();
around = new THREE.Geometry();
// 初始化贴图
const textureLoader = new THREE.TextureLoader();
// 圆点
const mapDot = textureLoader.load("assets/gradient.png");

初始化载体粒子体系:载体粒子体系的粒子数量要比所有模型的顶点数量的最大值还要大,这样才能保证切换到每一个模型,都不会出现缺失的情况,而多余的点呢就让他们从头开始重叠好了。当然不是越多越好,我的电脑是 MBP2018,20000 的时候就开始力不从心了,30000 直接无响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for (let i = 0; i < 15000; i++) {
const vertex = new THREE.Vector3();
vertex.x = 800 * Math.random() - 400;
vertex.y = 800 * Math.random() - 400;
vertex.z = 800 * Math.random() - 400;
geometry.vertices.push(vertex);
geometry.colors.push(new THREE.Color(255, 255, 255));
}
material = new THREE.PointsMaterial({
size: 4,
sizeAttenuation: true,
color: 0xffffff,
transparent: true,
opacity: 1,
map: mapDot,
});

material.vertexColors = THREE.VertexColors;
particles = new THREE.Points(geometry, material);
scene.add(particles);

将获取到的 3D 模型,通过 JSONLoader 加载后,得到的 geometry 对象放入一个数组 glist 中,用于模型切换。

加载模型 loadObject:

新版的threejsr98 → r99的时候废弃了JSONLoader,模型在网站也比较久了,ObjectLoader也不能加载,所以要么使用旧版的,要么单独下载JSONLoader

r98 → r99

  • WebGLRenderTarget.texture.generateMipmaps is now set to false by default.
  • There is a new (not backwards compatible) implementation for SSAOShader and SSAOPass.
  • JSONLoader has been removed from core. It is now located in examples/js/loaders/deprecated/LegacyJSONLoader.js.
  • Removed Geometry support from ObjectLoader. You have to include LegacyJSONLoader if you still want to load geometry data of type Geometry.
  • Removed Geometry support from SkinnedMesh. Use BufferGeometry instead.
  • Removed SkinnedMesh.initBones(). The SkinnedMesh constructor does not build the bone hierarchy anymore. You have to do this by yourself and then call SkinnedMesh.bind() in order to bind the prepared skeleton.

在这引入最新的three.min.jsLegacyJSONLoader

1
2
<script src="./js/three.min.js"></script>
<script src="./js/LegacyJSONLoader.js"></script>

加载模型 loadObject:

1
2
3
4
5
6
7
8
const loader = new THREE.LegacyJSONLoader();
loader.load("assets/qr.json", function (geo, materials) {
geo.center();
geo.normalize();

geo.scale(800, 800, 800);
glist.push(geo);
});

添加页面事件监听

  1. 按住鼠标拖动,可以旋转场景中的粒子体系。
  2. 滚动鼠标滚轮,切换模型。
  3. 手机上点击页面切换模型。
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
30
31
32
33
34
35
36
37
38
function onDocumentMouseMove(event) {
if (!canMouseMove) {
return false;
}
geometry.rotateY(((event.pageX - mouseX) / 2000) * 2 * Math.PI);
geometry.rotateX(((event.pageY - mouseY) / 2000) * 2 * Math.PI);
event.preventDefault();
mouseX = event.pageX;
mouseY = event.pageY;
}

function onDocumentMouseWheel(e) {
canMouseMove = false;
if (flag) {
return false;
}
e.deltaY > 0 ? objIndex++ : objIndex--;
if (objIndex > 4) {
objIndex = 0;
} else if (objIndex < 0) {
objIndex = 4;
}
tweenObj(objIndex);
flag = true;
}

function onDocumentTouchStart() {
canMouseMove = false;
if (flag) {
return false;
}
objIndex++;
if (objIndex > 4) {
objIndex = 0;
}
tweenObj(objIndex);
flag = true;
}

切换动画

使用的tweenjs

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
function tweenObj(index) {
let ani = null;
geometry.vertices.forEach(function (e, i, arr) {
ani = new TWEEN.Tween(e);
const length = glist[index].vertices.length;
const o = glist[index].vertices[i % length];
ani
.to(
{
x: o.x,
y: o.y,
z: o.z,
},
1000
)
.easing(TWEEN.Easing.Exponential.In)
.delay(400 * Math.random())
.start();
});
//动画完成时的回调
ani.onComplete(function (params) {
canMouseMove = true;
flag = false;
});
}

使用tween.delay(animationDuration*Math.random());是动画不那么生硬。

最后

渲染整个画面:

1
2
3
4
5
6
7
8
function render() {
around.rotateX(Math.PI / 1000);
TWEEN.update();
camera.lookAt(scene.position);
geometry.verticesNeedUpdate = true;
geometry.colorsNeedUpdate = true;
renderer.render(scene, camera);
}

TWEEN.update()geometry.verticesNeedUpdate = true共同决定了粒子体系切换动画可以展示出来。

GitHub 地址

总结

还有很多地方不完善:

  1. 切换时旋转画面会导致粒子位置计算错误,撕裂模型,暂时在切换时禁止了旋转事件。

  2. 雾化的位置和原版有很大差异,原版使用了三方的composer,这里用的是alteredq的一系列EffectComposer,包括过亮效果、暗角、电视效果等。

  3. KV的动画没有实现裙边的效果。

  4. 二维码的材质还是点,所以扫不出来,可以把材质的map设置为null即可是方形(没有纹理默认方形)。

    1
    2
    3
    4
    5
    new THREE.PointsMaterial({
    ...
    map: null, //texture
    ...
    });
  5. 流畅度方面,使用vertex shader性能可能更好,感觉好高深,等大神踩踩坑。

参考链接:

  1. threejs+tweenjs 实现 3D 粒子模型切换
  2. 3D 粒子效果在网页端实现分享
作者

刘念

发布于

2018-07-07

更新于

2023-11-22

许可协议

评论