likes
comments
collection
share

Threejs-小金龙,龙年大吉

作者站长头像
站长
· 阅读数 7

龙年来临之际,使用 Threejs 创建一个祝福页面,祝愿你腾飞如龙,展翅翱翔在蔚蓝的天空中,创造出属于自己的传奇。愿大家的心境如龙般坚韧,充满着勇气和智慧,面对挑战时始终坚定不移。愿你生活如龙般澎湃,充满活力和激情,绽放出绚丽多彩的人生画卷。在新的一年里,愿你的每一步都是坚定向前,每一天都是收获满满,愿龙年带给你无尽的好运和美好!

Threejs-小金龙,龙年大吉

基础场景搭建

参考 官方教程搭建一个基础的场景。

理解以下几个概念:

  • 场景(scene)
  • 相机(camera)
  • 渲染器(renderer)
  • 灯光 (Light)
  • 网格对象(Mesh)

此阶段的代码在此查看:threejs-demo/demos/03-response.html

文字

生成字体文件

Threejs-小金龙,龙年大吉

在 Threejs 场景中展示中文字体需要引入字体文件。

通常使用 Facetype.js (gero3.github.io) 将需要展示的中文生成 JSON 文件。

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

给文字添加材质

添加完材质的文字有一种金光闪闪的效果,如上图展示的那样。

// 加载材质贴图
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

<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)

添加喜庆元素

在页面中添加一些喜庆元素 Threejs-小金龙,龙年大吉

模型

GLTFLoader 能够加载包含在 GLTF 文件中的几何、材质、动画和场景等数据。

  // 加载模型
  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()
    })
  }

加载模型本身动画,添加如下函数:

// 对象动画
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函数中引用动画:

function render(){
    ...
    // 动画
    animate()
    ...
}

THREE.AnimationMixer 是 Three.js 中用于管理和控制动画的对象。它允许你在 Three.js 中加载和播放动画,以及控制动画的播放状态、速度和时间。

  1. 创建 AnimationMixer: 通过创建 THREE.AnimationMixer 的实例来初始化一个动画混合器对象。
  2. 关联模型和动画: 通过调用 animationMixer.clipAction() 方法,你可以将加载的模型与动画剪辑(animation clip)关联起来。动画剪辑包含了动画的关键帧数据,用于描述模型在不同时间点的动作状态。
  3. 更新动画: 在每一帧渲染过程中,需要调用 AnimationMixer 的 update() 方法来更新动画状态。这个方法会根据当前时间更新动画的播放状态,包括动画的骨骼变换、透明度、材质变换等

效果如图:

Threejs-小金龙,龙年大吉

在此查看阶段代码:threejs-demo/demos/08-add-animate.html

生成模型线稿

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 用于创建线条,函数接受两个参数:objectlineGroupobject 是一个 Three.js 对象,代表要修改材质的模型或者模型的父级对象。lineGroup 是另一个 Three.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-小金龙,龙年大吉

阶段代码:threejs-demo/demos/09-add-line.html

发光效果

在此查看阶段代码:threejs-demo/demos/10-add-bling.html

发光效果部分是通过创建多个渲染通道实现的。

查看 unreal 函数:

  • 首先,通过 RenderPass 类创建一个渲染场景的通道 renderScene,该通道用于将场景渲染到屏幕上。
  • 然后,通过 UnrealBloomPass 类创建一个虚幻发光效果的通道 bloomPass,该通道用于添加虚幻的发光效果到场景中。这里设置了虚幻发光效果的参数,包括大小、强度和半径。
  • 使用 EffectComposer 创建一个合成器 bloomComposer,用于将不同的渲染通道结合在一起。将 renderScenebloomPass 添加到合成器中。
  • 通过 ShaderPass 类创建一个着色器通道 mixPass,用于自定义着色器效果。在这里,定义了一个基本的着色器,用于将基础纹理和虚幻发光纹理叠加在一起,从而产生最终的渲染效果。
  • 使用 OutputPass 创建一个输出通道 outputPass,用于将最终的渲染结果输出到屏幕上
  • 使用 EffectComposer 创建一个最终的合成器 finalComposer,并将 renderScenemixPassoutputPass 添加到合成器中,以生成最终的渲染效果。

效果如图:

Threejs-小金龙,龙年大吉

至此发光小金龙的场景就实现完成了。

总结

本文主要包含的知识点有:

  • 加载模型 GLTFLoader
  • 遍历操作模型 traverse
  • 渲染通道 RenderPass,UnrealBloomPass
  • 合成器 EffectComposer
  • 作色器 ShaderPass
  • 文字创建和修饰的 FontLoader 和 TextGeometry
  • 文字的材质 MeshMatcapMaterial
  • 使用 Gsap 创建动画

祝大家龙年大吉。

参考链接