龙年来临之际,使用 Threejs 创建一个祝福页面,祝愿你腾飞如龙,展翅翱翔在蔚蓝的天空中,创造出属于自己的传奇。愿大家的心境如龙般坚韧,充满着勇气和智慧,面对挑战时始终坚定不移。愿你生活如龙般澎湃,充满活力和激情,绽放出绚丽多彩的人生画卷。在新的一年里,愿你的每一步都是坚定向前,每一天都是收获满满,愿龙年带给你无尽的好运和美好!
基础场景搭建
参考 官方教程搭建一个基础的场景。
理解以下几个概念:
- 场景(scene)
- 相机(camera)
- 渲染器(renderer)
- 灯光 (Light)
- 网格对象(Mesh)
此阶段的代码在此查看:threejs-demo/demos/03-response.html
文字
生成字体文件
在 Threejs 场景中展示中文字体需要引入字体文件。
通常使用 Facetype.js (gero3.github.io) 将需要展示的中文生成 JSON 文件。
js
import { FontLoader } from 'three/addons/loaders/FontLoader.js'
let textMesh
new FontLoader().load('fontface.json', font => {
const textGeometry = new TextGeometry('龙年大吉', {
font: font,
size: 100,
height: 40, // 指定文本的厚度或高度,以像素为单位
curveSegments: 100, // 指定曲线的分段数,这会影响文本曲线的光滑程度。分段数越大,曲线越光滑
bevelEnabled: true, // 指定是否启用斜角(bevel),即是否给文本添加倒角效果
bevelThickness: 10, // 如果启用了斜角,这个参数指定斜角的厚度
bevelSize: 10, // 如果启用了斜角,这个参数指定斜角的大小
bevelOffset: 2, // 如果启用了斜角,这个参数指定斜角的偏移量。
bevelSegments: 10, // 如果启用了斜角,这个参数指定斜角的分段数,影响斜角的光滑程度。
})
const textMaterial = new THREE.MeshMatcapMaterial({ color: 0xf1fa8c })
textMesh = new THREE.Mesh(textGeometry, textMaterial)
scene.add(textMesh)
// 调整文字位置使其水平居中
textGeometry.computeBoundingBox()
const textWidth =
textGeometry.boundingBox.max.x - textGeometry.boundingBox.min.x
textMesh.position.x = -textWidth / 2
textMesh.position.z = -500
textMesh.position.y = 10
在此查看该阶段完整代码:threejs-demo/demos/04-font.html
给文字添加材质
添加完材质的文字有一种金光闪闪的效果,如上图展示的那样。
js
// 加载材质贴图
const textureLoader = new THREE.TextureLoader()
const matcaps = {
textMatcap1: textureLoader.load('./images/matcap_5.png'),
}
// 应用贴图
const textMaterial = new THREE.MeshMatcapMaterial(matcaps.textMatcap1,flatShading: true)
// 文字对象
const textGeometry = new TextGeometry(
'龙年大吉',
...options, // 省略配置项
)
// 创建网格对象
const textMesh = new THREE.Mesh(textGeometry, textMaterial)
scence.add(textMesh)
THREE.MeshMatcapMaterial
是 Three.js 中用于创建使用 Matcap 材质的网格对象的材质类型。Matcap 材质是一种特殊的材质类型,它使用预先渲染好的图像来模拟光照效果,使得网格对象看起来具有高度的表现力和细节。
-
flatShading:true 启用平面着色
-
当
flatShading
设置为true
时,会启用平面着色,这意味着渲染时会忽略顶点的法向量插值,而是使用每个面的法向量来计算光照。这样会导致渲染出的表面呈现出角度较大的硬边效果。 -
当
flatShading
设置为false
时,会启用光滑着色,这意味着在渲染过程中会使用顶点的法向量插值来计算光照,从而使得表面的光滑度更高。
使用 GASP 创建文字动画
GSAP(GreenSock Animation Platform)是一个强大的 JavaScript 动画库,用于创建高性能、流畅和交互式的 Web 动画。
使用 CDN 的方式引入 gsap.js
js
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
class Text {
... 省略部分代码
initAnimation() {
let tl = gsap.timeline({
delay: 1,
defaults: {
duration: this.initializeDelay,
ease: 'elastic.out(1, .75)',
stagger: 0.1,
},
onComplete: () => {
this.upDownFlip()
},
})
tl.from(this.meshesPosition, { z: 1000 }, 'start').from(
this.meshesRotation,
{ x: Math.PI * 2 },
'start'
)
}
// 上下翻转
upDownFlip() {
gsap
.timeline({
repeat: -1,
defaults: {
duration: 2,
ease: 'elastic.out(1, .75)',
stagger: 0.1,
},
})
.to(
this.meshesPosition,
{ y: this.meshesPosition[0].y - 30 },
'start'
)
.to(this.meshesRotation, { x: Math.PI * 2 }, 'start')
.to(this.meshesPosition, { y: this.meshesPosition[0].y }, 'end')
.to(this.meshesRotation, { x: Math.PI * 4 }, 'end')
}
}
... 省略部分代码
initAnimation()
函数:这是一个初始化动画的函数。它首先创建了一个时间轴(timeline),并设置了一些默认参数,包括延迟(delay)、默认动画持续时间(duration)、缓动函数(ease)和间隔时间(stagger)。在时间轴的 onComplete 回调中调用了 upDownFlip()
函数。然后,时间轴执行了两个动画:一个是改变物体的 z 轴位置,另一个是改变物体的 x 轴旋转角度。
这段代码的效果是初始化一些物体的位置和旋转角度,并在完成初始化后循环执行一个上下翻转的动画效果。
这里需要给每个文字都添加一个动画,因此这里创建一个生成文字的类,用于添加文字,动画,材质等。该阶段代码在此查看:threejs-demo/demos/05-font-array.html)
添加喜庆元素
在页面中添加一些喜庆元素
模型
GLTFLoader 能够加载包含在 GLTF 文件中的几何、材质、动画和场景等数据。
js
// 加载模型
function addDragon() {
// 创建GLTF加载器对象
const loader = new GLTFLoader()
loader.load('./models/cute_dragon/scene.gltf', function (gltf) {
scene = gltf.scene
const Sketchfab_model = gltf.scene.getObjectByName('Sketchfab_model')
console.log('Sketchfab_model: ', Sketchfab_model)
scene.add(Sketchfab_model)
// 场景动画
initAnimations(gltf.animations, Sketchfab_model)
// initHelper()
addText()
initLight()
})
}
加载模型本身动画,添加如下函数:
js
// 对象动画
function initAnimations(animations, obj) {
// 注册动画器
playerMixer = new THREE.AnimationMixer(obj)
// 提取动画数据
animations.forEach(clip => {
playerMixer.clipAction(clip).play() // 播放动画
})
}
function animate() {
if (playerMixer) {
playerMixer.update(0.01) // 更新动画
}
}
在render函数中引用动画:
js
function render(){
...
// 动画
animate()
...
}
THREE.AnimationMixer
是 Three.js 中用于管理和控制动画的对象。它允许你在 Three.js 中加载和播放动画,以及控制动画的播放状态、速度和时间。
- 创建 AnimationMixer: 通过创建
THREE.AnimationMixer
的实例来初始化一个动画混合器对象。 - 关联模型和动画: 通过调用
animationMixer.clipAction()
方法,你可以将加载的模型与动画剪辑(animation clip)关联起来。动画剪辑包含了动画的关键帧数据,用于描述模型在不同时间点的动作状态。 - 更新动画: 在每一帧渲染过程中,需要调用 AnimationMixer 的
update()
方法来更新动画状态。这个方法会根据当前时间更新动画的播放状态,包括动画的骨骼变换、透明度、材质变换等
效果如图:
在此查看阶段代码:threejs-demo/demos/08-add-animate.html
生成模型线稿
js
function addDragon() {
const loader = new GLTFLoader() // 创建GLTF加载器对象
loader.load('./models/cute_dragon/scene.gltf', function (gltf) {
... 省略部分代码
// 绘制全部线条
const lineGroup = new THREE.Group()
Sketchfab_model.traverse(mesh => {
changeModelMaterial(mesh, lineGroup)
})
// 场景添加线条
scene.add(lineGroup)
... 省略部分代码
})
}
使用 traverse()
遍历 Three.js 对象的子对象树。它接受一个回调函数作为参数,该回调函数将在遍历过程中对每个子对象都被调用一次。
changeModelMaterial
用于创建线条,函数接受两个参数:object
和 lineGroup
。object
是一个 Three.js 对象,代表要修改材质的模型或者模型的父级对象。lineGroup
是另一个 Three.js 对象,用于保存模型周围的线条对象。
js
function changeModelMaterial(object, lineGroup) {
const group = object
if (group.isObject3D) {
const lg = new THREE.Group()
lineGroup.add(lg)
lg.name = group.name + '_line'
group.traverse(mesh => {
if (mesh.isMesh) {
console.log('mesh: ', mesh.name)
const quaternion = new THREE.Quaternion()
const worldPos = new THREE.Vector3()
const worldScale = new THREE.Vector3()
// 获取四元数
mesh.getWorldQuaternion(quaternion)
// 获取位置信息
mesh.getWorldPosition(worldPos)
// 获取缩放比例
mesh.getWorldScale(worldScale)
// 9 左眼白
// 15 右眼白
// 11 左眼珠
//13 右眼珠
if (['Object_9', 'Object_15'].includes(mesh.name)) {
mesh.material = new THREE.MeshBasicMaterial({
color: new THREE.Color('#2cc9ff'),
transparent: true,
opacity: 0.5,
})
} else if (['Object_11', 'Object_13'].includes(mesh.name)) {
mesh.material = new THREE.MeshBasicMaterial({
color: new THREE.Color('#cd1e33'),
transparent: true,
opacity: 0.6,
})
} else {
mesh.material = meshMaterial
}
// 以模型顶点信息创建线条
const line = getLine(mesh, Math.PI * 6.137, undefined, 1)
const name = mesh.name + '_line'
line.name = name
// 给线段赋予模型相同的坐标信息
line.quaternion.copy(quaternion)
line.position.copy(worldPos)
line.scale.copy(worldScale)
lg.add(line)
}
})
}
}
根据子对象的名称选择不同的材质:
- 如果名称为 'Object_9' 或 'Object_15',(眼白),则将材质设置为半透明的蓝色。
- 如果名称为 'Object_11' 或 'Object_13',(眼珠),则将材质设置为半透明的红色。
- 否则,将材质设置为另一个变量
meshMaterial
所定义的材质。
getLine()
函数的作用是根据模型对象的边缘信息,创建一个线条对象,并根据传入的参数设置线条的颜色和不透明度。
效果如图:
阶段代码:threejs-demo/demos/09-add-line.html
发光效果
在此查看阶段代码:threejs-demo/demos/10-add-bling.html
发光效果部分是通过创建多个渲染通道实现的。
查看 unreal
函数:
- 首先,通过
RenderPass
类创建一个渲染场景的通道renderScene
,该通道用于将场景渲染到屏幕上。 - 然后,通过
UnrealBloomPass
类创建一个虚幻发光效果的通道bloomPass
,该通道用于添加虚幻的发光效果到场景中。这里设置了虚幻发光效果的参数,包括大小、强度和半径。 - 使用
EffectComposer
创建一个合成器bloomComposer
,用于将不同的渲染通道结合在一起。将renderScene
和bloomPass
添加到合成器中。 - 通过
ShaderPass
类创建一个着色器通道mixPass
,用于自定义着色器效果。在这里,定义了一个基本的着色器,用于将基础纹理和虚幻发光纹理叠加在一起,从而产生最终的渲染效果。 - 使用
OutputPass
创建一个输出通道outputPass
,用于将最终的渲染结果输出到屏幕上 - 使用
EffectComposer
创建一个最终的合成器finalComposer
,并将renderScene
、mixPass
和outputPass
添加到合成器中,以生成最终的渲染效果。
效果如图:
至此发光小金龙的场景就实现完成了。
总结
本文主要包含的知识点有:
- 加载模型
GLTFLoader
- 遍历操作模型
traverse
- 渲染通道
RenderPass
,UnrealBloomPass
- 合成器
EffectComposer
- 作色器
ShaderPass
- 文字创建和修饰的
FontLoader
和TextGeometry
- 文字的材质
MeshMatcapMaterial
- 使用
Gsap
创建动画
祝大家龙年大吉。
参考链接
- 动画库 Let's get animating! | GSAP | Docs & Learning
- 模型下载 Sketchfab
- 掘金
- 3d/src/containers/Fans/index.js at master · dragonir/3d (github.com)
- 字体转换 Facetype.js
- 材质 - three.js manual (threejs.org)
- 在线免费的录屏和录屏转gif工具 (mnggiflab.com)
- 贴图模型库 Poly Haven
- AI 生成360环境贴图 Blockade Labs Skybox - AI-Generated 3D Worlds
- dragonir 的个人主页 - 文章 - 掘金 (juejin.cn)