百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

Theatre.js动画制作简明教程【three.js】

bigegpt 2024-08-10 12:16 9 浏览

在这个教程中,我们将介绍 Theatre.js 的基础知识并探索如何制作令人惊叹的动画序列。 我们将演示如何为 Three.js 立方体制作动画、集成引人注目的视觉效果、修改颜色、试验 HTML 元素以及以特定时间间隔将动画与声音播放同步。

推荐:用 NSDT编辑器 快速搭建可编程3D场景

1、Theatre.js安装和设置

首先,我们需要一个带有 Three.js 的入门模板和一个基本场景。 Theater.js 有两个基本包:

  • @theatre/studio 是我们用来创建动画的编辑器 GUI
  • @theatre/core 播放我们创建的动画。

我们可以像这样添加 Theater.js 包:

# with npm
npm install --save @theatre/core @theatre/studio
# with yarn
yarn add @theatre/core @theatre/studio

或者下载这个入门模板,其中包含所有必需的依赖项,然后运行以下命令:

# Install the dependencies 
yarn install

# Start the server
yarn run dev.

2、创建立方体和地板

入门模板为我们提供了一个简单的立方体、地板、一些照明和轨道控件。

// Cube
  const geometry = new THREE.BoxGeometry(10, 10, 10);
  const material = new THREE.MeshPhongMaterial({ color: 0x049ef4 });
  const box = new THREE.Mesh(geometry, material);
  box.castShadow = true;
  box.receiveShadow = true;
  scene.add(box);

// Floor
  const floorGeometry = new THREE.CylinderGeometry(30, 30, 300, 30);
  const floorMaterial = new THREE.MeshPhongMaterial({ color: 0xf0f0f0 });
  const floor = new THREE.Mesh(floorGeometry, floorMaterial);
  floor.position.set(0, -150, 0);
  floor.receiveShadow = true;
  scene.add(floor);

// Lights
  const ambLight = new THREE.AmbientLight(0xfefefe, 0.2);
  const dirLight = new THREE.DirectionalLight(0xfefefe, 1);
  dirLight.castShadow = true;
  dirLight.shadow.mapSize.width = 1024;
  dirLight.shadow.mapSize.height = 1024;
  dirLight.shadow.camera.far = 100;
  dirLight.shadow.camera.near = 1;
  dirLight.shadow.camera.top = 40;
  dirLight.shadow.camera.right = 40;
  dirLight.shadow.camera.bottom = -40;
  dirLight.shadow.camera.left = -40;
  dirLight.position.set(20, 30, 20);
  scene.add(ambLight, dirLight);

// OrbitControls
  controls = new OrbitControls(camera, renderer.domElement);
  controls.enableZoom = true;
  controls.enableDamping = true;
  controls.autoRotate = false;
  controls.dampingFactor = 0.1;
  controls.minDistance = 2.4;
  controls.target.set(0, 20, 0);

3、导入 Theater 并创建项目

我们需要从 @theatre/core 导入 { getProject, types }。 完成后,我们还需要从 @theatre/studio 导入并初始化 studio。

Theatre.js 中的项目就像一个保存的文件。 项目存储在 localStorage 中,因此如果关闭并重新打开浏览器,你不会丢失进度。 创建一个新的剧院项目并为其命名。
然后我们创建一个新工作表。 工作表包含所有可以设置动画的对象。

import { getProject, types } from '@theatre/core';
import studio from '@theatre/studio';
studio.initialize();

// Create a project for the animation
const project = getProject('TheatreTutorial_1');

// Create a sheet
const sheet = project.sheet('AnimationScene');

4、物体和属性

每个需要动画的对象都有一个相应的 Theater Sheet 对象。 这些工作表对象包含属性(Props),可以对它们进行动画处理以在场景中创建运动和其他动态效果。

让我们创建一个新的 boxObject 并将其命名为 Box。

const boxObj = sheet.object('Box', {});

属性对应于可以动画化的对象的特定特征,可以有不同的类型,可以使用 import {types} from '@theatre/core' 导入。

我们将添加一些道具。 让我们从旋转开始,创建一个复合类型的属性,并添加数值类型的xR、yR和zR,值:0,范围:[-Math.PI,Math.PI]。

同样,让我们添加位置和比例属性。 向其中添加一个 nudgeMultiplier 可以让我们进行更精细的控制。

const boxObj = sheet.object('Box', {
    rotation: types.compound({
      xR: types.number(0, { range: [-Math.PI, Math.PI] }),
      yR: types.number(0, { range: [-Math.PI, Math.PI] }),
      zR: types.number(0, { range: [-Math.PI, Math.PI] }),
    }),
    position: types.compound({
      x: types.number(0, { nudgeMultiplier: 0.1 }),
      y: types.number(0, { nudgeMultiplier: 0.1 }),
      z: types.number(0, { nudgeMultiplier: 0.1 }),
    }),
    scale: types.compound({
      xS: types.number(1, { nudgeMultiplier: 0.1 }),
      yS: types.number(1, { nudgeMultiplier: 0.1 }),
      zS: types.number(1, { nudgeMultiplier: 0.1 }),
    }),
});

现在我们可以看到工作表下有一个新的 Box 对象。

5、为立方体设置动画

是时候为我们的立方体制作动画了。 我们需要一种根据 boxObj 属性的值来旋转立方体网格的方法。 这可以通过使用 onValuesChange() 钩子监听 boxObj 的变化来完成。

boxObj.onValuesChange((values) => {
    const { xR, yR, zR } = values.rotation;
    box.rotation.set(xR, yR, zR);
    const { x, y, z } = values.position;
    box.position.set(x, y, z);
    const { xS, yS, zS } = values.scale;
    box.scale.set(xS, yS, zS);
});

移动滑块现在会实时影响我们的立方体。

6、添加关键帧

让我们添加一些关键帧。 可以右键单击任何属性,然后单击“序列”或“序列全部”。

这将打开带有属性的序列编辑器(Sequence Editor)。 我们可以调整序列时间线的大小、放大或缩小,并使用蓝色指针擦洗序列。

拖动以移动指针,然后单击黄色按钮添加关键帧。

让我们将序列时间线的大小调整为 2 秒多一点。 然后添加关键帧来为立方体的 y 位置设置动画。 同样,让我们对比例进行排序并向其添加关键帧。 你可以使用这些值或自己尝试一下,直到你觉得合适为止。

然后按空格键播放序列:

7、图编辑器

单击序列编辑器中每个属性旁边的按钮将打开图编辑器(Graph Editor)或多轨曲线编辑器(multi-track curve editor)。 当我们想要通过手动编辑一个或多个轨道的速度曲线来精细化动画时,这会派上用场。

单击关键帧之间的链接可显示可使用的默认缓动曲线列表。

8、修改颜色

让我们继续看看如何使用 Theater.js 修改颜色。 让我们创建一个新对象并将其命名为 Colors。 背景颜色是 types.rgba()。 同样,我们还为地板颜色和盒子颜色创建属性:

const colorObj = sheet.object('Colors',{
    backgroundColor: types.rgba(),
    floorColor: types.rgba(),
    boxColor: types.rgba(),
})

在 onValuesChange() 钩子中,我们可以设置 scene.background 或底层 HTML 元素的背景颜色。 使用 setRGB(),我们设置地板和盒子材质的颜色。 单击并拖动颜色选择器以更改颜色。

colorObj.onValuesChange((values)=>{
    // scene.background = new THREE.Color(values.backgroundColor.toString());
    // @ts-ignore
    document.body.style.backgroundColor = values.backgroundColor;
    floorMaterial.color.setRGB(values.floorColor.r,values.floorColor.g,values.floorColor.b)
    boxMaterial.color.setRGB(values.boxColor.r,values.boxColor.g,values.boxColor.b)
})

如果立方体在拉伸时能发光那就太好了。 让我们创建一个新的Theatre对象: boxEffects。

const boxEffectsObj = sheet.object('Effects',{
    boxGlow:types.rgba(),
})
boxEffectsObj.onValuesChange((values)=>{
    boxMaterial.emissive.setRGB(values.boxGlow.r,values.boxGlow.g,values.boxGlow.b);
})

让我们编排其序列,并在前几帧上添加两个 emissive为 #000000 的关键帧,并为压缩状态选择一种颜色。 然后在最后一帧恢复正常。

9、速度线效果

要添加卡通速度线 vFx,让我们创建三个立方体并将它们缩放为看起来像线,然后将它们以组的形式添加到场景中。

// Swoosh Effect Objects
const swooshMaterial = new THREE.MeshBasicMaterial({color:0x222222,transparent:true,opacity:1});
const swooshEffect = new THREE.Group();

const swooshBig = new THREE.Mesh(geometry, swooshMaterial );
swooshBig.scale.set(0.02,2,0.02)
swooshBig.position.set(1,0,-2)

const swooshSmall1 = new THREE.Mesh(geometry, swooshMaterial );
swooshSmall1.scale.set(0.02,1,0.02)
swooshSmall1.position.set(1,0,3)

const swooshSmall2 = new THREE.Mesh(geometry, swooshMaterial );
swooshSmall2.scale.set(0.02,1.4,0.02)
swooshSmall2.position.set(-3,0,0)

swooshEffect.add( swooshBig, swooshSmall1, swooshSmall2 );
swooshEffect.position.set(0,20,0)
scene.add(swooshEffect)

让我们向 boxEffect 对象添加更多道具来调整线条的比例、位置和不透明度。 尝试使用该关键帧以获得所需的效果。

const boxEffectsObj = sheet.object('Effects',{
    boxGlow:types.rgba(),
    swooshScale:types.number(1,{nudgeMultiplier:0.01}),
    swooshPosition:types.number(0,{nudgeMultiplier:0.01}),
    swooshOpacity:types.number(1,{nudgeMultiplier:0.01})
})
boxEffectsObj.onValuesChange((values)=>{
    boxMaterial.emissive.setRGB(values.boxGlow.r,values.boxGlow.g,values.boxGlow.b);
    swooshEffect.scale.setY(values.swooshScale);
    swooshEffect.position.setY(values.swooshPosition);
    swooshMaterial.opacity=values.swooshScale;
})

10、漫画文字效果

是时候来一些动漫文字效果了:“Boink!”

从 THREE 导入 {CSS2DRenderer,CSS2DObject} 并创建一个 textRenderer。 让我们将 style.position 设置为 absolute 并更新 OrbitControls 的 domElement。

创建一个新的 CSS2D 对象,将其添加到场景中,然后添加一个表示该对象的 HTML 元素。 将文本添加到框中,使其跟随屏幕上的框位置。

  <div id="boink">Boink!!</div>
import {CSS2DRenderer,CSS2DObject} from 'three/examples/jsm/renderers/CSS2DRenderer'
let textRenderer = new CSS2DRenderer();
textRenderer.setSize(window.innerWidth,window.innerHeight);
textRenderer.domElement.style.position = 'absolute';
textRenderer.domElement.style.top = "0";
textRenderer.domElement.style.left = "0";
textRenderer.domElement.style.width = "100%";
textRenderer.domElement.style.height = "100%";
textRenderer.domElement.style.zIndex = "2";
document.body.appendChild(textRenderer.domElement)

// OrbitControls
controls = new OrbitControls(camera, textRenderer.domElement);

// Text Effects
const boinkDom = document.getElementById('boink');
const boinkText = new CSS2DObject(boinkDom);
boinkText.position.set(-25,0,0)
box.add(boinkText);

// add this to your render()/tick() function
// textRenderer.render(scene, camera);

创建一个新的 theatre.js 对象: textEffectObj,其中包含不透明度、文本和比例的属性。

使用 onValuesChange(),更新HTML 元素的 innerText。 Theater.js 的一个有趣之处是:它也可以用于修改文本和动画文本。 对所有属性进行排序并添加关键帧,使文本在盒子弹起时弹出。

const textEffectObj = sheet.object('text',{
    opacity:1,
    text:"",
    scale: 1
});

textEffectObj.onValuesChange((values)=>{
    if(!boinkDom)return;
    boinkDom.innerText = values.text;
    boinkDom.style.opacity = ""+values.opacity
    boinkDom.style.fontSize = ""+values.scale+"px";
})

11、鼠标音效

最后,为了让一切变得栩栩如生,让我们添加声音效果。 我在 Pixabay 上搜索了一些免费的声音并将它们导入到项目中。 然后我使用 Three.js AudioLoader 加载它们。 以下是我向 Three.js 项目添加声音的方法:

// importing my sounds as urls
import swooshSound from '../assets/sounds/whoosh.mp3';
import boinkSound from '../assets/sounds/boink.mp3';
import thudSound from '../assets/sounds/loud-thud-45719.mp3';

const listener = new THREE.AudioListener();
const loader = new THREE.AudioLoader(loadingMgr);
let soundReady = false;
const swoosh = new THREE.Audio(listener)
const boink = new THREE.Audio(listener)
const thud = new THREE.Audio(listener)

setupSounds();

function setupSounds() {
  camera.add(listener);

  audioSetup(swoosh,swooshSound,0.3,loader)
  audioSetup(boink,boinkSound,0.2,loader)
  audioSetup(thud,thudSound,0.5,loader)
}

function audioSetup(sound:THREE.Audio, url:string, volume:number, loader:THREE.AudioLoader){
  loader.load(
    url,
    // onLoad callback
    function ( audioBuffer ) {
      sound.setBuffer( audioBuffer );
      sound.setVolume(volume)
      sound.loop=false;
    },
  );
}

设置完成后,我们可以根据序列中的指针位置继续播放声音。 我们可以通过利用 onChange() 钩子并监视指针位置的变化以在特定时间间隔触发声音播放来实现此目的。

// play the audio based on pointer position
onChange(sheet.sequence.pointer.position, (position) => {
    if(!soundReady)return;
    if(position > 0.79 && position < 0.83){
        if(!thud.isPlaying){
            thud.play();
        }
    }
    else if(position > 1.18 && position < 1.23){
        if(!boink.isPlaying){
            boink.play();
        }
    }
    else if(position > 0.00 && position<0.04){
        if(!swoosh.isPlaying){
            swoosh.playbackRate= 1.7;
            swoosh.play();
        }
    }
})

要为 click添加新的事件侦听器,将 soundReady 设置为 true,并利用 sheet.sequence.play() 播放动画,迭代计数为 Infinity,范围为 0-2。

<style>
.enterSceneContainer{
  z-index: 4;
  position: absolute;
  display: block;
  width: 100%;
  height: 100%;
  text-align: center;
  transition: all 0.5s ease;
}
</style>
<div class="enterSceneContainer" id="tapStart">
    <p>Tap to start</p>
</div>
// Play sequence on click once all assets are loaded
const tapStart = document.getElementById('tapStart');

tapStart.addEventListener(
    'click',
    function () {
        soundReady = true;
        tapStart.style.opacity = "0";
        setTimeout(()=>{
          tapStart.style.display = "none";
        },400)
        sheet.sequence.play({ iterationCount: Infinity, range: [0, 2] });
    }
);

12、色调映射和编码器

为了增强场景的颜色,你可以为渲染器指定不同的 toneMappings和 outputEncodings。

在尝试了各种选项后,我选择将它们设置为这个特定项目的 LinearToneMapping 和 sRGBEncoding。

renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.LinearToneMapping;

要添加雾并将其与场景背景同步,可以在 colorObj.onValuesChange() 钩子中包含相关代码。

scene.fog = new THREE.FogExp2(0xffffff, 0.009);
colorObj.onValuesChange((values)=>{
    // @ts-ignore
    scene.fog.color = new THREE.Color(values.backgroundColor.toString());
    // ... rest of the code here ...
})

13、部署到生产环境

为了完成该项目,我们需要导出动画并将其部署到生产中。 只需单击Studio UI 中的项目名称并选择“导出到 JSON”,然后保存状态即可。

将保存的状态导入到 main.js 中,并在 getProject 中传递保存的状态。

import projectState from '../assets/Saved_TheatreState.json';

project = getProject('TheatreTutorial_1', { state: projectState });

导出动画后,你可以删除 studio import 和 studio.initialize(),因为制作不需要它们。 或者,你可以根据需要有条件地删除或包含它们。

let project;
// Using Vite
if (import.meta.env.DEV) {
    studio.initialize();
    // Create a project from local state
    project = getProject('TheatreTutorial_1');
}
else {
    // Create a project from saved state
    project = getProject('TheatreTutorial_1', { state: projectState });
}

不要忘记查看最终代码。 或者,也可以点击此链接来观看视频教程。


原文链接:http://www.bimant.com/blog/theatre-js-animation-tutorial/

相关推荐

C#.NET Autofac 详解(c# autoit)

简介Autofac是一个成熟的、功能丰富的.NET依赖注入(DI)容器。相比于内置容器,它额外提供:模块化注册、装饰器(Decorator)、拦截器(Interceptor)、强o的属性/方法注...

webapi 全流程(webapi怎么部署)

C#中的WebAPIMinimalApi没有控制器,普通api有控制器,MinimalApi是直达型,精简了很多中间代码,广泛适用于微服务架构MinimalApi一切都在组控制台应用程序类【Progr...

.NET外挂系列:3. 了解 harmony 中灵活的纯手工注入方式

一:背景1.讲故事上一篇我们讲到了注解特性,harmony在内部提供了20个HarmonyPatch重载方法尽可能的让大家满足业务开发,那时候我也说了,特性虽然简单粗暴,但只能解决95%...

C# 使用SemanticKernel调用本地大模型deepseek

一、先使用ollama部署好deepseek大模型。具体部署请看前面的头条使用ollama进行本地化部署deepseek大模型二、创建一个空的控制台dotnetnewconsole//添加依赖...

C#.NET 中间件详解(.net core中间件use和run)

简介中间件(Middleware)是ASP.NETCore的核心组件,用于处理HTTP请求和响应的管道机制。它是基于管道模型的轻量级、模块化设计,允许开发者在请求处理过程中插入自定义逻辑。...

IoC 自动注入:让依赖注册不再重复劳动

在ASP.NETCore中,IoC(控制反转)功能通过依赖注入(DI)实现。ASP.NETCore有一个内置的依赖注入容器,可以自动完成依赖注入。我们可以结合反射、特性或程序集扫描来实现自动...

C#.NET 依赖注入详解(c#依赖注入的三种方式)

简介在C#.NET中,依赖注入(DependencyInjection,简称DI)是一种设计模式,用于实现控制反转(InversionofControl,IoC),以降低代码耦合、提高可...

C#从零开始实现一个特性的自动注入功能

在现代软件开发中,依赖注入(DependencyInjection,DI)是实现松耦合、模块化和可测试代码的一个重要实践。C#提供了优秀的DI容器,如ASP.NETCore中自带的Micr...

C#.NET 仓储模式详解(c#仓库货物管理系统)

简介仓储模式(RepositoryPattern)是一种数据访问抽象模式,它在领域模型和数据访问层之间创建了一个隔离层,使得领域模型无需直接与数据访问逻辑交互。仓储模式的核心思想是将数据访问逻辑封装...

C#.NET 泛型详解(c# 泛型 滥用)

简介泛型(Generics)是指在类型或方法定义时使用类型参数,以实现类型安全、可重用和高性能的数据结构与算法为什么需要泛型类型安全防止“装箱/拆箱”带来的性能损耗,并在编译时检测类型错误。可重用同一...

数据分析-相关性分析(相关性 分析)

相关性分析是一种统计方法,用于衡量两个或多个变量之间的关系强度和方向。它通过计算相关系数来量化变量间的线性关系,从而帮助理解变量之间的相互影响。相关性分析常用于数据探索和假设检验,是数据分析和统计建模...

geom_smooth()函数-R语言ggplot2快速入门18

在每节,先运行以下这几行程序。library(ggplot2)library(ggpubr)library(ggtext)#用于个性化图表library(dplyr)#用于数据处理p...

规范申报易错要素解析(规范申报易错要素解析)

为什么要规范申报?规范申报是以满足海关监管、征税、统计等工作为目的,纳税义务人及其代理人依法向海关如实申报的行为,也是海关审接单环节依法监管的重要工作。企业申报的内容须符合《中华人民共和国海关进出口货...

「Eurora」海关编码归类 全球海关编码查询 关务服务

  海关编码是什么?  海关编码即HS编码,为编码协调制度的简称。  其全称为《商品名称及编码协调制度的国际公约》(InternationalConventionforHarmonizedCo...

9月1日起,河南省税务部门对豆制品加工业试行新政7类豆制品均适用投入产出法

全媒体记者杨晓川报道9月2日,记者从税务部门获悉,为减轻纳税人税收负担,完善农产品增值税进项税额抵扣机制,根据相关规定,结合我省实际情况,经广泛调查研究和征求意见,从9月1日起,我省税务部门对豆制品...