// 硬编码点数组
var points = [
[0, 2],
[2, 10],
[-1, 15],
[-3, 20],
[0, 25]
];
// 将点转为顶点
for (var i = 0; i < points.length; i++) {
var x = points[i][0];
var y = 0;
var z = points[i][10];
points[i] = new THREE.Vector3(x, y, z);
}
// 根据顶点创建路径
var path = new THREE.CatmullRomCurve3(points);
在得到路径后,我们就能基于它创建管道了。
// 基于路径创建管道几何体
// 第一个参数是路径
// 第二个参数是组成管道的片段(segment)数量
// 第三个参数是管道半径
// 第四个参数是沿半径的片段数
// 第五个参数是指定管道是否闭合
var geometry = new THREE.TubeGeometry( path, 64, 2, 8, false );
// 红色基础材质(译者注:不受光影响)
var material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
// 创建网格(mesh)
var tube = new THREE.Mesh( geometry, material );
// 将管道网格添加至场景中
scene.add( tube );
In this one, I'm playing with the Y position of the points to generate my path. That way the tube is not only on one plane, but in three dimensions.
在该案例中,路径的生成加入了 Y 坐标(即不再为 0)。这使得管道不仅是在一个平面上,而是在三维空间上穿梭了。
var frames = path.computeFrenetFrames(segments, true);
// true 表明是否让路径闭合,在该案例中需要路径闭合
该函数返回的是三个由 Vector3() 组成的数组。
现在我们拥有每个片段所需的所有信息,能开始沿着每个片段生成粒子了。
我们将每个粒子以 Vector3() 形式存储在 Geometry() 中,以便后续复用。
// 创建一个用于插入粒子的空几何体
var geometry = new THREE.Geometry();
现在需要为每块片段放置粒子。这就是通过循环遍历所有片段的原因。
我不打算在这里阐述该函数是如何工作的,查看下面代码,你会发现所有细节都在注释中!⬇
// 循环遍历所有片段
for (var i = 0; i < segments; i++) {
// 从佛莱纳标架获取片段的主法向量
var normal = frames.normals[i];
// 从佛莱纳标架获取片段的副法向量
var binormal = frames.binormals[i];
// 计算片段的索引(0 至 1)
var index = i / segments;
// 获取片段中心点的坐标
// 在第一章节用于沿路径移动摄像机的函数
var p = path.getPointAt(index);
// Loop for the amount of particles we want along each circle
// 循环每个圆所需的粒子数量
for (var j = 0; j < circlesDetail; j++) {
// 复制圆心的位置
var position = p.clone();
// 需要将每个点基于角度 0 至 Pi*2 进行定位
// 如果只想要半个管道(如水滑梯),你可以从 0 至 Pi。
var angle = (j / circlesDetail) * Math.PI * 2;
// 计算当前角度的 sine 值
var sin = Math.sin(angle);
//计算当前角度的负 cosine 值
var cos = -Math.cos(angle);
// 根据每个点的角度、片段的主法向量与副法向量,计算出每个点的法向量
var normalPoint = new THREE.Vector3(0,0,0);
normalPoint.x = (cos * normal.x + sin * binormal.x);
normalPoint.y = (cos * normal.y + sin * binormal.y);
normalPoint.z = (cos * normal.z + sin * binormal.z);
// 法向量乘以半径长度,因为我们所需的管道的半径不是 1。
normalPoint.multiplyScalar(radius);
// 圆心位置与法向量相加
position.add(normalPoint);
// 将该向量放到几何体中
geometry.vertices.push(position);
}
}
<p data-height="265" data-theme-id="0" data-slug-hash="EmaReQ" data-default-tab="result" data-user="Mamboleoo" data-embed-version="2" data-pen-title="Crazy 6" class="codepen">See the Pen <a href="https://codepen.io/Mamboleoo/pen/EmaReQ/">Crazy 6</a> by Louis Hoebregts (<a href="https://codepen.io/Mamboleoo">@Mamboleoo</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://production-assets.codepen.io/assets/embed/ei.js"></script>
对于该案例,我将每个圆的顶点连接起来。而且调整了顶点的角度及其颜色以创造出旋转的幻觉。
for (var i = 0; i < tubeDetail; i++) {
// 为每个圆创建新的几何体
var circle = new THREE.Geometry();
for (var j = 0; j < circlesDetail; j++) {
// 将点添加到圆的顶点数组
circle.vertices.push(position);
}
// 再次将第一个点的添加到圆的顶点数组,以保证圆是闭合的
circle.vertices.push(circle.vertices[0]);
// 以自定义颜色创建新材质
var material = new THREE.LineBasicMaterial({
color: new THREE.Color("hsl("+(noise.simplex2(index*10,0)*60 + 300)+",50%,50%)")
});
// 创建线(Line)对象
var line = new THREE.Line(circle, material);
// 将其添加到场景中
scene.add(line);
原文链接:
译者注:本文由两章节组成。
隧道动画(第一章节)
嘿,读者们! 👋
如果说哪样东西是我的真爱,那么它应该就是隧道动画 😍
不懂我的意思?那看看我之前创建的案例吧:
我甚至使用这种动画作为我们团队的 2017 愿望卡 🎆。
我将在本文阐述上述案例的基本实现。其中,第一步是创建一个隧道(管道),并在其内部执行摄像机动画。接下来,我们看看如何自定义隧道。
对于该案例,我会使用 Three.js 实现 WebGL 部分。如果对此不熟悉,你可以看看 Rachel Smith 的 相关文章。
目录
1. 建立场景
第一步,搭建用于初始化 Three.js 场景的基本要素。
别忘了在页面中引入 Three.js 库
See the Pen Setup the scene by Louis Hoebregts (@Mamboleoo) on CodePen.
如果你能看到一个红色立方体在旋转,那么这就意味着我们可以继续往下走啦 📦 !
2. 创建一个管道几何体
想要在 Three.js 中创建管道,首先需要创建一条路径(path)。
THREE.CatmullRomCurve3()
构建函数能实现这个需求,它能基于一个顶点数组创建一条平滑的曲线。在案例中,我硬编码了一个点(point)数组,并将每个点转为
Vector3()
。当拥有了顶点数组后,就能使用上述构造函数创建路径。
在得到路径后,我们就能基于它创建管道了。
See the Pen Create a tube geometry by Louis Hoebregts (@Mamboleoo) on CodePen.
这时应该能看到一个红色管道在场景中旋转 😊。
3. 基于 SVG 多边形创建一个管道
在大多数情况下,我们并不希望硬编码路径上的点。其实我们可以通过一些随机算法生成一系列点。但对于下一个案例,我们将从由 Adobe Illustrator 创建的 SVG 上获取点的值。
如果不在路径上设置任何贝塞尔曲线,Illustrator 会将路径作为多边形导出,如:
我们手动地将多边形的点转化为一个数组:
如果感兴趣,你可以创建一个动态将 SVG 字符串转为数组的函数 😉
See the Pen Create a tube from a SVG polygon by Louis Hoebregts (@Mamboleoo) on CodePen.
你也可以看到左侧是 SVG 多边形,右侧则是根据多边形的点创建的管道。
4. 摄像机在管道内移动
有了管道后,剩余的主要部分就是动画了!
我们会使用 path 非常有用的函数
path.getPointAt(t)
。该函数会基于所指定的参数值,返回路径上的任意一点。参数的取值范围为 0 至 1。0 是路径的第一个点,1 是路径的最后一个点。
我们会在每帧中调用此函数,以让摄像机沿着路径移动。另外,我们还需要在每帧中增大该函数的参数值以获得意想不到的效果。
由于
.getPointAt()
函数只接受[0, 1]
区间内的值,我们需要对该值进行“取余”运算,以确保它不会大于 1。目前摄像机的位置设定没有任何问题,但摄像机的朝向始终不变。为了解决该问题,我们需要将摄像机的朝向设置在比自身位置更前一点的地方。因此,在每帧中,我们不仅需要计算摄像机的自身位置,还要计算比该位置更靠前的朝向坐标。
另外,材质还提供了其他可选参数。目前,管道的材质是
MeshBasicMaterial
,但其只渲染了管道外层(译者注:默认情况下),而摄像机是置于管道内的,因此只需要渲染管道材质背面即可。另外,由于我们并未在场景中添加任何光源,将材质设置为线框(wireframe)时,才能清楚看到具体效果。瞧,现在摄像机在管道内动起来了!🎉
See the Pen Move the camera inside the tube by Louis Hoebregts (@Mamboleoo) on CodePen.
5. 添加光源
我并不打算在本文详细讲解光源,但会告诉你如何在管道中设置基础的光源效果。
其原理与摄像机的运动相同。我们将光源放置在摄像机的朝向位置。
这就是具体效果啦!
See the Pen Add a light by Louis Hoebregts (@Mamboleoo) on CodePen.
6. 疯狂起来吧
基于最后一步(译者注:即上一步),我通过一些参数的变换,创建出新的动画类型。若感兴趣可以查看源代码。 😉
对于该案例,我为每个面设置了不同颜色。这样就得到了一个有趣的马赛克效果啦。
See the Pen Crazy 1 by Louis Hoebregts (@Mamboleoo) on CodePen.
In this one, I'm playing with the Y position of the points to generate my path. That way the tube is not only on one plane, but in three dimensions. 在该案例中,路径的生成加入了 Y 坐标(即不再为 0)。这使得管道不仅是在一个平面上,而是在三维空间上穿梭了。
See the Pen Crazy 2 by Louis Hoebregts (@Mamboleoo) on CodePen.
对于最后一个案例,我创建了 5 个半径和颜色均不相同的隧道。而为了得到更佳的显示效果,它们均被设置了不同的透明度。
See the Pen Crazy 3 by Louis Hoebregts (@Mamboleoo) on CodePen.
这就是第一章节的结尾了。而在下一章节,我将阐述如何在不使用
TubeGeometry
的情况下创建粒子管道。若想一次性体验上述所有案例,你可以看看该 集合。
我希望你能从中学习到一些东西!如果遇到任何问题,请毫不犹豫地通过 Twitter 告诉我。
隧道动画(第二章节)
正如我在上一章节的结尾说道,我们将看到如何在不使用
TubeGeometry()
的情况下创建粒子管道。你最终能创建这种类型的动画
目录
1. 计算粒子的位置
为了达到想要的效果,我们需要沿着路径生成粒子。其实,Three.js 也是使用相同的方式生成管道几何体,其中不同的一点是:它加入了面(face),以形成一个常见的管道。
首先明确的细节是管道的构建元素及其半径。
这些细节由两个值设定:
现在我们或许已经知道粒子的总数了(剧透:segments * circleDetails),而此刻则需要我们计算
佛莱纳标架(Frenet frame)
。我并不是这个领域的专家,但需要我们明白的是,佛莱纳标架(Frenet frame)是为管道所有片段(segments)计算出来的值。而每个值则由单位切向量、主法向量和副法向量组成。换句话说,这些值指定了每个片段的旋转角度及其朝向。
如果你想了解更多关于佛莱纳标架的计算过程,可以看看这篇文章。
多亏了 Three.js,我们不必理解上述任何知识即可让我们继续进行下去。使用 path 的内置函数即可:
该函数返回的是三个由
Vector3()
组成的数组。现在我们拥有每个片段所需的所有信息,能开始沿着每个片段生成粒子了。
我们将每个粒子以
Vector3()
形式存储在Geometry()
中,以便后续复用。现在需要为每块片段放置粒子。这就是通过循环遍历所有片段的原因。
我不打算在这里阐述该函数是如何工作的,查看下面代码,你会发现所有细节都在注释中!⬇
唷,这段代码并不是那么容易理解。我阅读了 Three.js 的源码后才将其编写出来。
通过下面这个案例,你可以看到粒子是如何逐个计算出来的。
(如果管道已完全显示,请点击 “Rerun”)
See the Pen Calculate the positions of the particles by Louis Hoebregts (@Mamboleoo) on CodePen.
2. 创建管道
现在已经拥有填充顶点后的几何体对象了。通过 Three.js 的
Points
构造函数,你能创建精妙的粒子案例。而且对于简单的点,其渲染性能优异。可以通过纹理或不同的颜色自定义点的样式。与创建
网格(Mesh)
的方式相同,我们需要两个元素才能创建Points
对象,这两者是材质(material)与几何体(geometry)。因为在第一步中几何体已被创建,所以这里需要定义的是材质。最后,创建点对象(Points),并将其添加至场景中:
See the Pen Create the tube by Louis Hoebregts (@Mamboleoo) on CodePen.
3. 动起来
为了让所有东西动起来,我们将复用前面动画案例的代码。
🎉 欢呼吧,我们有一个由粒子构成的基础隧道了 🎉
See the Pen Moving particle tunnel by Louis Hoebregts (@Mamboleoo) on CodePen.
4. 疯狂起来吧
经过第二章节的学习后,现在的你可以接触到无数个不同的隧道了!可以在下面三个案例中体验到基于上述知识构建的自定义隧道。
多彩隧道
在该案例中,我为每个点应用了自定义颜色。另外,也在场景中添加了雾化效果,让隧道产生渐隐效果。
See the Pen Crazy 4 by Louis Hoebregts (@Mamboleoo) on CodePen.
方格洞穴(Squared cave)
See the Pen Crazy 5 by Louis Hoebregts (@Mamboleoo) on CodePen.
该隧道仅由立方体组成。我在每个顶点的位置创建新的网格(Mesh),以替换
Points
对象。另外,颜色是基于柏林噪声算法生成。八边形隧道
对于该案例,我将每个圆的顶点连接起来。而且调整了顶点的角度及其颜色以创造出旋转的幻觉。
感谢你阅读我这篇关于隧道动画的文章!
若你想一次性体验上述所有案例(第二章节),你可以看看该 集合。
希望你会对这篇文章感兴趣!如果你创造出了漂亮的隧道,请与我分享 --> Twitter。如果遇到任何问题,请毫不犹豫地告诉我。 😉