正题
本篇文章大概花费你10分钟,读完本篇文章你可以学到下面几点,可以挑自己感兴趣的点进行阅读
- three.js 中加载圣诞树模型
- 实现自定义曲线路径动画
- three.js 中如何 实现粒子动画
- three.js 音频导入 和📷 动画
我们先看下圣诞动画实现的效果:
圣诞动画
基本场景的搭建
首先圣诞树需要一个东西去承载, 也就是所谓的地面, 地面的其实使用的 「three.planeGeometry」, 这个api是用也是很简单定义平面的宽度 和长度, 所以你只需要的你的场景足够大, 也就说你平面的宽度和长度 足够大,然后你就可以看着制作出一个平面。代码如下:
const plane = new THREE.PlaneGeometry(1000, 1000) const material = new THREE.MeshPhongMaterial({ map: new THREE.TextureLoader().load('./edge.jpg'), })
是的就这么简单的一行代码,然后配上材质, 我们看下效果:
ground
这里有同学问了这下面这两个问题??
- 飞哥 为啥看不到材质哇
- 为啥这个地面是紫色的哇
我们明明设置了材质,为啥看不到材质呢????
我给大家看下材质的图片 :
material
其实贴图的大小就这么大,然后你要贴在这么大的平面上肯定啥也看不到哇, 就好比如下这个场景:
show
紫色的就表示地面, 灰色的就是地板,这就现在的情况, 这种情况怎么解决呢, 有同学说选一个足够大的图片,可以是可以,但是你看着不怪嘛, 另一个比较聪明的同学,可以不可以有浏览器中图片 「repeat」 属性呢??是的材质当然是支持的
其实你创建的texture 可以用来操作横向纹理 和纵向 纹理的铺法
分别对应下面这两个属性
.wrapS
这个值定义了纹理贴图在水平方向上将如何包裹,在UV映射中对应于「U」。默认值是THREE.ClampToEdgeWrapping,即纹理边缘将被推到外部边缘的纹素。其它的两个选项分别是THREE.RepeatWrapping和THREE.MirroredRepeatWrapping。请参阅texture constants来了解详细信息。
.wrapT : number
这个值定义了纹理贴图在垂直方向上将如何包裹,在UV映射中对应于「V」。可以使用与 .wrapS : number相同的选项。
「请注意:纹理中图像的平铺,仅有当图像大小(以像素为单位)为2的幂(2、4、8、16、32、64、128、256、512、1024、2048、……)时才起作用。宽度、高度无需相等,但每个维度的长度必须都是2的幂。这是WebGL中的限制,不是由three.js所限制的」
我们设置下横向平铺 和纵向平铺 为重复是不是就可以实现重叠了,
代码如下:
if (material.map) { material.map.wrapS = THREE.RepeatWrapping material.map.wrapT = THREE.RepeatWrapping material.map.repeat.x = 30 }
然后实现上面的有很多瓷砖的效果 , 我们来解释下第二个问题 贴图是白色的 , 为什么地板 看着是紫色的, 这是为啥呢 ,
答案就是 这个材质 THREE.MeshPhongMaterial 他其实是受光照影响的,
场景中其实有两个光源:
- 一个环境光
- 一个圣诞树下的点光源
没有环境光
没有环境光,贴图的颜色也是很黑暗,所以在three.js 中环境光 是十分重要的。
雪花粒子动画
其实无论是什么粒子,你看完这一种实现 其他的粒子你可以随便拿捏。首先还是分析,粒子是啥也就是现在场景中的雪花, 其实一个三角形有 3个顶点 ,对应的矩形 就是 4个顶点, 如果一个3d 图形,有1000个甚至 有很多个顶点组成。然后我们在对每个顶点去做贴图,其实就可以生成一个有许多顶点的图形 的立方体呢 ,这就是 3d实现粒子的原理了 很简单。
所以雪花粒子,就是搭配顶点材质, 其实就是这样的一个材质 「PointsMaterial」,和上面的高光材质一样使用
我们对每个粒子的位置去做随机性,是不是就可以制造出所谓的效果了。直接上代码:
const geo = new THREE.BufferGeometry() // 设置1000个顶点 const vertices = [] for (let i = 0; i < 10000; i++) { const x = Math.random() * 2000 - 1000 const y = Math.random() * 2000 - 1000 const z = Math.random() * 2000 - 1000 vertices.push(x, y, z) } geo.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)) // 加载雪花贴图 const texture = new THREE.TextureLoader().load( './snow.png', ) const material = new THREE.PointsMaterial({ size: 20, map: texture, blending: THREE.AdditiveBlending, depthTest: false, transparent: true, }) material.color.setHSL(0.9, 0.05, 0.5) const particles = new THREE.Points(geo, material)
这里要注意的事,不是生成mesh ,而是使用 THREE.Points, 或者说这两个有什么不同的呢 ???底层的webgl 渲染方式不一样xdm
- 「points一个用于显示点的类。由WebGLRenderer渲染的点使用 gl.POINTS。」
- 「mesh 表示基于以三角形为polygon mesh(多边形网格 他是以面去渲染的」
因为顶点的大小是 固定的,如果想看粒子的大小,你可以改材质的size 就可以实现了。
场景默认大小是20 ,我们改成50 的你可以看下效果 :
50的大小
有点丑呵呵哈哈哈!!!!!
至于动画 就很简单的, 一句话概括 ,整体动, 我们只需要对这个particles整体 去做动画就好了,自然所有粒子就开始动了。
if (this.particle) { this.particle.rotation.y = time }
3d路径环绕动画
我们一步一步拆解, 首先你得有3d 路径 对吧, 这里的使用的Three.js 的api
使用一个点的列表然后你就可以得到一个 闭合的3D曲线 然后 在将这个3d 曲线分成 n个点, 最后将这n个点, 生成一个geometry,
其实一条线, 然后three.js 去创建闭合的曲线。
this.curve = new THREE.CatmullRomCurve3( this.curveHandles.map((handle) => handle.position), true, 'centripetal', ) const points = this.curve.getPoints(50) const line = new THREE.LineLoop( new THREE.BufferGeometry().setFromPoints(points), new THREE.LineBasicMaterial({ visible: false, // linecap: 'round', //ignored by WebGLRenderer // linejoin: 'round' }), )
THREE.CatmullRomCurve3:简单介绍下使用Catmull-Rom算法, 从一系列的点创建一条平滑的三维样条曲线。然后three.js 封装好了插值方法,可以将这条曲线拆分成很多个点对吧, 线有了, 这时候我们去场景中加载文字立方体,不清楚的同学 看下 这篇文章
「浅谈3d文字」
图片
默认文字立方体是这样的,如果让他的朝向 和曲线的方向一致呢,其实这里用到了three.js 另一个工具类, flow 其实你只要传给mesh 和路线给他,然后设置他就自动完成路径匹配了,代码如下:
this.flow = new InstancedFlow(1, undefined, geometry, material); this.flow.updateCurve(0, this.curve) this.scene.add(this.flow.object3D) this.flow.setCurve(0, 0);
路径弯曲
其实在3d 中有种很难的 就是吸附这种其实特别像圆柱面的侧面 ,你仔细看下。接下里有了方法,然后怎么去运动呢 对吧 , 这个也不用担心,这个工具类已经帮我们封装好了。
if (this.flow) { this.flow.moveAlongCurve(0.002) }
沿着自定义曲线每秒移动多少,然后到终点自动回头。这个方法是要 requestAnimation里面哦。
模型导入和音乐加载
模型我也是找了很多网站, 最后还是free3d.com 这个里面找到了很多免费的模型,其实模型的导入无非就是three.js 有对应的loader 去加载,然后你根文件的类型去找到对应的loader, 我这里的圣诞树的话,其实obj loader , mtl loader这个是加载材质信息的, 因为圣诞树上材质,
const obj = new OBJLoader() //obj加载器 const mtl = new MTLLoader() //材质文件加载器 mtl.load('./tree/12150_Christmas_Tree_V2_L2.mtl', (materials) => { // 返回一个包含材质的对象MaterialCreator //obj的模型会和MaterialCreator包含的材质对应起来 obj.setMaterials(materials) obj.load('./tree/12150_Christmas_Tree_V2_L2.obj', (tree) => { //tree.receiveShadow = true; tree.castShadow = true this.tree = tree this.tree.scale.set(0.4, 0.4, 0.4) //放大obj组对象 // 先绕Y轴旋转 在绕 x 轴旋转 const matrix = new THREE.Matrix4().makeRotationY(Math.PI / 2).makeRotationX(-Math.PI / 2) this.tree.rotation.setFromRotationMatrix(matrix) this.tree.position.set(0, -5, 0) this.scene.add(this.tree) //返回的组对象插入场景中 }) })
如果你在模型导入的时候时常找不到模型, 你可以先将模型先放大,后面在做位置微调就好了。
音乐的加载three.js 中有对应的音乐加载器,然后将音乐绑在相机上,会有声音变化的效果,代码如下
addMusic() { const listener = new THREE.AudioListener(); this.camera.add(listener); // 创建一个非位置音频对象 用来控制播放 const audio = new THREE.Audio(listener); // 创建一个音频加载器对象 const audioLoader = new THREE.AudioLoader(); // 加载音频文件,返回一个音频缓冲区对象作为回调函数参数 audioLoader.load('./music.mp3', function (AudioBuffer) { // console.log(AudioBuffer) // 音频缓冲区对象关联到音频对象audio audio.setBuffer(AudioBuffer); audio.setLoop(false); //是否循环 audio.setVolume(1); //音量 // 播放缓冲区中的音频数据 audio.pause() audio.play(); //play播放、stop停止、pause暂停 }); }
最后我在讲下这运镜的动画是怎么实现的
相机动画
看镜头不断从远到近,不断地反复轮回,其实核心就一个字 改变相机的位置,但是你如果一直加 整个场景的东西 就会看不见了,
所以现在的需求固定相机在某个位置,然后到某一个位置后就返回。如图:
其实也就是在A点和B 点 相机来回摆动,形成了错觉, 整个场景在动的感觉,其实这里用到了正余弦函数:我们看下两个的图像
图像
为啥这两个 这么符合 无论 是哪一个函数 他们的取值范围永远是 【-1,1】 而且同时也有单调性, 不像线性渐变辣么突兀,所以对相机运用这个动画非常适合,大家以后如果有这种场景,可以使用这个公式
y = default + K * cos / sin(time)
default默认是 一开始大小, K 其实就是 最终的位置 减去默认的大小 就可以了,这样就不用担心了 哈哈哈哈!!
代码如下:
this.camera.position.x = Math.sin(time * 0.0005) * 150 this.camera.position.z = 100 + Math.cos(time * 0.0005) * 150 this.camera.position.y = 30 + Math.cos(time * 0.0005) * 30