Babylon-js-精要-全-
Babylon.js 精要(全)
原文:
zh.annas-archive.org/md5/c071bab1287252d97dba89bcda0206db译者:飞龙
前言
3D 开发一直是一件神秘的事情。从开始讲起,这本书介绍了 3D 开发所需的基本知识以及使用 Babylon.js 框架所需的知识。它侧重于 Babylon.js 提供的简洁性,并结合理论和实践。所有章节都提供了可运行的示例文件;每个示例文件都提供了之前学到的框架功能。最后,开发者将能够轻松理解未来添加到框架中的新功能,并仅使用文档来使用更高级的功能。
本书涵盖内容
第一章, Babylon.js 和 TypeScript 语言,快速介绍了 Babylon.js 的故事,并开设了一门关于 TypeScript 语言的基础课程。
第二章, Babylon.js 和可用工具的基础知识,从 Babylon.js 框架开始,创建第一个 3D 场景,展示了框架的简洁性以及理论。
第三章, 在屏幕上创建、加载和绘制 3D 对象,从 第二章 的概念开始,介绍创建 3D 场景和与 3D 艺术家合作的正确方法。
第四章, 使用材质自定义 3D 对象的外观,解释了 3D 引擎中材质的概念。换句话说,让我们释放 Babylon.js 标准材质的潜力。
第五章, 在对象上创建碰撞,通过管理场景中的碰撞,包括物理模拟,专注于游戏玩法本身。
第六章, 在 Babylon.js 中管理音频,解释了 Babylon.js 框架的一个增值功能。让我们在场景中添加和管理声音,包括空间化声音。
第七章, 在对象上定义动作,介绍了一种智能的方法,可以在不写太多代码的情况下触发 3D 对象上的动作,因为一些任务对开发者来说可能很痛苦。
第八章, 使用内置后处理添加渲染效果,展示了大多数 3D 开发者的首选部分。这展示了如何使用后处理效果轻松美化 3D 场景,结合 Babylon.js 提供的简洁性。
第九章, 创建并播放动画,让我们能够使用 Babylon.js 提供的动画系统进行操作。本章提供了你准备构建自己的专业 3D 应用程序所需的最终技能!
您需要为这本书准备什么
本书针对希望在其浏览器上构建完整 3D 视频游戏或应用程序的 HTML5 开发者。读者应熟悉 JavaScript 语言和基本 3D 表示(向量和维度的概念)。Babylon.js 框架设计得易于使用,因此,不需要 3D 开发背景,欢迎初学者。
这本书面向的对象
Babylon.js Essentials 旨在为希望进入 Web 3D 开发世界或将其 Babylon.js 框架添加到技能集的开发者设计。理解面向对象编程的概念将有助于理解 Babylon.js 框架的架构。此外,熟悉 Web 开发将有助于理解所使用的原则。
约定
在这本书中,您将找到许多文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称如下所示:“使用 TS,输入新的Array()相当于新的Array<any>()。”
代码块设置如下:
var skybox = BABYLON.Mesh.CreateBox("skybox", 300, scene);
var skyboxMaterial = new BABYLON.StandardMaterial("skyboxMaterial", scene);
skyboxMaterial.backFaceCulling = false;
skyboxMaterial.reflectionTexture =
new BABYLON.CubeTexture("skybox/TropicalSunnyDay", scene);
任何命令行输入或输出都应如下所示:
var myVar: FileAccess = FileAccess.Read; // Equivalent to 0
新术语和重要单词以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,在文本中如下所示:“选择文件位置,就像您为 Blender 保存 Babylon.js 场景时所做的,然后点击导出。”
注意
警告或重要注意事项以如下框中的形式出现。
小贴士
小贴士和技巧看起来像这样。
读者反馈
我们欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或不喜欢什么。读者反馈对我们很重要,因为它帮助我们开发出您真正能从中获得最大价值的标题。
要向我们发送一般反馈,只需发送电子邮件至<feedback@packtpub.com>,并在邮件主题中提及书的标题。
如果您在某个主题上具有专业知识,并且您对撰写或为本书做出贡献感兴趣,请参阅我们的作者指南www.packtpub.com/authors。
客户支持
现在您是 Packt 图书的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大价值。
下载示例代码
您可以从www.packtpub.com下载示例代码文件,这是您购买的所有 Packt Publishing 书籍的账户。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
下载本书中的彩色图像
我们还为您提供了一个包含本书中使用的截图/图表的彩色图像的 PDF 文件。彩色图像将帮助您更好地理解输出中的变化。您可以从www.packtpub.com/sites/default/files/downloads/BabylonJSEssentials_ColorImages.pdf下载此文件。
错误清单
尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在我们的某本书中找到错误——可能是文本或代码中的错误——如果您能向我们报告,我们将不胜感激。这样做可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何错误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击错误提交表单链接,并输入您的错误详细信息来报告它们。一旦您的错误得到验证,您的提交将被接受,错误将被上传到我们的网站或添加到该标题的错误清单部分。
要查看之前提交的错误清单,请访问www.packtpub.com/books/content/support,并在搜索字段中输入书籍名称。所需信息将出现在“错误清单”部分下。
盗版
互联网上版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过<copyright@packtpub.com>与我们联系,并提供指向疑似盗版材料的链接。
我们感谢您在保护我们作者和我们为您提供有价值内容的能力方面提供的帮助。
问题
如果您对本书的任何方面有问题,您可以通过<questions@packtpub.com>与我们联系,我们将尽力解决问题。
第一章. Babylon.js 和 TypeScript 语言
Babylon.js 是一个允许你为 Web 创建完整的 3D 应用程序和 3D 视频游戏的框架。Babylon.js 拥有一个日益增长的社区;一个积极为项目做出贡献、添加更多功能的社区。本章简要介绍了框架的愿景和 TypeScript 语言,因为 Babylon.js 就是使用这个语言开发的。
Babylon.js 框架集成了处理特定 3D 应用所需的所有工具。它允许你加载和绘制 3D 对象,管理这些 3D 对象,创建和管理特殊效果,播放和管理空间化声音,创建游戏玩法,等等。Babylon.js 是一个易于使用的框架,因为你可以用最少的代码行设置这些事情(你稍后就会看到)。
Babylon.js 是一个使用 TypeScript 开发的 JavaScript 框架。TypeScript 是一种编译型和多平台语言,它生成纯 JavaScript 文件。
本章将涵盖以下主题:
-
Babylon.js 简介
-
Babylon.js 使用 TypeScript 的原因
-
TypeScript 简介
创始人
Babylon.js 是由 David Catuhe (@deltakosh)、David Rousset (@davrous)、Pierre Lagarde (@pierlag) 和 Michel Rousseau (@rousseau_michel) 创建的。它是一个本质上在他们的业余时间开发的开源项目。当他们开始 Babylon.js 时,他们希望它设计得易于使用,然后为每个人提供一个可访问的 3D 引擎。官方网站 (www.babylonjs.com/) 包含了大量针对初学者(甚至是在 3D 方面)到更高级用户的教程,每个功能都有示例,场景作为示例。
Babylon.js 解决方案提供的在线工具
Babylon.js 为你提供了几个在线工具,以帮助开发者和艺术家进行实验和尝试他们的作品:
-
对于开发者来说,游乐场 (
www.babylonjs-playground.com/) 允许你进行实验和训练。它展示了一个带有自动补全(Monaco)的代码编辑器和画布来查看结果。它还提供了一些代码示例供你训练使用。 -
对于艺术家来说,沙盒 (
www.babylonjs.com/sandbox/) 允许你将导出的 Babylon.js 场景(Blender 和 3ds Max)拖放到浏览器中,以实时查看结果。沙盒为你提供了调试工具,可以激活/停用功能并查看对实时性能的影响。 -
创建您的自定义着色器(CYOS)允许开发者开发着色器并实时查看结果。还有几个现成的着色器可供训练和实验。
为什么 Babylon.js 使用 TypeScript 开发?
Babylon.js 是一个自 GitHub 上创建以来贡献不断增加的大项目。它为你提供了许多功能,有时也提供了许多参数以实现更多灵活性。TypeScript 语言对于编写健壮的代码很有用,因为它的目标是改进和保障 JavaScript 代码的生产。
TypeScript 语言
TypeScript (TS) 是由微软开发的一个免费和开源的语言。它是一个编译语言,用于生成 JavaScript(实际上,TS 代码是转编译的),并提供一个可选的静态类型系统。类型系统在 Babylon.js 中使用,以获得更干净和更具描述性的代码。这意味着如果一个函数有很多参数,那么填写和理解它们比总是使用文档作为参考要容易得多。此外,它允许开发者声明类(如 ECMAScript 6 规范所做的那样)和接口,以获得更好的可理解架构和代码结构。
TypeScript 的功能
类型系统非常强大,因为它允许开发者创建接口、枚举类型和类,并处理泛型和联合类型。总的来说,开发者使用类型系统来更好地理解和保障他们构建和使用的库。
TS 语言支持继承(类)并提供访问修饰符(私有 / 公共 / 受保护)来修改类的成员的访问权限。然后,开发者可以一目了然地看到他们可以使用和修改的成员。
TypeScript 简介 - 你必须知道的内容
让我们通过一些功能示例和配置来介绍 TypeScript:如何将 TS 文件编译成 JS 文件,与类 / 类型 / 联合类型、函数、继承和接口一起工作。
使用 Gulp 进行编译
Gulp 是一个可用的 npm 包的任务运行器。它提供了一个插件来处理 TypeScript 编译。唯一要做的就是使用 gulp 和 gulp-typescript 配置一个任务。
要下载 gulp 包,你必须安装 Node.js (nodejs.org/) 以获取对 npm 包的访问权限:
-
使用以下命令行安装 Gulp:
npm install gulp -
使用以下命令行安装 Gulp-Typescript:
npm install gulp-typescript -
要配置 Gulp 任务,只需提供一个名为
gulpfile.js的 JS 文件,其中包含任务描述。 -
导入 Gulp 和 Gulp-TypeScript:
var gulp = require("gulp"); var ts = require("gulp-typescript"); -
定义默认任务以转编译你的 TS 文件:
gulp.task('default', function() { // Default task var result = gulp.src([ // Sources "myScript1.ts", "myScript2.ts", // Other files here ]) .pipe(ts({ // Trans-compile out: "outputFile.js" // Merge into one output file })); return result.js.pipe(gulp.dest("./")); // output file desti nation }); -
一旦默认任务列出了所有需要转编译的 TS 文件,只需使用以下命令行调用 Gulp:
gulp
使用类型变量进行工作
使用 TypeScript 进行工作与 JS 非常相似,因为类型系统是可选的。尽管如此,TS 中的常见类型如下:
-
字符串
-
数字
-
布尔值
-
任何
-
无效
-
枚举
-
数组
使用 JS,你应该编写以下内容:
var myVar = 1.0;// or
var myVar = "hello !";
在这里,你可以用 TS 写出完全相同的内容。TS 编译器将处理类型推断并为你猜测变量类型:
var myVar = 1.0; // Which is a number
// or
var myVar = "hello !"; // Which is a string
要使用 TS 指定变量的类型,输入以下命令:
var myVar: type = value;
然后,在先前的示例中,添加以下代码:
var myVar: number = 1.0;
// or
var myVar: string = "hello !";
// etc.
然而,即使没有提及类型,也不允许使用不同类型的值来分配新值:
var myVar = 1.0; // Now, myVar is a number
// and
myVar = "hello !"; // Forbidden, "hello" is a string and not a number
为了获得具有变量灵活性的 JS,让我们引入 any 类型。any 类型允许开发者创建没有任何静态类型的变量。以下是一个示例:
var myVar: any = 1.0; // Is a number but can be anything else
myVar = "Hello !"; // Allowed, myVar's type is "any"
以下是 types.ts 文件的截图:

让我们介绍一些特定的类型。这是介绍 TypeScript 中的泛型和枚举类型的时机。数字、布尔值和字符串的使用在 TypeScript 和 JavaScript 中相同。因此,无需学习更多。
枚举类型
使用枚举类型(enum)就像使用数字一样。语法如下:
enum FileAccess {Read, Write};
这生成了以下 JS 代码:
var FileAccess;
(function (FileAccess) {
FileAccess[FileAccess["Read"] = 0] = "Read";
FileAccess[FileAccess["Writer"] = 1] = "Writer";
})(FileAccess || (FileAccess = {}));
在两种语言中访问枚举类型如下:
var myVar: FileAccess = FileAccess.Read; // Equivalent to 0
数组
使用 TypeScript 定义数组与 JS 类似。以下是一个示例:
// In both languages
var myArray = [];
// or
var myArray = new Array();
在 TypeScript 中,数组是一个泛型类。然后,您可以指定数组中包含的项目类型,如下所示:
var myArray = new Array<number>();
注意
注意:在 TypeScript 中,使用 new Array() 等同于 new Array<any>()。
您现在可以按如下方式访问常用函数:
var myArray = new Array<any>();
myArray.push("Hello !");
myArray.push("1");
myArray.splice(0, 1);
console.log(myArray); // "[1]"
使用类和接口
类和接口允许您构建类型,就像 Array 类一样。一旦创建了一个类,您就可以使用关键字 new 创建实例,这将在内存中创建一个对象。
以下是一个示例:
var myArray = new Array<any>(); // Creates a new instance
创建类
在 TypeScript 中定义类的语法如下:
class Writer {
constructor() {
// initialize some things here
}
}
这在 JS 中生成以下内容:
var Writer = (function () {
function Writer() {
}
return Writer;
})();
在两种语言中,您都可以创建 Writer 的实例:
var myInstance = new Writer();
您还可以使用作为命名空间的模块:
module MY_MODULE {
class Writer {
...
}
}
访问:
var writer = new MY_MODULE.Writer(...);
创建类成员
使用 JS 和约定,您可以编写以下内容:
function Writer() {
this.myPublicMember = 0.0; // A public member
this._myPrivateMember = 1.0; // A member used as private
}
在 TypeScript 中,您可以显式指定成员的访问修饰符(公共、私有和受保护),如下所述:
-
公共:任何代码块都可以访问成员进行读写
-
私有:只有这个可以访问此成员进行读写
-
受保护:外部代码块无法访问成员;只有这个和特殊化者(继承)可以访问此成员进行读写
让我们通过 Writer 类进行实验:
// declare class
class Writer {
// Union types. Can be a "string" or
// an array of strings "Array<string>"
public message: string|string[];
private _privateMessage: string = "private message";
protected _protectedMessage: string;
// Constructor. Called by the "new" keyword
constructor(message: string|string[]) {
this.message = message;
this._protectedMessage = "Protected message !"; // Allowed
}
// A public function accessible from everywhere.
// Returns nothing. Then, its return type is "void".
public write(): void {
console.log(this.message); // Allowed
console.log(this._privateMessage); // Allowed
console.log(this._protectedMessage); // Allowed
}
}
var writer = new Writer("My Public Message !");
console.log(writer.message); // Allowed
console.log(writer._privateMessage); // Not allowed
console.log(writer._protectedMessage); // Not allowed
使用继承
让我们创建一个专门扩展 Writer 类的新类。由于继承,专门的类可以访问基类的所有公共和受保护成员。extends 关键字表示继承。
让我们创建一个名为 BetterWriter 的新类,该类专门(扩展)了 Writer 类:
// The base class is "Writer"
class BetterWriter extends Writer {
constructor(message: string|string[]) {
// Call the base class' constructor
super(message);
}
// We can override the "write" function
public write(): void {
if (typeof this.message === "string") {
// Call the function "write" of the base class
// which is the "Writer" class
super.write();
}
else {
for (var i=0; i < this.message.length; i++) {
console.log(this.message[i]); // Allowed
console.log(this._privateMessage); // Not allowed
console.log(this._protectedMessage); // Allowed
}
}
}
}
使用接口
接口用于创建合约。这意味着如果一个类实现了接口,该类必须提供接口中定义的所有函数和成员。如果不这样做,它不遵守合约,编译器将输出错误。
所有的定义函数都是公开的,所有的定义成员也都是公开的。
使用 Babylon.js 时,一个很好的例子是使用 IDisposable 接口。这意味着用户可以调用名为 dispose() 的方法。这个函数的职责是停用和/或释放所使用的系统。
以下是一个示例:
interface IWriter {
// The class "Writer" must have the "message" member
message: string|string[];
// The class "Writer" must provide the "resetMessages" function.
resetMessages(): void;
}
class Writer implements IWriter {
public message: string|string[];
...
constructor(...) {
...
}
...
// All functions declared in the interface are public.
public resetMessages(): void {
this.message = this._privateMessage = this._protectedMessage = "";
}
}
摘要
在本章中,你获得了使用 Babylon.js 开发程序所需的必要知识。你会发现,在大多数情况下,使用 TypeScript 可以更高效、更安全。此外,一些开发者可能会更习惯使用类型,因为他们已经习惯了使用类型进行开发。
不要犹豫,使用附带的示例文件操作 TypeScript。别忘了安装 gulp 并运行命令行。
你也可以运行以下命令行:
gulp watch
这将自动跟踪并重新编译每次修改的 TS 文件。
在下一章中,让我们直接进入主题,通过介绍 Babylon.js 框架以及如何创建引擎和场景实体(如灯光、相机和网格(3D 对象))来了解 Babylon.js。你将使用 Babylon.js 构建你的第一个 3D 场景,并快速理解框架的架构!
第二章。Babylon.js 的基础和可用工具
本章介绍了 Babylon.js 的基础。它包括 引擎、场景、相机、灯光、网格 等概念。通过实践,你会了解 3D 中使用的基工具,如向量以及它们在 3D 引擎中如何/在哪里使用。你还将使用图结构进行实践,并了解 Babylon.js 的架构。
本章将涵盖以下主题:
-
讨论 Babylon.js 的结构和图
-
创建你的第一个场景
Babylon.js 的结构和图
首先,让我们创建并解释在屏幕上绘制东西所需的必要工具,例如一个处理场景以绘制 3D 对象并使用相机和灯光的引擎。
引擎和场景
引擎是 Babylon.js 的核心,场景允许你创建和管理你将在屏幕上绘制的实体(对象、灯光、相机等),这得益于引擎。你可以将引擎视为与视频卡(GPU)通信的网关,而场景是一个高级接口,处理以下多个实体:
-
3D 对象(更多内容请参考第三章,在屏幕上创建、加载和绘制 3D 对象)
-
相机
-
灯光
-
粒子系统(烟雾、雨等)
-
纹理
-
骨骼(动画 3D 对象)
-
后处理(效果)
-
材质(更多内容请参考第四章,使用材质自定义 3D 对象的外观)
-
精灵
换句话说,一个场景处理多个实体,并将调用引擎在屏幕上绘制这些实体。
要在屏幕上绘制,网页(index.html)必须包含一个画布。画布用于创建引擎将使用的 WebGL 上下文。创建引擎所需的唯一参数是画布,创建场景所需的唯一参数是引擎。
网页中的画布如下所示:
<canvas id="renderCanvas"></canvas>
以下代码在整个页面上执行以进行渲染:
<style>
html, body {
overflow: hidden;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#renderCanvas {
width: 100%;
height: 100%;
}
</style>
要创建一个引擎,你必须将画布引用作为参数。获取 DOM 对象的引用如下:
var canvas = document.getElementById("renderCanvas");
通过传递画布引用来创建一个引擎:
var engine: BABYLON.Engine = new BABYLON.Engine(canvas);
创建场景如下:
var scene: BABYLON.Scene = new BABYLON.Scene(engine);
每创建一个新的场景,都会存储在 engine.scenes 中,它是一个 BABYLON.Scene 的数组。
一旦创建了一个场景,你就可以渲染每一帧(在理想情况下每秒 60 帧,如果硬件不足则更少)。要执行此操作,你必须调用 engine.runRenderLoop(function) 函数。匿名函数 function 在每一帧被调用,这是绘制场景的地方:
// runRenderLoop with TypeScript. The anonymous function doesn't
// have any parameter
engine.runRenderLoop(() => {
scene.render();
});
在每一帧绘制屏幕上的每个对象时,都会调用 scene.render() 函数。
添加相机和灯光
一旦创建了一个场景,你就可以创建诸如灯光和相机等对象。最小场景需求由一个相机(用于在场景中获取视图)和灯光(照亮场景中的 3D 对象,但如果场景中的对象是自发光的,则此不是必需的)组成。
添加相机
有几种类型的相机和灯光。Babylon.js 框架为你提供了固定相机(无移动)、FPS 相机、旋转相机(围绕一个点旋转)、游戏手柄相机(XBOX 360 游戏手柄)和触摸相机(用于触摸设备)。所有这些相机都可以用最少的代码行在场景中创建。例如,让我们从一个位置在坐标(x=10,y=20,z=30)的免费相机(FPS)开始。这是介绍BABYLON.Vector3类的好机会:
var camera = new BABYLON.FreeCamera("cameraName", new BABYLON.Vector3(10, 20, 30), scene);
构造函数需要一个名称(cameraName),相机的位置以及场景引用(在哪里添加相机)。位置是BABYLON.Vector3(x, y, z)类型,它是 Babylon.js 的 3D 向量类。Vector3对象提供了加法、减法、乘法、除法等数学函数。
一旦创建了相机,让我们改变相机的位置并执行Vector3加法:
camera.position = new BABYLON.Vector3(10, 0, 10).addInPlace(new BABYLON.Vector3(0, 10, 0));
.addInPlace方法修改了当前的Vector3实例,但你也可以调用.add方法,它将返回一个应用了加法的新Vector3实例。
现在相机的新的位置是 (x=10,y=10,z=10)。修改.position属性并不适用于所有相机,特别是旋转相机(BABYLON.ArcRotateCamera)。旋转相机围绕一个点(即相机的目标点)旋转,可以解释如下:

立方体位于位置 (x=0,y=0,z=0),并且是相机的目标。要改变相机的位置,你必须调用camera.setPosition(position)函数。此函数将计算 alpha(围绕 Y 轴的角度)、beta(围绕 X 轴的角度)和半径的适当值。然后,.position属性仅用于读取。
添加灯光
一旦你创建了你的相机,让我们添加一个灯光。不同的灯光允许你在场景中使用不同的功能进行照明。点光源和半球光源倾向于照亮场景中的对象,而聚光灯和方向光源也提供了实时阴影的支持。
让我们从点光源开始。灯光的工作方式类似于相机,这意味着你必须提供场景引用(在哪里添加灯光):
var light = new BABYLON.PointLight("lightName", BABYLON.Vector3.Zero(), scene);
第二个参数是灯光的位置。BABYLON.Vector3.Zero()是一个静态方法,它是创建一个坐标为(x=0,y=0,z=0)的Vector3实例的快捷方式。现在,让我们玩一下灯光的参数:
light.position = new BABYLON.Vector3(20, 20, 20);
light.diffuse = new BABYLON.Color3(0, 1, 0);
light.specular = new BABYLON.Color3(1, 0, 0);
light.intensity = 1.0;
以下是对前面代码的参数说明:
-
.diffuse参数表示光的固有颜色,在示例中是绿色。 -
.specular参数表示表面反射的光的颜色,这里为红色 -
.intensity参数是灯光的强度,默认值为 1.0
将以下参数应用于 3D 对象的结果如下:

通过修改反射颜色为红色和蓝色,以下效果得以实现:

添加网格
网格是什么?实际上,3D 对象就是我们所说的网格。它们的结构相当简单,因为它们在内存中有两个重要的缓冲区:
-
顶点缓冲区是一个 3D 点的数组:顶点。顶点表示构建几何形状(如立方体、球体、角色、武器等)所需的 3D 点。例如,一个立方体有八个顶点。
-
索引缓冲区是一个数字数组,表示顶点缓冲区中的索引,用于构建三角形。实际上,图形卡被优化来计算和渲染屏幕上的三角形。例如,一个立方体的面将使用两个三角形来渲染,如下面的图像所示,其中绿色线条显示了面:
![添加网格]()
图形卡的工作分为两个步骤:
-
它们将顶点缓冲区的顶点(3D 点)投影(变换)到屏幕上。这些操作由一个名为顶点着色器的 GPU 程序执行。换句话说,顶点着色器计算每个三角形的 2D 位置。
-
它们使用一个名为像素着色器的 GPU 程序用颜色填充与三角形相关的像素。
为了总结图形卡的工作,顶点着色器计算屏幕上的 2D 三角形,像素着色器用不同的颜色照亮像素。
使用 Babylon.js 创建网格
Babylon.js 在 BABYLON.Mesh 类中提供了静态方法,允许您创建基本网格,如盒子、球体和环面。每个创建的网格都是 BABYLON.Mesh 的一个实例。让我们从一个盒子开始:
var box = BABYLON.Mesh.CreateBox("boxName", size, scene);
size 参数表示顶点之间的距离(例如,5)和 scene 参数是场景引用(在哪里添加网格)。一旦创建了网格,您就可以像以下这样访问其属性和方法:
box.position = new BABYLON.Vector3(0, 2.5, 0);
box.rotation = new BABYLON.Vector3(0, Math.PI / 4, 0);
box.scaling = new BABYLON.Vector3(2, 2, 2);
.rotation 参数表示网格的旋转,以弧度表示 [0, 2π],然后以度数表示,π = 180 度 和 2π = 360 度。
.scaling 参数表示网格在三个方向上的缩放(x、y、z)。在示例中,网格是原来的两倍大。
如果您只修改其旋转,看看结果会怎样:

以下图像是应用新的缩放(x=2、y=2、z=2)的结果:

一些其他的基本网格
存在多个基本网格,BABYLON.Mesh 可以为您创建,例如球体和平面。让我们使用 BABYLON.Mesh 类创建一个球体和平面:
var sphere = BABYLON.Mesh.CreateSphere("sphereName", segments, size, scene);
var plane = BABYLON.Mesh.CreatePlane("planeName", size, scene);
球体的 segments 参数表示球体的细节级别,例如,10。
平面的 size 参数表示顶点之间的距离,例如,10。
管理场景图
场景可以通过名为场景图的节点图来管理。在 Babylon.js 中,每个网格、灯光和摄像机都扩展了 BABYLON.Node 类。换句话说,每个 BABYLON.Node 都在场景图中。
每个节点都有一个子节点数组和唯一的一个父节点。每个创建的节点默认是图根的子节点。
要修改节点的父节点,只需修改节点的引用:
light.parent = camera;
现在,因为灯光是摄像机的子节点,所以灯光的位置取决于摄像机的位置。确实,灯光空间是摄像机的一个子空间。换句话说,你可以将位置(x=0,y=0,z=0)视为摄像机的位置。
当设置父节点时,子节点的变换属性(位置、旋转和缩放)会受到父节点的影响。
示例
让我们使用两个节点(node1 和 node2)来管理场景图,并比较如果你手动操作时等效的代码:
node1.position = new BABYLON.Vector3(0, 0, 0);
node1.parent = node2;
这与以下代码等效:
engine.runRenderLoop(() => {
node1.position = node2.position;
node1.rotation = node2.rotation;
node1.scaling = node2.scaling;
});
以下架构解释了前面的代码:

创建你的第一个场景
现在,你已经拥有了构建第一个场景所需的所有必要元素。在这里,场景将由旋转摄像机、点光源和盒子组成。让我们使用 TypeScript 创建一个类并使用 Babylon.js 进行练习。
创建类和场景节点
以下类在构造函数中直接创建 Babylon.js 元素:
export class BasicScene {
public camera: BABYLON.ArcRotateCamera; // Our camera
public light: BABYLON.PointLight; // Our light
public box: BABYLON.Mesh; // Our box
private _engine: BABYLON.Engine; // The Babylon.js engine
private _scene: BABYLON.Scene; // The scene where to add the nodes
// Our constructor. The constructor provides the canvas reference
// Then, we can create the Babylon.js engine
constructor(canvas: HTMLCanvasElement) {
// Create engine
this._engine = new BABYLON.Engine(canvas);
// Create the scene
this._scene = new BABYLON.Scene(this._engine);
// Create the camera
this.camera = new BABYLON.ArcRotateCamera("camera", 0, 0, 30, BABYLON.Vector3.Zero(),
this._scene);
this.camera.attachControl(canvas, true);
// Create the light
this.light = new BABYLON.PointLight("light",new BABYLON.Vector3(20, 20, 20),
this._scene);
this.light.diffuse = new BABYLON.Color3(0, 1, 0);
this.light.specular = new BABYLON.Color3(1, 0, 1);
this.light.intensity = 1.0;
// Create the box
this.box = BABYLON.Mesh.CreateBox("cube", 5, this._scene);
}
}
调用 runRenderLoop 方法
让我们在类中添加一个方法来调用 runRenderLoop 方法:
public runRenderLoop(): void {
this._engine.runRenderLoop(() => {
this._scene.render();
});
}
这个场景与之前提到的图像完全相同——有一个盒子和绿色的灯光。
管理场景图
为了练习场景图,让我们创建一个方法,该方法将灯光设置为摄像机的子节点。该方法将灯光的位置设置为坐标(x=0,y=0,z=0)并将灯光的父节点设置为摄像机:
public setCameraParentOfLight(): void {
this.light.parent = this.camera;
}
摘要
在本章中,你学习了如何创建一个最小的 HTML 页面来处理 Babylon.js 引擎并绘制场景。你还学习了如何使用 Babylon.js 的内置节点,如灯光、摄像机和网格。
不要犹豫,使用内置网格如环面、环面结和球体进行练习,并玩转场景图和节点的属性,如位置。
在下一章中,你将学习场景是如何由设计师构建的,以及它们是如何使用 Babylon.js 导入的。你还将看到,使用 Babylon.js 创建场景与 3D 软件接口相当简单,因为所有工具都由 Babylon.js 解决方案提供(如导出器和导入器)。
下一章也将解释艺术家如何创建用于网络的 3D 场景,因为他们的问题与控制台和 PC 游戏等桌面软件不同。
第三章:在屏幕上创建、加载和绘制 3D 对象
在上一章中,你了解到 3D 对象,即网格,由一系列 3D 点(顶点缓冲区)和索引(索引缓冲区)组成,这些点创建出屏幕上绘制的三角形。为了创建如角色或建筑等复杂网格,3D 艺术家使用能够处理我们的顶点缓冲区和索引缓冲区的建模软件,包括许多易于创建的强大工具。
Babylon.js 解决方案附带 Blender (www.blender.org/) 和 3ds Max (www.autodesk.fr/products/3ds-max/overview) 的插件,这两款建模器是艺术家所熟知的。这些插件旨在为 3D 艺术家使用,用于将这些建模器中构建的场景导出为 Babylon.js 能够加载的格式。导出的文件是具有 .babylon 扩展名的 JSON 文件。有关格式的更多信息,请参阅 doc.babylonjs.com/generals/File_Format_Map_(.babylon)。
在本章中,我们将涵盖以下主题:
-
使用 Blender 导出场景
-
使用 3ds Max 导出场景
-
使用 Babylon.js 编程加载场景
使用 Blender 导出场景
Blender 是一个免费的开源建模器,使用专用工具创建 3D 模型。Blender 以其跨平台(在 Windows、Mac OS X 和 Linux 上运行)而闻名,并在开源领域被誉为更好的 3D 建模器(拥有强大的 3D 渲染、动画、纹理化等工具)。有关其功能和演示的更多信息,您可以访问 www.blender.org。
此外,Blender 允许开发者使用 Python 语言编写插件,这一特性使得 Babylon.js 团队能够开发 Blender 的导出器。实际上,Babylon.js 导出器将允许你直接在 Blender 中构建场景(包括网格、灯光、相机等),并轻松地将场景导出为 Babylon.js 格式,你可以在项目中加载。让我们看看以下步骤,看看你如何(仍然很容易)做到这一点。
为 Blender 设置 Babylon.js 导出器
在 Babylon.js GitHub 存储库中,你将找到一个包含 Blender 插件的文件夹,位于 Exporters/Blender/。io_scene_map 文件夹和 io_export_babylon.py 文件需要复制到 Blender 的 addons 文件夹中(通常位于 C:\Program Files\Blender Foundation\2.75\scripts\addons\):

文件复制完成后,只需激活插件,使其出现在导出器菜单中。激活后的结果如下所示:

在 Blender 中激活 Babylon.js 导出器
要激活插件,请按照以下步骤操作:
-
前往用户首选项...:
![在 Blender 中激活 Babylon.js 导出器]()
-
点击插件标签。
-
在搜索栏中搜索
babylon。 -
激活插件。
-
保存用户设置:
![在 Blender 中激活 Babylon.js 导出器]()
一旦插件被激活,插件就会出现在导出器菜单中。你现在可以导出 Babylon.js 的场景。
导出场景
让我们从默认创建的初始场景开始:一个立方体、相机和灯光。右键单击立方体以选择它。现在,让我们看看右侧的属性。(参考以下截图。)多亏了插件,有一个专门用于 Babylon.js 的区域。这些属性在导出时是针对 Babylon.js 框架的,并且不会干扰 Blender 的渲染(这是要告诉你的 3D 艺术家的一些有趣的事情):

放大属性:

对于网格(如立方体),属性如下:
-
使用平面着色:这是使用平面着色而不是平滑网格
-
检查碰撞:这是检查相机是否与网格碰撞
-
投射阴影:这是为了检查如果场景中存在阴影光(另一种类型的光),对象是否会在其他网格上投射阴影
-
接收阴影:这是检查如果场景中存在阴影光,对象是否接收来自投射阴影的其他对象的阴影
-
自动启动动画:如果网格是动画的,它会在开始时自动启动网格的动画
现在,在 Blender 中选择相机并查看以下截图:

放大属性:

对于相机,属性如下:
-
相机类型:这是相机的类型,例如自由相机(FPS)、弧形旋转相机等
-
检查碰撞:这是检查相机是否应该检查启用了检查碰撞属性的对象的碰撞
-
应用重力:如果重力应该吸引相机,则应用此选项
-
椭球体:这是围绕相机的半径,你可以在这里检查轴(X、Y和Z)上的碰撞
-
立体眼镜空间:这是立体相机(可以在相机类型属性中选择立体相机)
-
自动启动动画:如果相机是动画的,它会在开始时自动启动相机的动画
在 Blender 中选择灯光并查看以下截图:

放大属性:

对于灯光,属性如下:
-
阴影贴图类型:如果选择不是 None,灯光将变成阴影灯光。方差阴影贴图比标准阴影贴图更占用空间,但它们更真实。
-
阴影贴图大小:这相当于阴影的质量,必须是 2 的幂。1024 是阴影的良好质量。
-
自动启动动画:如果灯光是动画的,它将在开始时自动启动灯光的动画。
让我们添加一个平面来创建地面并导出场景:

一旦启动导出器,Blender 会显示一个带有左侧选项和文件浏览器(用于保存场景文件)的界面。选项如下:
-
仅导出当前层:Blender 可以处理多个图层。勾选此项以仅导出当前图层。
-
无顶点着色:这是禁用或启用顶点着色。关于这一点,将在后续章节中关于特殊效果的部分提供更多信息。

要导出场景,只需单击导出 Babylon.js 场景按钮。
一旦导出场景,不要犹豫使用 Sandbox 工具(www.babylonjs.com/sandbox/)来测试您的场景。只需将.babylon文件拖放到浏览器中。以下是结果:

使用 3ds Max 导出场景
对于 Blender,您可以在 Babylon.js GitHub 仓库的Exporters/3Ds Max/Max2Babylon*.zip中找到 3ds Max 插件(用 C#编写),其中*是导出器的版本。在存档中,您将找到两个文件夹:2013和2015,这是Max2Babylon导出器目前支持的 3ds Max 版本。
3ds Max 是 3D 艺术家用来创建 3D 场景的另一个工具,例如 Blender。3ds Max 是最著名的 3D 建模器,因为它被用于许多专业的 3D 视频游戏,并且所有 3D 艺术家都知道它。
安装 3ds Max 的 Babylon.js 导出器
一旦确定要使用的正确版本(2013或2015),只需将二进制文件复制/粘贴到bin/assemblies文件夹中,然后启动 3ds Max(通常位于C:\Program Files\Autodesk\3ds Max 201*\bin\assemblies\):

一旦启动 3ds Max,顶部菜单中就会出现一个名为Babylon的新菜单。此菜单用于将场景导出为.babylon文件。至于 Blender,您可以通过在 3ds Max 中右键单击对象来修改 Babylon.js 属性:

-
在工具栏上放大查看:
![安装 3ds Max 的 Babylon.js 导出器]()
-
在上下文菜单中放大查看:
![安装 3ds Max 的 Babylon.js 导出器]()
修改属性
Max2Babylon 插件由于右键点击增加了两个菜单:Babylon 属性和Babylon 动作构建器。
Actions Builder 是一个帮助在对象上创建动作的工具。让我们等待第七章,在对象上定义动作,以了解更多关于如何在场景中的对象上创建动作的信息。例如,Actions Builder 被用于在 Mansion 场景中的对象上创建动作,该场景可在babylonjs.com/demos/mansion/找到。
这些属性可以在网格、灯光、相机和场景中修改(未选择对象)。您将检索到与 Blender 相同的属性。只需点击Babylon 属性菜单。
注意
注意:包括物理和声音在内的某些属性将在第五章,在对象上创建碰撞和第六章,在 Babylon.js 中管理音频中分别介绍。

对于网格,有以下新属性:
-
不导出:如果网格应该导出到场景文件,则使用此选项。
-
显示边界框:这是用于调试的,显示一个代表网格外壳的线框框。
-
显示子网格边界框:与显示边界框选项类似,此选项将显示所选网格的子网格的边界框。
-
可拾取:如果 Babylon.js 应该能够拾取网格,则使用此选项。Babylon.js 允许你在场景中发射射线并找到射线与场景中网格的交点。勾选此选项以允许拾取。例如,网格拾取可以在用户点击时选择场景中的网格。
-
自动动画:如果对象(灯光、网格或相机)在开始时被动画化,则使用此选项。
-
从和到:这是动画开始时的起始帧和结束帧。
-
循环:如果动画应该循环,则使用此选项。
在 3ds Max 中选择灯光,让我们看看下面的截图:

对于灯光,属性与 Blender 相当:
-
不导出:如果灯光应该导出到场景文件,则使用此选项。
-
偏差:用于移除阴影效果可能产生的可能伪影。默认值在大多数情况下应该是足够的。
-
类型:与 Blender 类似,表示灯光是否应在场景中计算阴影。
方差和模糊方差阴影贴图比标准阴影贴图更复杂,但更真实。 -
模糊信息:当阴影贴图类型为模糊方差时使用。方差阴影贴图(VSM)比硬阴影更真实,并且可以模糊以抑制伪影。
在 3ds Max 中选择相机,让我们看看下面的截图:

对于相机,添加的属性如下:
-
速度:这是相机的速度。
-
惯性:Babylon.js 中的所有相机都带有惯性,这代表相机的惯性。这意味着当你移动 Babylon.js 中的相机时,你给它一个运动速度,这个速度会根据其惯性值减慢。(
0表示直接减慢,没有惯性,而大于0表示相机需要更多时间才能减慢。)
导出场景
让我们创建与 Blender 相同的场景:一个平面、一个立方体、一个相机和一个灯光:

要导出此场景,只需使用顶部菜单打开导出窗口,Babylon:

放大顶部菜单:

将会弹出导出窗口,让我们看一下以下截图:

选项如下:
-
将纹理复制到输出目录:勾选此选项以导出纹理(如果有任何网格至少有一个纹理)到场景文件的输出目录。换句话说,3ds Max 场景的所有纹理(应用于网格)都将复制到与导出的 Babylon.js 场景文件相同的文件夹中。
-
生成.manifest 文件:manifest 文件用于 Babylon.js 的离线模式。导出的场景可以通过互联网和浏览器缓存加载,以节省连接。manifest 文件告诉客户端场景是否已更改。如果已更改,它将通过互联网重新加载整个场景。
-
导出隐藏对象:在 3ds Max 中,对象可以被隐藏(不绘制)。此选项配置导出器以保留或导出隐藏对象。
-
自动保存 3ds Max 文件:每次导出时,都会保存 3ds Max 项目。
-
仅导出所选内容:这仅将所选对象(网格、相机、灯光等)导出到场景文件中。
-
生成二进制版本:这会将场景导出到一个二进制文件(增量加载)。
选择文件位置,就像为 Blender 保存 Babylon.js 场景时一样,然后点击导出。3ds Max 插件还提供通过启动默认浏览器并使用本地服务器来导出和运行场景的功能。结果如下:

使用 Babylon.js 编程加载场景
要使用 TypeScript 加载场景,Babylon.js 为你提供了一个名为BABYLON.SceneLoader的类。这个类包含静态方法,允许你加载场景(创建新的)、附加场景和加载网格。
基本上,作为一个开发者,你会使用这些方法来加载文件。.Load 方法为你创建一个新场景并加载一切(网格、光源、粒子系统、相机等),并返回新场景。.Append 方法接受一个现有场景作为参数,并将其附加到现有场景上(用于混合多个场景)。最后,.ImportMesh 方法仅导入网格、骨骼(参考第九章创建和播放动画;
第一个参数,`./`,是场景文件文件夹。第二个参数,`awesome_scene.babylon`,是要加载的场景文件名。最后,最后一个参数是引擎。实际上,加载器需要一个引擎来创建一个新场景。
**附加场景**:将场景附加到另一个场景的方式几乎相同;只需调用 `BABYLON.SceneLoader.Append` 方法而不是 `.Load`。
以下是一个示例:
```js
BABYLON.SceneLoader.Load("./", "another_awesome_scene.babylon", scene);
前两个参数与 .Load 方法等效。最后一个场景参数是原始场景。实际上,由加载器创建的新场景将与原始场景合并(附加)。
导入网格:在这里,方法不同,但仍然位于 SceneLoader 类中的 .ImportMesh 方法。一个场景文件(.babylon)包含范围 [0, n] 内的多个网格。默认情况下,.Load 和 .Append 方法导入场景文件中定义的所有网格。使用 .ImportMesh,你可以通过给出它们的名称来指定加载器导入的网格。
以下是一个示例:
BABYLON.SceneLoader.ImportMesh("", "./", "awesome_meshes.babylon", scene);
第一个参数,名为 meshesNames,是 any 类型。在这里,空字符串告诉加载器导入所有网格。要指定要导入的网格,只需将它们的名称添加到一个数组中,例如,["awesome_mesh1", "awesome_mesh2"]。第二个和第三个参数与 .Load 和 .Append 方法等效。最后一个参数是要导入网格的场景。
回调
这三个方法,.Load、.Append 和 .ImportMesh,在成功、进度和错误上提供回调,默认为 null。它们用于控制加载过程。
对于 .Load 和 .Append,回调是相同的。
加载和附加
让我们看看以下示例:
// Same for append
BABYLON.SceneLoader.Load("./", "awesome_scene.babylon", engine,
(scene: BABYLON.Scene) => {
// Success, with "scene" as the new scene created by the loader
// We can load another scenes here
}, () => {
// progress
}, (scene: BABYLON.Scene) => {
// error, with "scene" as the new scene created by the loader
}
成功和错误回调提供了由加载器创建的新场景。实际上,如果你查看 Babylon.js 的源代码,你会看到 .Load 方法只是通过创建一个新场景来调用 .Append 方法。
导入网格
让我们看看以下示例:
BABYLON.SceneLoader.ImportMesh("", "./", "awesome_meshes.babylon", scene,
(meshes, particleSystems, skeletons) => {
// Success, the callback provide access to the new imported meshes,
// particle systems and skeletons.
}, () => {
// Progress
}, (scene: BABYLON.Scene, e: any) => {
// Error callback, access the error with "e"
});
在成功回调中,网格、particleSystems和skeletons参数分别是BABYLON.Mesh、BABYLON.ParticleSystem和BABYLON.Skeletons的数组,并且只包含添加的对象:
-
网格参数包含从场景文件中导入的所有网格。
-
particleSystems参数包含所有导入的粒子系统(烟雾、雨等)。 -
骨骼参数包含所有导入的骨骼。骨骼是用于创建动画的实体,并与网格相关联。让我们以一个角色为例:骨骼代表角色的骨架,用于创建腿部、手部、头部等动作。最后,角色可以通过骨骼行走和奔跑。
概述
现在,所有工具都已安装并准备好使用。不要犹豫,查看示例文件以实验场景加载器。有一些示例(场景)文件包括一个头骨和用 3ds Max 构建的场景。自然地,你会在代码中找到如何使用场景加载器,然后获得适当的架构。
作为下一步,这是一个很好的机会来介绍材料和如何使用这些材料自定义 3D 对象的的外观。当然,Babylon.js 已经为你提供了一个默认的材料,这使得创建材料的工作比你自己创建材料要容易。
第四章:使用材料定制 3D 对象外观
我们称之为材料的对象在 3D 渲染中至关重要。它们被用来在屏幕上渲染对象以及它们的渲染方式。这意味着材料被用来应用纹理和变换,例如波浪,例如管理透明度等等。换句话说,材料是用于轻松定制 3D 对象外观的接口。
以下是一个使用 Babylon.js 的示例:
myMesh.material = new BABYLON.StandardMaterial("material", scene);
// Done! Now customize everything you want here
myMesh.material.diffuseTexture = diffuseTexture;
myMesh.material.transparency = 0.5;
// Etc.
我们在本章中将涵盖以下主题:
-
讨论材料背后的神奇理论
-
使用 Babylon.js 标准材料
-
使用材料与纹理
讨论材料背后的神奇理论
材料被用来定制 3D 对象的外观;然而,在其背后隐藏着两个被称为着色器的程序。材料的目标是隐藏这种着色器的概念,并简单地与材料对象中的值一起工作。换句话说,材料中的值可以是物体的发射颜色、漫反射颜色、透明度等级等等。
事实上,要进一步探讨理论,有几种类型的着色器,如下列所示:
-
顶点着色器:这直接作用于 3D 对象几何形状。
-
像素着色器:这直接作用于屏幕上的像素。
-
几何着色器(在 WebGL 中不可用):这作用于 3D 对象几何形状;然而,在这里它能够根据顶点着色器的输出直接向 3D 对象的几何形状中添加多边形。
-
计算着色器(在 WebGL 中不可用):这不能直接在 3D 对象和像素上工作。它只是用来使用 GPU 而不是 CPU 计算一些用户定义的数据。例如,计算着色器将一个纹理作为输入(你可以将其视为一个大矩阵)并将程序的输出结果输出到另一个纹理中。计算着色器被高度用于计算逼真的海洋波浪或神经网络。
-
细分着色器(在 WebGL 中不可用):这允许我们在 GPU 上直接计算细节级别(LOD),而不是在 CPU 上(在现代渲染管线中如 Direct3D 11 和 OpenGL 4.0 中相对较新)。
在我们的案例和 WebGL 中,只有顶点和像素着色器被用来在屏幕上渲染 3D 场景;然而,在将来看到其他类型的着色器在 WebGL 中实现并不会令人惊讶。
理论
我们在前几章中创建的网格包含顶点缓冲区和索引缓冲区。顶点缓冲区描述了顶点的 3D 位置。主要问题是如何将 3D 空间中的顶点投影到 2D 空间,即屏幕空间。实际上,为了在屏幕上绘制元素,GPU 通过程序将顶点位置转换成屏幕上的 2D 位置。这些程序是顶点着色器和像素着色器。它们是用一种名为 GLSL 的语言编写的,这是与 WebGL 一起使用的 OpenGL 着色语言。
顶点着色器
顶点着色器用于转换顶点位置。它由 GPU 为每个顶点执行,可以用于无限数量的功能。例如,为了在大型海洋平面上创建波浪,我们将使用顶点着色器计算每个顶点的波浪函数,而不是使用在 CPU 端执行的 TS 代码——把工作留给真正的工人。
一旦顶点着色器在屏幕上计算出一个三角形(一个面),多亏了索引缓冲区,像素着色器就会被调用,以照亮当前渲染的当前网格(当前面)当前三角形(当前面)使用的像素。
像素着色器
像素着色器具有与顶点着色器相同的结构;它使用相同的语言(GLSL)编写,并且为屏幕上当前三角形使用的每个像素调用。像素着色器的主要功能是返回一个以 RGBA 格式计算的颜色,该颜色可以从用户定义的值或直接从纹理中确定。
使用 Babylon.js 标准材质
Babylon.js 允许你创建材质,这意味着它可以创建具有自定义着色器的自定义材质;然而,它提供了一个已经开发好的标准材质,该材质设计为可以通过许多自定义来适应。
实际上,当你向 Babylon.js 场景添加灯光时,灯光属性,如漫反射颜色,会被发送到场景的材质中,以计算网格上的光贡献。
标准材质及其常见属性
在 Babylon.js 中,每个网格都有一个材质,网格可以共享相同的材质。使用 Babylon.js 创建标准材质并将其分配给网格,就像编写以下代码一样简单:
myMesh.material = new BABYLON.StandardMaterial("materialName", scene);
通过给材质命名以及指定你想要添加材质的场景,简单地创建一个新的StandardMaterial对象,并将它分配给网格的the.material属性。一旦材质创建完成,你就可以开始修改值并自定义网格外观。默认材质默认看起来类似于以下图像:

- 漫反射颜色:漫反射颜色表示物体的颜色,如下所示:
myMaterial.diffuseColor = new BABYLON.Color3(0, 0, 1); // Blue

- 镜面颜色:镜面颜色表示物体反射的光的颜色(这与漫反射颜色混合),如下所示:
myMaterial.specularColor = new BABYLON.Color3(1, 0, 0); // Red

- 发光颜色:发光颜色表示物体发出的颜色(这与镜面颜色和漫反射颜色混合),如下所示:
myMaterial.emissiveColor = new BABYLON.Color3(0, 1, 0); // Gre en

- 管理透明度:为了管理透明度,标准材质在[0, 1]区间提供了一个.alpha 属性,如下所示:
myMaterial.alpha = 0.2; // 80% transparent

使用雾效
标准材质允许你应用与当前渲染场景相关的雾效。要在一个对象上启用雾,只需在材质和场景上设置 the.fogEnabled 属性为 true,如下所示:
myMaterial.fogEnabled = true;
scene.fogEnabled = true;
让我们从以下场景开始:

场景中的雾可以自定义。以下列出了几种雾的类型:
-
线性雾 (
BABYLON.Scene.FOGMODE_LINEAR) -
指数 (
BABYLON.Scene.FOGMODE_EXP) -
指数 2(比之前更快)(
BABYLON.Scene.FOGMODE_EXP2)
在线性模式下,可以设置两个属性——scene.fogStart 和 scene.fogEnd。以下是一个示例:
scene.fogMode = BABYLON.Scene.FOGMODE_LINEAR;
scene.fogStart = 10;
scene.fogEnd = 100;

使用以下方法更改雾的颜色:
scene.fogColor = new BABYLON.Color3(1, 1, 1); // White

在指数模式下,可以设置 scene.fogDensity 属性。
以下是一个示例:
scene.fogMode = BABYLON.Scene.FOGMODE_EXP;
scene.fogDensity = 0.02;

使用材质使用纹理
本章是介绍纹理使用的合适位置。纹理是图像(.png、.jpeg 等),图形库能够将其应用到网格上。Babylon.js 处理了多种纹理方法,例如视频纹理、立方体纹理等。现在,让我们解释如何使用材质来使用纹理。
加载并应用纹理
如你可能已经猜到的,使用 Babylon.js 加载并将纹理应用到网格上可以很容易。标准材质提供了一种方法,就像颜色一样,可以应用漫反射纹理(例如,镜面、发射和环境纹理)。只需将 .diffuseTexture 属性设置为纹理的引用,如下所示:
myMaterial.diffuseTexture = myTexture;
要创建 myTexture 对象,让我们看看以下所示的 BABYLON.Texture 类:
var myTexture = new BABYLON.Texture("path_to_texture.png", scene);
现在已经完成了。漫反射纹理现在将被应用到网格上。以下是一个示例,考虑示例文件中的 floor_diffuse.png 图像:

现在,让我们玩一下纹理的属性。在示例文件中,有一个包含 alpha 通道(透明度)的 cloud.png 纹理。如果你应用云纹理,结果如下所示:

黑色部分代表 alpha 通道,看起来像污染球体渲染部分的伪影。实际上,纹理是通过像素着色器应用到网格上的,你必须告诉着色器纹理可以包含 alpha 通道。这项工作可以通过纹理的 .hasAlpha 属性来完成,如下所示:
cloudTexture.hasAlpha = true;
结果如下所示:

你可以看到球体的背面没有被渲染。这是由于图形库的优化,因为在大多数情况下,渲染摄像机看不到的背面三角形的三角形并不是必需的。这被称为“背面裁剪”。
要禁用背面裁剪,只需将材质的.backFaceCulling属性设置为 false,如下所示:
myMaterial.backFaceCulling = false;
结果如以下图像所示:

由于纹理是通过像素着色器应用的,因此可以通过材质设置许多参数,例如纹理的垂直和水平缩放。让我们从以下纹理开始:

纹理的.uScale和.vScale属性允许我们向网格应用重复图案。1.0 的默认值意味着纹理在网格上重复一次。让我们看看以下5.0的结果:
myTexture.uScale = 5.0;
myTexture.vScale = 5.0;

至于纹理缩放,你可以创建一个偏移量来根据.uScale和.vScale属性调整纹理在网格上的位置,如下所示:
myTexture.vScale = 0.2;
myTexture.uScale = 0.2;

凹凸贴图
在 Babylon.js 处理的纹理方法中,我们可以找到凹凸贴图技术。这项技术用于在表面上创建凸起,并使用两种不同的纹理(漫反射纹理和法线纹理)创建更逼真的表面。这项技术因其不修改网格的原始几何形状,而仅通过着色器使用两种纹理进行计算而闻名。
漫反射纹理和法线纹理由艺术家提供,并由一些艺术家工具构建,以帮助生产。以下是一个法线纹理的示例:

实际上,像素着色器应用漫反射纹理并修改根据法线纹理的像素。
让我们看看法线贴图效果。这是没有法线贴图的情况:

使用法线贴图,图像将类似于以下:

使用 Babylon.js 应用法线贴图效果与应用漫反射纹理一样简单。让我们用以下两行代码来解释:
myMaterial.diffuseTexture = new BABYLON.Texture("diffuse.png", scene);
myMaterial.bumpTexture = new BABYLON.Texture("normal.png", scene);
材料内部效果能够根据属性来调整渲染。然后,如果设置了.bumpTexture属性,效果将计算凹凸贴图技术。
高级纹理
Babylon.js 的标准材质允许我们应用反射纹理。如果设置了此纹理,材质的内部效果将使用此纹理创建反射效果。
立方体贴图
立方体贴图是特殊的。使用立方体贴图和反射纹理的一个有趣用途是天空盒网格。天空盒由六个面组成,通常用于重现场景周围的环境,通常是天空。为了处理六个面,立方体贴图将加载六个纹理并将它们应用到网格上。
让我们使用 Babylon.js 加载一个立方体贴图,如下所示:
var cubeTexture = new BABYLON.CubeTexture("skybox/TropicalSunnyDay", scene);
参数如下所示:
-
六个纹理的路径。在这个例子中,每个纹理名称必须以
TropicalSunnyDay开头,后跟立方体的六个方向:nx、ny、nz、px、py和pz。 -
添加立方体纹理的场景。
注意
注意:立方体纹理的默认扩展名是 .jpg。您可以通过传递一个包含字符串的数组作为第三个参数来了解确切的扩展名。以下是一个例子:
var cubeTexture = new BABYLON.CubeTexture("skybox/TropicalSunnyDay", scene, ["_px.png", "_py.png", "_pz.png", "_nx.png", "_ny.png", "_nz.png"]);
现在,让我们创建一个天空盒。天空盒是一个立方体,其背面裁剪被禁用,因为摄像机将在立方体内,如下所示:
var skybox = BABYLON.Mesh.CreateBox("skybox", 300, scene);
var skyboxMaterial = new BABYLON.StandardMaterial("skyboxMaterial", scene);
skyboxMaterial.backFaceCulling = false;
skyboxMaterial.reflectionTexture =
new BABYLON.CubeTexture("skybox/TropicalSunnyDay", scene);
在结果中,立方体纹理被应用于立方体;然而,我们可以在立方体纹理部分之间找到一些伪影:

这些伪影是由于纹理的坐标模式引起的。实际上,纹理是通过网格几何体提供的 2D 坐标应用于网格的。至于顶点缓冲区和索引缓冲区,几何体包含一个通常称为 UVs 缓冲区的坐标缓冲区,存在几种方法来应用特定的坐标模式。在这种情况下,应该应用天空盒坐标模式。这可以通过the.coordinatesMode属性实现,如下所示:
myTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
一旦设置坐标模式,结果看起来很棒,如下面的图像所示:

天空盒外部的结果如下所示:

镜面纹理
让我们用一个新的纹理类型镜面纹理来解释反射纹理的使用。使用 Babylon.js,可以使用镜面纹理来反射世界。镜面纹理是特别的,因为它是由 Babylon.js 创建的,可以将场景渲染成纹理。在反射纹理后面,使用了一种新的纹理类型:渲染目标纹理。渲染目标纹理用于直接将网格渲染到纹理中,以便进一步使用。现在,让我们创建一个镜面纹理,如下所示:
var mirror = new BABYLON.MirrorTexture("mirror", 512, scene);
参数如下所示:
-
纹理名称:Babylon.js 创建的纹理的名称。
-
纹理大小:纹理越大,镜面纹理越清晰。不幸的是,纹理越大,对性能的影响也越大。512 或 1024(512 x 512 或 1024 x 1024 像素)是镜面纹理的好大小。
-
添加纹理的场景。
现在,为了反射世界,镜面纹理需要一个最后一个参数:镜面平面。以地面为例,我们希望地面反射其自身之上的世界:
mirror.mirrorPlane = BABYLON.Plane.FromPositionAndNormal(
new BABYLON.Vector3(0, 0, 0), new BABYLON.Vector3(0, -1, 0));
一个平面有四个属性:a、b、c和d。前三个属性代表法向量,其中d是到原点的距离。FromPositionAndNormal静态方法是一个辅助方法来创建一个平面。参数如下:
-
平面的位置向量。在这里,原点(
x=0、y=0、z=0)。 -
在示例文件中,平面的位置是(
x=0、y=-5、z=0)。因此,平面的位置必须是(x=0、y=5、z=0)。 -
平面的法向量。在这里,原点是(
x=0,y=-1,z=0)。
然后,代码行变成了以下内容:
var mirror = new BABYLON.MirrorTexture("mirror", 512, scene);
mirror.mirrorPlane = BABYLON.Plane.FromPositionAndNormal(
new BABYLON.Vector3(0, 0, 0),
new BABYLON.Vector3(0, -1, 0));
myMaterial.reflectionTexture = mirror;
要在镜面纹理中配置渲染目标纹理,我们必须提供一个BABYLON.AbstractMesh数组。这个数组用于仅渲染数组中索引的网格。这个数组已经由镜面纹理创建,属性名为.renderList。然后,镜面纹理将仅暴露数组中添加的网格,如下所示:
mirror.renderList.push(myMesh1);
mirror.renderList.push(myMesh2);
// Etc.
在地面上(与平面处于相同位置)和反射球体上的结果如下所示:

总结
在本章中,你了解了材质是什么,后台发生的理论,以及如何使用 Babylon.js 的标准材质。你看到在 Babylon.js 中使用材质也很简单。
示例文件倾向于重现本章中看到的概念:颜色、透明度、纹理、雾效、背面裁剪等。现在,你可以使用材质并自定义网格的外观。
作为具体例子,材质在所有 3D 场景中都被高度使用,并由 3D 艺术家配置:如果我们以 Michel Rousseau 制作的 Babylon.js 场景为例,他在其中使用了两个网格和两种不同的材质来重现以下图像:

第一个网格是花瓶的主体,应用了标准材质,第二个网格代表余烬,应用了另一种标准材质。每个材质都配置了不同的漫反射纹理。如果我们设置主体为isVisible = false,我们可以看到余烬在现实中的样子,如下所示:

最后,在这个场景中渲染的所有网格都使用标准材质,配置了不同的值和纹理,最终看起来类似于以下图像:

在下一章中,你将使用 FPS 相机和碰撞管理创建你的第一个场景。你将学习如何管理对象之间的碰撞以及如何使用 Babylon.js 管理物理。
第五章。在对象上创建碰撞
上一章介绍了 3D 编程基础和 Babylon.js。你现在理解了完整的流程,可以轻松地使用设计师提供的材料和网格创建和自定义你的场景。
在本章中,让我们来玩一下游戏本身,通过创建碰撞和物理模拟与场景中的对象进行交互。如果你想在场景中行走而不穿越墙壁,碰撞对于增加场景的真实感非常重要。此外,让我们通过 Babylon.js 引入物理模拟,并最终看看将这两个概念集成到场景中有多容易:
-
检查场景中的碰撞
-
模拟物理
检查场景中的碰撞
从概念出发,配置和检查场景中的碰撞可以不使用数学符号。我们都有重力和椭球体的概念,即使“椭球体”这个词不一定熟悉。
在 Babylon.js 中碰撞是如何工作的?
让我们从以下场景开始(一个相机、光源、平面和盒子):

目标是防止场景中当前活动的相机穿越场景中的对象。换句话说,我们希望相机保持在平面上方,并在与盒子碰撞时停止前进。
为了执行这些操作,Babylon.js 提供的碰撞系统使用一个碰撞器。一个碰撞器可以用简单对象的边界框来表示,它看起来类似于以下图片:

为了信息,边界框简单地表示网格顶点的最小和最大位置,并且由 Babylon.js 自动计算。
如第三章中所述,“创建、加载和绘制 3D 对象在屏幕上”,网格由通过索引缓冲区连接在一起的顶点组成,最终构建成三角形;更确切地说,碰撞是基于这些三角形,并由 Babylon.js 自动管理/计算。这意味着你不需要做任何特别复杂的事情来配置对象的碰撞。
总结来说,所有碰撞都是基于每个网格的三角形,以确定是否应该阻止相机。
注意
你还会看到物理引擎使用相同类型的碰撞器来模拟简单网格(边界框)的物理。
在这种情况下,复杂的工作在于设计师。他们必须优化所有网格以与碰撞良好配合。这意味着他们必须借助 3D 模型器将大网格分割成多个或子网格。
实际上,在渲染场景时,Babylon.js 的碰撞系统将根据相机的全局位置测试场景中每个网格是否应该进行碰撞测试。这就是 3D 引擎倾向于优化碰撞的原因,因为主要问题在于碰撞是在 CPU 端计算的。
例如,一些 3D 引擎提供了一种通过提供特定类型的碰撞来配置网格碰撞的方法;即使网格很复杂,你也可以选择是否想要使用边界框作为碰撞体(以节省 CPU 性能)或直接使用网格的三角形(实现真实碰撞)。
配置场景中的碰撞
让我们通过 Babylon.js 的碰撞引擎本身来练习。你会发现引擎特别隐藏,因为你只需要在场景和对象上启用检查。
首先,配置场景以启用碰撞,然后唤醒引擎。如果以下属性为 false,则 Babylon.js 将忽略所有后续属性,并且碰撞引擎将处于 待机 模式。然后,可以很容易地在场景中启用或禁用碰撞,而无需修改更多属性,如下所示:
scene.collisionsEnabled = true; // Enable collisions in scene
接下来,配置相机以检查碰撞。碰撞引擎将检查所有已启用碰撞的渲染相机。在这里,我们只需要配置一个相机:
camera.checkCollisions = true; // Check collisions for THIS camera
为了完成,将每个网格的 .checkCollisions 属性设置为 true 以激活碰撞(这里指的是平面和盒子),如下所示:
plane.checkCollisions = true;
box.checkCollisions = true;
现在,碰撞引擎将检查场景中平面和盒子网格上的碰撞。
如果你只想在平面上启用碰撞,并希望相机穿过盒子,你必须将盒子的 .checkCollisions 属性设置为 false,如下所示:
plane.checkCollisions = true;
box.checkCollisions = false;
配置重力和椭球体
重力
在上一节中,相机检查了平面和盒子的碰撞;然而,这并没有提交给一个著名的力,即 重力。为了丰富场景中的碰撞,你可以应用重力力,例如,从楼梯上下来。
首先,通过将 .applyGravity 属性设置为 true 来在相机上启用重力,如下所示:
camera.applyGravity = true; // Enable gravity on the camera
最后,通过将 BABYLON.Vector3 设置为场景的 .gravity 属性来自定义重力方向,如下所示:
scene.gravity = new BABYLON.Vector3(0.0, -9.81, 0.0); // To stay on earth
当然,空间中的重力应该如下所示:
scene.gravity = BABYLON.Vector3.Zero(); // No gravity in space
不要犹豫,尝试调整值以调整重力到你的场景参考系。
椭球体
最后一个参数是用来丰富场景中碰撞的相机椭球体。椭球体表示相机在场景中的尺寸。换句话说,它根据椭球体的 x、y 和 z 轴调整碰撞(椭球体由 BABYLON.Vector3 表示)。
例如,相机必须测量 1.8 米(y 轴)并且与 x(侧面)和 z(前方)轴的最小碰撞距离必须是 1 米。然后,椭球体必须设置为(x = 1,y = 1.8,和 z = 1)。简单来说,设置相机的 .ellipsoid 属性,如下所示:
camera.ellipsoid = new BABYLON.Vector3(1, 1.8, 1);
注意
相机的椭球体的默认值是(x = 0.5,y = 1.0,和 z = 0.5)
至于重力,不要犹豫,根据场景的比例调整 x、y 和 z。
模拟物理
物理模拟与碰撞系统有很大不同,因为它不是发生在相机上,而是发生在场景本身的物体上。换句话说,如果一个盒子启用了(并且配置了)物理,那么这个盒子将会与场景中的其他网格交互,并尝试表示真实的物理运动。
例如,让我们考虑一个空中的球体。如果你将物理定律应用于球体,球体将会落下,直到它与另一个网格碰撞,根据给定的参数,它将在场景中弹跳和滚动。
注意
示例文件重现了场景中间的盒子上的球体落下的行为。
在 Babylon.js 中启用物理
在 Babylon.js 中,物理模拟只能通过插件来完成。目前有两个可用的插件:Cannon.js 框架和 Oimo.js 框架。这两个框架包含在 Babylon.js 的 GitHub 仓库中的 dist 文件夹内。
每个场景都有自己的物理模拟系统,可以通过以下行来启用:
var gravity = new BABYLON.Vector3(0, -9.81, 0);
scene.enablePhysics(gravity, new BABYLON.OimoJSPlugin());
// or
scene.enablePhysics(gravity, new BABYLON.CannonJSPlugin());
.enablePhysics(gravity, plugin) 函数接受以下两个参数:
-
场景中作用在对象上的重力力
-
要使用的插件:
-
Oimo.js:
new BABYLON.OimoJSPlugin() -
Cannon.js:
new BABYLON.CannonJSPlugin()
-
要在场景中禁用物理,只需调用 .disablePhysicsEngine() 函数,如下所示:
scene.disablePhysicsEngine();
模拟器
一旦在场景中启用了物理模拟,就可以配置场景网格的物理属性(或物理状态)。要配置网格的物理属性,BABYLON.Mesh 类提供了一个 setPhysicsState(impostor, options) 函数。
参数如下:
-
impostor:每种网格类型都有其对应的模拟器,根据其形状。例如,一个盒子可能会滑动,而一个球体会滚动。有几种类型的模拟器。 -
options:这些选项定义了在物理方程中使用的值。包括质量、摩擦和恢复。
让我们考虑一个质量为 1 的名为 box 的盒子,并设置其物理属性,如下面的代码片段所示:
box.setPhysicsState(BABYLON.PhysicsEngine.BoxImpostor, { mass: 1 });
就这些了,现在盒子已经配置好了,可以通过物理方程与其它配置好的网格进行交互。让我们想象一下,盒子在空中,将会落下直到与另一个配置好的网格碰撞。
现在,让我们考虑一个质量为 2 的名为 sphere 的球体,并设置其物理属性,如下面的代码片段所示:
sphere.setPhysicsState(BABYLON.PhysicsEngine.SphereImpostor, { mass: 2 });
您会注意到,球体,作为一种特殊的网格,有自己的模拟器(SphereImpostor)。与盒子相比,插件的物理方程将使球体滚动,而盒子将在其他网格上滑动。
根据它们的重量,如果盒子和球体相撞,那么球体将倾向于更强烈地推动盒子。
在 Babylon.js 中可用的模拟器如下:
-
盒子模拟器:
BABYLON.PhysicsEngine.BoxImpostor -
球体模拟器:
BABYLON.PhysicsEngine.SphereImpostor -
平面模拟器:
BABYLON.PhysicsEngine.PlaneImpostor -
圆柱形模拟器:
BABYLON.PhysicsEngine.CylinderImpostor
注意
事实上,在 Babylon.js 中,根据 Cannon.js 和 Oimo.js 插件,盒子、平面和圆柱形模拟器是相同的。Babylon.js 的物理引擎中还存在其他尚未支持(但可能很快就会支持)的物理体类型;例如,软体(如因风而变形的旗帜)和流体体(根据物体的质量和水的特性,如清澈或水状,模拟物体在水表面的行为)。目前只支持刚体。
无论 impostor 参数如何,options 参数都是相同的。您可以通过提供以下参数来自定义网格的物理状态:
-
质量:这是网格在世界的质量。网格越重,停止其运动就越困难。
-
摩擦系数:这表示与网格接触的阻力。换句话说,这表示网格的滑溜程度。为了给您一个顺序,冰的摩擦系数等于
1.0。我们可以确定摩擦系数在[0, 1]范围内。 -
恢复系数:这表示网格将如何反弹到其他物体上。考虑乒乓球及其桌面;如果桌面的材料是地毯,恢复系数将很小。然而,如果桌面的材料是玻璃,恢复系数将是最大的。恢复系数的实际范围是
[0, 1]。
在示例文件中,这些参数被设置,如果您玩弄它们,您会看到这三个参数在物理方程中相互关联。
对网格施加力
在任何时刻,您都可以向配置好的网格施加新的力或冲量。以爆炸为例,一个盒子位于坐标 (x = 0, y = 0, z = 0),爆炸发生在盒子上方坐标 (x = 0, y = -5, z = 0)。在现实生活中,盒子会被推上去;通过调用 BABYLON.Mesh 类提供的 applyImpulse(force, contactPoint) 函数,这个动作是可能的。
一旦网格配置了其选项和模拟器,您就可以在任何时刻调用此函数来对对象施加力。此函数的参数如下:
-
force:这表示 x、y 和 z 轴上的力 -
接触点:这表示力在 x、y 和 z 轴上的起点。
例如,爆炸只在y轴上产生力(为什么不呢?),其值为10(10是一个任意值),并且其原点位于坐标(x = 0,y = -5,z = 0),如下所示:
mesh.applyImpulse(new BABYLON.Vector3(0, 10, 0), new BABYLON.Vector3(0, -5, 0));
一旦对网格应用了冲量(只应用一次),箱子将被推起,并会根据其物理参数(质量、摩擦和恢复)下落。
在 Blender 中配置
回到艺术家使用的 3D 软件,如 Blender 和 3ds Max,也可以配置碰撞和物理模拟。
Blender 中的碰撞
从这个场景开始,碰撞的配置变得容易,如下面的截图所示:

对于第三章, 在屏幕上创建、加载和绘制 3D 对象,让我们聚焦于 Babylon.js 属性,如下面的图像所示:

首先,点击选项按钮(在顶部),然后对于选中的对象,在这里是立方体,通过勾选检查碰撞复选框来启用碰撞。对于每个网格,勾选复选框以启用碰撞。
现在,让我们在 Blender 中配置相机。在场景中选择相机,点击相机选项(在顶部),如果你想要应用重力,请勾选启用碰撞和应用重力复选框,如下面的图像所示:

要配置相机的椭球体,两个复选框下方有一个BABYLON.Vector3对话框可用。你可以按照相同的顺序配置相机椭球的x、y和z值。
最后,如果你想为相机应用重力,最后要配置的参数是场景的重力。首先,点击场景菜单(在顶部)并修改重力值。在 Blender 中,y轴和z轴是互换的,因此,Blender 中重力的z轴对应于 Babylon.js 中的y轴,如下面的图像所示:

现在,你可以导出 Blender 项目并测试它;重力将被应用,相机将检查立方体和平面的碰撞。
在 Blender 中配置物理模拟
至于碰撞系统,你可以在 Blender 中配置网格以模拟物理。你会找到模拟者、质量、摩擦和恢复等概念,并且所有这些参数都可以在 Blender 中配置。一旦选择了一个网格,点击物理选项(在顶部)并点击刚体按钮(只支持刚体),如下面的图像所示:

一旦点击按钮,将出现更多选项。其中一些选项与 Blender 相关;然而,只有其中一些会吸引我们的注意,如下所示:

您将找到三个参数(选项):质量、摩擦和弹性(弹性表示恢复)。在 Blender 中,模拟者被称为形状,您将找到相同的模拟者:盒子和球体。
在 3ds Max 中配置碰撞
对于 Blender,您也可以在 3ds Max 中配置碰撞。要启用对象的碰撞,只需打开网格的 Babylon 属性,如图所示:

要在网格上启用碰撞,只需勾选以下图像所示的检查碰撞复选框:

对于 Blender,您还必须配置相机以检查碰撞并应用重力(或不应用)。对于网格,打开相机的 Babylon 属性并勾选检查碰撞和应用重力复选框。您还可以以相同的顺序在x、y和z轴上配置相机的椭球体,如图所示:

最后,打开 3ds Max 场景的 Babylon 属性,以配置重力,如果您想将重力应用于相机,操作如下:

在 3ds Max 中配置物理模拟
与 Blender 不同,在 3ds Max 中,物理属性位于 Babylon 网格属性中。只需选择一个网格并打开 Babylon 属性,您会发现与以下截图所示相同的概念:模拟者、质量、摩擦和恢复,如下所示:

摘要
您现在可以配置场景的碰撞并模拟物理。无论是通过代码还是通过艺术家,您都可以理解流程,以便使您的场景更加生动。不要犹豫去操作示例文件。有三个重要的函数,如下所示:
-
createScene(): 此函数创建网格和材质 -
createCollisions(): 此函数配置场景、相机和网格上的碰撞 -
createPhysics(): 此函数配置网格的物理属性并应用冲量
在下一章,是时候通过添加音频轨道使您的场景更加生动了。您可以通过代码或借助 3ds Max 和 Blender 中的声音支持,由艺术家帮助,为您的场景添加声音,无论是有空间化的还是无空间化的。
第六章:在 Babylon.js 中管理音频
在上一章中,你通过添加碰撞检查和物理模拟来为场景添加动态效果。另一个重要功能是处理场景中的声音,并最终使场景更加生动。本章不仅解释了使用 Babylon.js 进行声音管理以创建配乐声音,还涵盖了空间化声音(3D)。在本章中,我们将涵盖以下主题:
-
播放 2D 声音
-
播放 3D 声音
播放 2D 声音
Babylon.js 框架提供了一个基于 WebAudio 的音频引擎。它允许你使用 Babylon.js 团队为你开发的工具轻松添加 2D 和 3D 声音。
创建 2D 声音
Babylon.js 框架提供了一个 BABYLON.Sound 类。此类允许你为场景创建和管理 2D 和 3D 声音。要添加声音,你需要做的只是创建一个新的 BABYLON.Sound 对象,如下所示:
var sound = new BABYLON.Sound("sound_name", "sound_file", scene);
现在,你可以访问 .play、.pause 和 .stop 等方法。
事实上,声音是异步加载的,因此你无法在创建新的声音对象后立即调用 sound.play()。这就是为什么 BABYLON.Sound 构造函数在场景之后提供了一个 readyToPlayCallback 参数,以便处理加载过程。要加载后播放声音,只需设置 readyToPlayCallback 参数,如下所示:
var sound = new BABYLON.Sound("sound_name", "sound_file", scene, () => {
sound.play();
});
幸运的是,开发者考虑到了这种行为,并提供了一个名为 options 的最后一个参数。此参数允许你自动设置默认行为,而不是在准备播放回调中管理它们。options 参数是可选的,其外观类似于以下内容:
var sound = new BABYLON.Sound("sound_name", "sound_file", scene, () => {
}, {
loop: true, // [Boolean] plays sounds in loop mode
autoPlay: true, // [Boolean] plays the sound after loading
volume: 1.0, // Between [Number [0, 1]] volume of sound
playbackRate: 1.0 // [Number 1.0] playing speed
spatialSound: false, [Boolean] is a 3D sound
maxDistance: 100, [Boolean] maximum distance of 3D sound
});
autoPlay 参数会在加载后自动播放声音,你不需要在准备播放回调中管理声音。
Babylon.js 框架仍然保持了简单性,因为你只需在 BABYLON.Sound 类上调用一个 new 语句,就为场景添加了一个音频轨道。
当然,你可以在单个场景中播放多个声音,如下面的代码所示:
var options = {
loop: true,
autoPlay: true
};
var sound1 = new BABYLON.Sound("sound1", "sound1.mp3", scene, null, options);
var sound2 = new BABYLON.Sound("sound2", "sound2.mp3", scene, null, options);
管理二维声音
有几个属性可以用来在 2D 中操作声音,例如音量、声音是否正在播放等。
要设置声音的音量,只需调用声音的 .setVolume 函数。新的音量设置在 [0, 1] 区间内,如下面的代码所示:
sound.setVolume(0.5);
你还可以获取音量,如下面的代码所示:
var currentVolume = sound.getVolume();
你可以随时检查声音的当前状态,如下所示:
sound.isPlaying; // true if the sound is playing
sound.isPaused // true if the sound was paused
你也可以随时设置声音的状态,如下所示:
sound.play(); // Plays the sound
sound.pause(); // Pauses the sound
sound.stop(); // Stops the sound
播放 3D 声音
在前面的主题中,你已经在场景中添加并播放了 2D 声音。这些 2D 声音可以很容易地用作游戏的配乐。为了给场景增加动态效果,就像物理和碰撞一样,你可以配置声音在场景中空间化。空间化声音,也称为 3D 声音,提供了玩家与声音之间的距离和方向感。换句话说,玩家离声音越远,声音的衰减就越大。此外,如果声音位置相对在玩家的右边,右扬声器产生的声音比左扬声器多,反之亦然。
例如,如果声音从你的右边发出,只有右边的扬声器应该播放它,而且你离声音越远,声音的音量应该越低。
创建 3D 声音
你可以想象,对于 2D 声音,你可以使用相同的BABYLON.Sound构造函数创建空间化声音。只需更改options参数,因为必须将spatialSound设置为true,如下所示:
var sound = new BABYLON.Sound("sound", "sound_file", scene, () => {
sound.play();
}, { spatialSound: true });
一旦创建了空间化声音,你可以使用BABYLON.Vector3在场景的世界中设置其 3D 位置,如下所示:
sound.setPosition(new BABYLON.Vector3(0, 0, 50)); // For example
管理三维声音
与 2D 声音相比,你可以使用 3D 声音自定义更多属性。空间化声音提供了配置衰减和声像模型的属性。
例如,默认的距离模型(衰减)设置为线性。存在其他两种模型,如exponential和inverse,如下所示:
sound.distanceModel = "exponential";
关于雾效(第四章;
关于`maxDistance`属性,如果你使用指数模型,可以设置`rolloffFactor`属性,如下面的代码所示:
```js
sound.updateOptions({
rolloffFactor: 2
});
当然,你可以同时更新多个值。只需将.updateOptions参数配置为相应的值。以下是一个示例:
sound.updateOptions({
maxDistance: 10,
rolloffFactor: 2
});
对于空间化声音,最后一个有用的功能是直接将声音附加到网格上。然后,不需要手动更新声音的位置来将其设置为网格的位置,如下所示:
sound.attachToMesh(myMesh);
现在,当调用scene.render()时,声音和网格共享相同的位置,并由场景一起更新。
创建方向性空间化声音
你之前创建的空间化声音是全方向的。这意味着如果你在扬声器后面,你会听到和站在扬声器前面一样响亮的声音。这在现实生活中是不会发生的。Babylon.js 音频引擎提供了一种创建易于配置的方向性声音的方法。
注意
注意,方向性空间化声音仅在声音附加到网格上时才起作用。
让我们从以下声音参考开始:
var sound = new BABYLON.Sound("sound", "sound_file", scene, () => {
}, { loop: true, autoplay: true });
你可以通过调用仅三个函数来配置它为方向性。
首先,声音的方向由一个锥体表示。只需设置方向锥,如下所示:
sound.setDirectionalCone(90, 180, 0.1); // Values are in degree
有三个参数,如下所示:
-
内锥体的大小(以度为单位)应该比外锥体小
-
外锥体的大小(以度为单位)应该比内锥体大
-
当玩家位于外锥体外时,空间化声音的音量
为了获得完美的方向性声音,内锥体和外锥体的大小应该相等。
一旦设置了方向锥,只需根据网格旋转设置声音的方向。该参数是局部于网格的。然后,如果你旋转扬声器,例如,声音将始终跟随扬声器的旋转,这取决于参数。考虑以下示例:
sound.setLocalDirectionToMesh(new BABYLON.Vector3(0, 0, 1)); // Always speak ahead (Z is the forward axis)
最后,不要忘记按照以下方式将声音附加到网格上:
sound.attachToMesh(myMesh); // myMesh should be the speaker in the example
摘要
这章快速演示了当为开发者提供强大工具时,在 3D 引擎中使用声音(2D 和 3D)可以变得非常简单。示例文件创建了一个作为配乐播放的 2D 声音和一个位于盒子位置处的 3D 声音。不要犹豫,尝试调整距离模型,并使用你的耳机检查效果。
在下一章中,我们将尝试使用 Babylon.js 的ActionManager类自动化一些事情。这个类在触发器被激活时对对象执行动作很有用。例如,如果玩家左键单击盒子,它将播放名为my_sound.wav的声音。这也是介绍动作构建器的时候,它是 Babylon.js 3ds Max 导出器的一部分。动作构建器允许艺术家(和开发者)在不编写任何代码的情况下为他们的对象创建动作。
第七章。在对象上定义动作
让我们进一步探讨 Babylon.js 中的游戏玩法。该框架提供了一个 ActionManager 类,允许我们在引擎触发器时管理和执行动作。
例如,想象一个有按钮的场景。当按钮被按下(左键点击)时,场景中的灯光应该关闭,除了一个。这只是一个例子。在本章中,我们将涵盖以下主题:
-
在对象上定义动作
-
使用条件来控制动作图
-
在 3ds Max 中使用动作构建器
在对象上定义动作
Babylon.js 框架附带了一系列不同的动作。你可以找到可以播放声音、停止声音、插值对象属性、设置对象属性值等动作。
在对象上启用动作
能够处理 ActionManager 的唯一实体是场景和场景中的网格。每个 Babylon.js 网格都有自己的动作管理器引用。要在一个网格上启用动作,只需创建一个新的网格引用,如下所示:
myMesh.actionManager = new BABYLON.ActionManager(scene);
ActionManager 构造函数只接受场景作为参数。
让我们在由一个盒子和一个平面组成的场景中创建你的第一个动作。当用户左键点击盒子时,这个动作必须将盒子的位置在 y 轴上从 0 改变到 6。唯一需要做的事情就是在盒子的 ActionManager 上调用 .registerAction 函数,如下所示:
myMesh.actionManager = new BABYLON.ActionManager(scene);
myMesh.actionManager.registerAction(new BABYLON.SetValueAction(
// The trigger type
BABYLON.ActionManager.OnLeftPickTrigger,
// The target
myMesh,
// Property to modify
"position.y",
// The new value
6,
// The condition
null
));
让我们暂时将条件视为 null 并在下一个主题中解释它。
如果我们更仔细地看,一个动作是由触发器触发的(在这里,BABYLON.ActionManager.OnLeftPickTrigger)。只有网格有几种类型的触发器,如下所示:
-
OnPickTrigger:当对象被选中(点击)时。 -
OnLeftPickTrigger:当对象被选中(仅左键点击)时。 -
OnRightPickTrigger:当对象被选中(仅右键点击)时。 -
OnCenterPickTrigger:当对象被选中(仅鼠标滚轮点击)时。 -
OnPointerOverTrigger:当指针在对象上(进入)时。 -
OnPointerOutTrigger:当指针离开对象(退出)时。 -
OnIntersectionEnterTrigger:当对象与另一个对象相交时。 -
OnIntersectionExitTrigger:当对象完成与另一个对象的相交时。 -
NothingTrigger:仅用于链式动作。让我们看看下一个子主题。
注意
注意:所有选择触发器都需要网格是可选择的:myMesh.isPickable = true;
此外,场景上还有几种仅在场景中可用的触发器。确实,场景也可以有自己的 ActionManager,如下所示:
-
OnEveryFrameTrigger:这会在 Babylon.js 渲染的每一帧中触发相关的动作 -
OnKeyDownTrigger:当用户按下键(键盘) -
OnKeyUpTrigger:当用户完成按键(键盘)时
如你所猜,一些触发器需要参数。例如OnIntersectionEnterTrigger、OnIntersectionExitTrigger、OnKeyDownTrigger和OnKeyUpTrigger。要配置需要参数的触发器,只需提供一个包含触发类型和参数的 JavaScript 对象,而不是直接在动作构造函数的参数中给出触发类型。以下是一个使用OnIntersectionEnterTrigger的先前例子:
myMesh.actionManager.registerAction(new BABYLON.SetValueAction(
// The trigger, the structure is the same for
// OnIntersectionExitTrigger
{
trigger: BABYLON.ActionManager.OnIntersectionEnterTrigger,
parameter: myOtherMeshReference
},// The target
myMesh,
// Property to modify
"position.y",
// The new value
6,
// The condition
null
));
对于OnKeyDownTrigger和OnKeyUpTrigger,结构相同,参数是键。以下是一个例子:
myMesh.actionManager.registerAction(new BABYLON.SetValueAction(
// The trigger, the structure is the same for
// OnKeyUpTrigger
{
trigger: BABYLON.ActionManager.OnKeyDownTrigger,
parameter: "d" // When the user pushes the key "d"
},
// The target
myMesh,
// Property to modify
"position.y",
// The new value
6,
// The condition
null
));
让我们更仔细地看看以下操作参数:
-
触发器:这决定了动作何时由动作管理器触发。您可以将触发器视为事件类型。
-
目标:这代表将被动作修改的对象(不一定在所有情况下都是网格或场景;然而,通常是一个 JavaScript 对象)。
-
要修改的属性:这代表目标将被动作修改的属性。
-
新值:这代表影响目标属性的新值。这可以是一个数字、字符串、对象等。
几乎所有可用的操作都使用这些参数。
对象上的链式操作
Babylon.js 的ActionManager类允许您构建一个操作图。所有注册的操作都将同时检查(以验证触发器)并执行(如果检查得出结论)。然而,想象以下场景:
当用户左键单击盒子时,新的y轴位置是0,当用户再次左键单击盒子时,新的y轴旋转是PI / 4。
第二个动作(再次左键单击)只有在第一个动作执行时才必须执行。要执行此操作,您可以在动作中调用.then函数。以下是一个例子:
myMesh.actionManager.registerAction(new BABYLON.SetValueAction(
BABYLON.ActionManager.OnLeftPickTrigger,
myMesh,
"position.y",
6,
null
)
// Will be executed on the second left-click
.then(new BAYBLON.SetValueAction(
BABYLON.ActionManager.NothingTrigger,
myMesh,
"rotation.y",
Math.PI / 4,
null
));
此方法适用于所有动作。
可用的操作
现在,让我们列举所有可用的操作,如下所示:
BABYLON.SwitchBooleanAction: 这将切换布尔属性(从假变为真,或从真变为假)。以下是一个例子:
new BABYLON.SwitchBooleanAction(trigger, target,
propertyToModify (boolean), condition);
-
BABYLON.SetValueAction: 这将设置属性的新值,类似于前面的例子。 -
BABYLON.IncrementValueAction: 这将按指定的值增加属性的值(仅适用于数字值)。以下是一个例子:new BABYLON.IncrementValueAction(trigger, target, propertyToModify (number), valueToIncrement) -
BABYLON.PlayAnimationAction: 这将播放对象中可用的动画,例如角色或按钮必须是BABYLON.Node。第九章,创建和播放动画中介绍了如何设置和播放动画。以下是一个例子:new BABYLON.PlayAnimationAction(trigger, target, startFram e, endFrame, loop);动画与帧一起工作,例如从 0 到 200。动作指定起始帧和结束帧(例如 25 到 35)。循环参数是一个布尔值,指定播放的动画是否循环。
-
BABYLON.StopAnimationAction: 这将停止目标对象的动画。考虑以下示例:
new BABYLON.StopAnimationAction(trigger, target);
BABYLON.DoNothingAction:这什么都不做。它用于控制图并绕过点击,例如。考虑以下示例:
new BABYLON.DoNothingAction(trigger);
BABYLON.ExecuteCodeAction: 这将执行您自己的函数。考虑以下示例:
new BABYLON.ExecuteCodeAction(trigger, (evt: ActionEvent) => {
console.log("executing action !");
console.log(evt.source);
console.log(evt.pointerX);
console.log(evt.pointerY);
console.log(evt.meshUnderPointer);
console.log(evt.sourceEvent);
console.log(evt.additionalData);
});
BABYLON.SetParentAction: 这为目标对象设置一个新的父对象。目标必须是 BABYLON.Node(网格、光源、相机等)。考虑以下示例:
new BABYLON.SetParentAction(trigger, target,
theParentReference);
BABYLON.PlaySoundAction: 这将播放一个声音。只需提供BABYLON.Sound引用。考虑以下示例:
new BABYLON.PlaySoundAction(trigger, theSoundReference);
BABYLON.StopSoundAction: 这将停止一个声音。只需提供BABYLON.Sound引用。考虑以下示例:
new BABYLON.StopSoundAction(trigger, theSoundReference);
-
BABYLON.InterpolateValueAction: 这将插值目标对象BABYLON.Node的值(创建动画)。考虑以下示例:new BABYLON.InterpolateValueAction(trigger, target, propertyToInterpolate, finalValue, durationInMS);要插值的属性必须是
number、BABYLON.Color3、BABYLON.Vector3或BABYLON.Quaternion类型。例如,要插值的属性可以是
position,值是new BABYLON.Vector3(0, 6, 0)。 -
BABYLON.CombineAction: 这是一个特殊动作。它允许在任何地方同时执行多个动作。考虑以下示例:new BABYLON.CombineAction(trigger, childrenActions);childrenActions是BABYLON.Action的数组。它包含同时执行的动作。
使用条件来控制动作图
在前面的子主题(可用的动作)中,动作中的条件被绕过了。实际上,每个动作都可以有一个条件来控制是否执行 ActionManager 中的动作。条件可以检查一个值是否与另一个值比较时处于以下状态之一:
-
相等
-
较小
-
大于
-
不同
有三种类型的条件,如下所示:
-
状态条件:这检查
BABYLON.Node的.state属性是否等于给定的状态。状态是一个字符串。 -
值条件: 这检查属性是否等于、小于、大于或不同于给定的值。
-
谓词条件: 这将调用一个自定义方法,该方法将返回
true或false。
如果一个动作有一个条件,那么只有当条件返回 true 时,该动作才会被执行。
向您的动作图添加条件显示了 Actions Builder 的所有功能,这在下一个主题中介绍。为了理解以下示例,在练习条件之前,您可以访问艺术家 Michel Rousseau 制作的这个演示(www.babylonjs.com/Demos/ActionBuilder/),他仅使用动作和条件来创建场景中的交互。
原则是必须按顺序激活三个按钮才能关闭主光源。
作为开发者,理论如下:
-
当用户点击按钮时(每个按钮的
OnLeftPickTrigger):-
如果按钮被按下(
StateCondition),则取消按按钮并关闭灯光(SetValueAction) -
如果按钮没有被按下(
StateCondition),则按按钮并打开灯光(SetValueAction)
-
-
在每一帧(
OnEveryFrameTrigger):-
如果三个按钮都被按下(链式
StateCondition),则将主灯光的漫反射颜色设置为黑色(关闭)(SetValueAction) -
如果三个按钮中的任何一个没有被按下(每个按钮的
StateCondition),则将主灯光的漫反射颜色设置为白色(打开)(SetValueAction)
-
该理论可以通过在图中通过链式连接条件(检查是否按下三个按钮)和动作来执行。当然,动作构建器将允许你在图上获得实际表示,而不是仅仅在脑海中想象。你只需操作它来训练和学习使用动作的正确思考方式。
状态条件
状态条件检查BABYLON.Node的.state属性是否等于给定的状态。
考虑以下使用BABYLON.StateCondition的示例:
myMesh.state = "isUp";
// If mesh state is "isDown", make position.y up
var condition1 = new BABYLON.StateCondition(myMesh.actionManager,
myMesh, "isDown");
myMesh.actionManager.registerAction(new BABYLON.SetValueAction(
BABYLON.ActionManager.OnLeftPickTrigger, myMesh,
"position.y", 6, condition1);
// Else if mesh state is "isUp", make position.y down
var condition2 = new BABYLON.StateCondition(myMesh.actionManager,
myMesh, "isUp");
myMesh.actionManager.registerAction(new BABYLON.SetValueAction(
BABYLON.ActionManager.OnLeftPickTrigger, myMesh,
"position.y", 0, condition2);
断言条件
断言条件执行一个自定义函数,该函数将返回 true 或 false。如果条件需要测试多个值或在使用 TypeScript 操作对象后返回 true 或 false,则特别有用。让我们使用断言条件创建一个与之前相同的示例,如下所示:
myMesh.position.y = 6;
// If mesh position.y is 6, set it to 0
var condition1 = new BABYLON.PredicateCondition(myMesh.actionManag
er, () => {
return myMesh.position.y === 6;
});
myMesh.actionManager.registerAction(new BABYLON.SetValueAction(
BABYLON.ActionManager.OnLeftPickTrigger, myMesh,
"position.y", 0, condition1);
// Else if mesh position.y is 0, set it to 6
var condition2 = new BABYLON.PredicateCondition(myMesh.actionManag
er, () => {
return myMesh.position.y === 0;
});
myMesh.actionManager.registerAction(new BABYLON.SetValueAction(
BABYLON.ActionManager.OnLeftPickTrigger, myMesh,
"position.y", 6, condition2);
值条件
值条件是一种自动检查值是否等于、小于、大于或不同的方法,而无需编写断言条件。让我们再次使用BABYLON.ValueCondition创建之前的示例,如下所示:
myMesh.position.y = 6;
// If mesh position.y is 6, set it to 0
var condition1 = new BABYLON.ValueCondition(myMesh.actionManager, myMesh, "position.y", 6, BABYLON.ValueCondition.IsEqual);
myMesh.actionManager.registerAction(new BABYLON.SetValueAction(
BABYLON.ActionManager.OnLeftPickTrigger, myMesh, "position.y", 0 , condition1);
// Else if mesh position.y is 0, set it to 6
var condition2 = new BABYLON.ValueCondition(myMesh.actionManager, myMesh, "position.y", 0, BABYLON.ValueCondition.IsEqual);
myMesh.actionManager.registerAction(new BABYLON.SetValueAction(
BABYLON.ActionManager.OnLeftPickTrigger, myMesh, "position.y", 6 , condition2);
值条件的工作方式类似于动作,你必须指定目标(myMesh)、属性(position.y)以及要测试的值(0和6)。
在 3ds Max 中使用动作构建器
创建动作可以节省代码行数。Babylon.js 解决方案还提供了一种方式,让 3ds Max 艺术家和开发者创建动作,包括条件,而无需任何代码。
动作构建器允许 Michel Rousseau 在他的场景中创建动作和场景,而无需任何代码,如下所示:
-
动作构建器场景:
www.babylonjs.com/?AB
要做到这一点,你可以简单地使用动作构建器,它是 Babylon.js 3ds Max 插件的组成部分。动作构建器位于 GitHub 仓库中 3ds Max 插件中的BabylonActionsBuilder文件夹。你可以将文件夹复制并粘贴到 3ds Max 插件目录中,该目录通常位于C:\Program Files\Autodesk\3ds Max 2013\bin\assemblies,如下面的截图所示:

它是如何工作的
要使用动作构建器,只需在 3ds Max 中选择一个对象,右键单击它,选择Babylon...菜单,然后打开Babylon Actions Builder。
注意
注意:要编辑场景动作,在 3ds Max 场景中选择“无”,右键单击空白处,选择Babylon...菜单,然后打开Babylon Actions Builder。

动作构建器窗口出现。你现在可以添加动作到你的对象,而无需任何代码行。在动作构建器中,所有动作都可用,除了执行代码的动作(BABYLON.ExecuteCodeAction和BABYLON.PredicateCondition)。

在左侧,有一个触发器、动作和条件的列表。要添加一个动作或条件,你必须从一个触发器开始。只需将所需的触发器拖放到根节点(ourMesh)上。

现在,子动作将在用户左键单击ourBox对象(一个网格)时执行。
一旦添加了所需的触发器,你可以在图中拖放任何动作或条件。例如,让我们添加BABYLON.InterpolateValueAction,如图所示:

如果你点击添加的动作,右侧将出现几个菜单。这些菜单将对应于动作构造函数的参数。你将检索目标、属性和值参数,如图所示:

动作构建器中.then函数的等价操作是在动作中添加一个子动作。
让我们也在图中添加一个条件,以及一个插值值动作的子动作(相当于.then)。至于触发器和动作,拖放一个值条件(BABYLON.ValueCondition)到插值值动作上,如图所示:

在这里,当用户左键单击网格时,将执行动作 1。一旦动作 1 执行,只有当值条件返回true时,才会执行动作 2。
管理多个管道
使用 TypeScript,你可以通过在ActionManager对象中调用.registerAction函数,向你的对象添加多个动作分支。使用动作构建器,你可以简单地添加另一个触发器来处理新的动作图,如图所示:

在这种配置下,每个触发器的第一个动作将同时执行,除非有条件,并且动作的条件返回 false。
摘要
现在,你可以在你的项目中创建动作并节省大量代码行。你已了解如何使用BABYLON.ActionManager类,如何添加动作,不同的触发器,以及如何使用条件来控制图。你还能使用动作构建器与艺术家和 3ds Max 协作,从而节省更多代码行。
在下一章中,让我们专注于使用后处理(效果)的渲染部分。这将是一个介绍 HDR、SSAO、模糊、光晕等概念的良机。这是 3D 开发者们最喜欢的部分之一,用于创建如后处理等特殊效果。
第八章. 使用内置后处理添加渲染效果
记得第四章,关于材质的内容吗?在材质背后是称为着色器的 GPU 程序。着色器是两个链接程序的组合:顶点着色器和像素着色器。顶点着色器在顶点上工作(将它们的 3D 位置转换为屏幕上的 2D 位置),而像素着色器在像素上工作(确定每个像素的最终颜色)。在这里,你可以看到后处理只是一个像素着色器,因为顶点着色器对所有后处理都是相同的。
最后,后处理倾向于只在视图空间中创建效果。换句话说,后处理永远不会应用于如网格等对象,它们只应用于相机本身。在本章中,我们将涵盖以下主题:
-
使用 Babylon.js 的后处理
-
使用 Babylon.js 的后处理渲染管线
-
讨论内置的后处理
使用 Babylon.js 的后处理
幸运的是,你不必自己创建后处理,即使你可以使用 Babylon.js 来做。Babylon.js 中已经有一些后处理可用,并且在大多数情况下,只需写一行代码即可使用!
从你的第一个后处理开始
在 Babylon.js 中,你可以使用后处理创建模糊、辉光、HDR、SSAO、体积光后处理等。
让我们从以下场景开始:

对于第一个例子,让我们使用内置后处理创建一个垂直模糊后处理。模糊后处理可以通过创建BABYLON.BlurPostProcess类的新实例来获取,如下所示:
var blurV = new BABYLON.BlurPostProcess(
"blurV", // Name of the post-process
new BABYLON.Vector2(0, 1), // Direction of the blur (vertical)
4, // The blur width
0.5, // The ratio of the post-process
camera // The camera to attach to
);
结果如下所示:

Babylon.js 中大多数可用的后处理将具有以下相同的参数:
-
名称: 这是后处理的名称。
-
比例: 这是后处理在
[0, 1]区间内的比例。比例用于在较低分辨率下计算后处理以节省性能。换句话说,如果比例为0.5,后处理将应用于画布分辨率除以 2。 -
相机: 后处理应用于相机。然后,你只需提供相机引用,后处理的构造函数就会自动附加到相机上。
以另一个例子为例,让我们创建一个黑白后处理,它将场景完全转换为黑白,如下所示:
var bw = new BABYLON.BlackAndWhitePostProcess(
"blackAndWhite", // name of the post-process
1.0, // ratio of 1.0, keep the full resolution
camera // the camera to attach to
);
以下截图显示了结果:

后处理链
使用 Babylon.js,您可以链式后处理。这意味着对于某个后处理,前一个后处理将作为参考。例如,两个模糊后处理可以用来水平垂直模糊场景;第一个后处理水平模糊场景,第二个使用水平模糊后处理垂直模糊场景。
让我们创建第一个后处理,即水平模糊后处理:
var blurH = new BABYLON.BlurPostProcess(
"blurH",
new BABYLON.Vector2(1, 0),
8,
0.5,
camera
);

现在,让我们在水平模糊后处理之后创建垂直模糊后处理,如下所示:
var blurV = new BABYLON.BlurPostProcess(
"blurV",
new BABYLON.Vector2(0, 1),
8,
0.5,
camera
);

最后,为什么不添加“黑白”后处理,如下所示:
var bw = new BABYLON.BlackAndWhitePostProcess("bw", 1.0, camera);
最终结果显示在下述截图:

移除和检索后处理
要从相机中移除后处理,您可以直接在某个后处理上调用.dispose函数。该函数会移除内部资源并将后处理从相机中分离。
例如,考虑以下链式后处理:
blurH.dispose();
blurV.dispose();
bw.dispose();
相反,您可以从/到相机中分离和附加后处理而不删除内部资源。只需在相机上调用.attachPostProcess或.detachPostProcess函数即可。考虑以下示例:
camera.detachPostProcess(blurH); // Detach
camera.attachPostProcess(blurH); // Re-attach the post-process
要检索可用的后处理,您可以访问相机的._postProcesses属性。考虑以下示例:
for (var i=0; i < camera._postProcesses.length; i++) {
console.log(camera._postProcesses[i].name);
}
使用 Babylon.js 的后处理渲染管线
现在,您能够创建后处理并将它们附加到相机上。问题是如果您在项目中管理多个相机,那么您将不得不销毁或分离后处理以便重新附加到新的相机上。为了简化任务,您可以使用渲染管线。换句话说,您可以将渲染管线视为后处理列表,您可以将它们附加到多个相机上。
创建渲染管线
这些步骤包括创建管线引用,将管线添加到场景中,并将管线附加到相机上。
创建一个渲染管线,如下所示:
var pipeline = new BABYLON.PostProcessRenderPipeline(
engine, // The Babylon.js engine
"renderingPipeline" // The name of the rendering pipeline
);
一旦管线被创建,将其添加到场景的后处理渲染管线管理器中(由创建渲染管线构造函数时传入的引擎引用),如下所示:
scene.postProcessRenderPipelineManager.addPipeline(pipeline);
一旦管线被添加到后处理渲染管线管理器中,您就可以添加效果。这个过程包括通过调用管线的.addEffect方法向管线中添加一个新的BABYLON.PostProcessRenderEffect对象,如下所示:
// Create the post-process (horizontal blur)
var blurH = new BABYLON.BlurPostProcess(
"blurH",
new BABYLON.Vector2(1, 0), 8, 0.5,
null, // The camera is null
null, // Keep the bilinear filter as default
engine // Because the camera is null, we must provide the engine
);
// Create the render effect
var blurHEffect = new BABYLON.PostProcessRenderEffect(
engine, // The Babylon.js engine
"blurHEffect", // The name of the post-process render effect
() => { // The function that returns the wanted post-process
return blurH;
}
);
// Add the render effect to the pipeline
pipeline.addEffect(blurHEffect);
如您所见,构建后处理的方法必须更改。现在,后处理将不会接受任何相机作为参数,因为它们不是应用于特定相机。这就是为什么我们必须提供所有参数的原因,例如过滤器类型(默认为双线性,然后可以是 null)和引擎。这种方法适用于所有后处理,您可以轻松地将后处理渲染效果添加到管道中,如下所示:
var blurH = new BABYLON.BlurPostProcess(..);
var blurV = new BABYLON.BlurPostProcess(..);
var bw = new BABYLON.BlackAndWhitePostProcess(..);
// The horizontal blur post-process render effect
var blurHEffect = new BABYLON.PostProcessRenderEffect(
engine, // The Babylon.js engine
"blurHEffect",
() => {
return blurH;
}
);
// The vertical blur post-process render effect
var blurVEffect = new BABYLON.PostProcessRenderEffect(
engine, // The Babylon.js engine
"blurVEffect",
() => {
return blurV;
}
);
// The black and white post-process render effect
var bwEffect = new BABYLON.PostProcessRenderEffect(
engine,
"bwEffect",
() => {
return bw;
}
);
// And finally add the render effects to the pipeline by
// following the desired order
pipeline.addEffect(blurHEffect);
pipeline.addEffect(blurVEffect);
pipeline.addEffect(bwEffect);
最后,让我们将管道连接到相机或相机列表。现在,后处理将应用于相机,如下面的代码片段所示:
scene.postProcessRenderPipelineManager.attachCamerasToRenderPipeline(
"renderingPipeline", // The name of the pipeline to attach
camera // the camera to attach to. Can be an array of cameras
);
您还可以按照以下方式从相机或相机列表中分离管道:
scene.postProcessRenderPipelineManager.detachCamerasFromRenderPipeline(
"renderingPipeline", // The name of the pipeline to detach
camera // the camera to detach. Can be an array of cameras
);
结果看起来完全一样;然而,现在更容易在多个相机之间共享后处理,如下面的屏幕截图所示:

在管道中启用和禁用效果
后处理渲染管道的另一个特点是禁用和启用效果的可能性,这是一个有用的功能,允许您高度调试项目的渲染部分。
假设,在您的项目中,您可以在两个相机之间切换(scene.activeCamera = theNewCamera)。第一个相机是模糊的,第二个是模糊的并且是黑白。目标是两个相机可以共享相同的后处理渲染管道引用,除了第一个相机必须禁用黑白后处理。
要在渲染管道中禁用效果,您可以在场景的后处理渲染管道管理器上调用.disableEffectInPipeline方法。所需的唯一参数是管道的名称、效果的名称以及将不再启用后处理的相机。如果我们以之前的例子为例,让我们禁用黑白后处理,如下所示:
scene.postProcessRenderPipelineManager.disableEffectInPipeline(
"renderingPipeline", // The name of the render pipeline
"bwEffect", // The name of the "black and white" effect to disable
camera // The camera attached to the pipeline
);
换句话说,这种方法允许您仅对作为参数传递的相机禁用渲染效果。
相反,您可以在任何时候通过在场景的后处理渲染管道管理器上调用.enableEffectInPipeline方法来启用之前已禁用的渲染效果。让我们启用黑白渲染效果,如下所示:
scene.postProcessRenderPipelineManager.enableEffectInPipeline(
"renderingPipeline", // The name of the render pipeline
"bwEffect", // The name of the "black and white" effect to enable
camera // The camera attached to the pipeline
);
内置的后处理
让我们从本章最有趣的部分开始;在 Babylon.js 中使用内置的后处理。有几个后处理可以通过以下元素美化您的场景:
-
体积光散射:这展示了如何轻松散射给定光源(如太阳或月亮)的光线。
-
SSAO 渲染管道:屏幕空间环境遮挡。换句话说,这个渲染管道倾向于通过仅使用后处理来近似场景的环境遮挡,以获得更逼真的效果。
-
HDR 渲染管线:高动态范围渲染。此渲染管线与场景中的光照直接相关,并倾向于模拟视网膜在现实世界中的工作方式。
体积光散射后处理
让我们从有趣的体积光散射(VLS)后处理开始。VLS 后处理倾向于模拟光源根据光与相机之间的障碍物散射光线的现象。
让我们考虑以下原始场景:

VLS 后处理接受一个代表光色的网格作为参数。实际上,光源并不是真正的光源(BABYLON.Light),而是一个配置了材质的网格,通过漫反射纹理或颜色来模拟光的颜色。
例如,考虑一个由广告牌表示的白色太阳。使用 VLS 后处理,结果看起来类似于以下截图:

没有后处理的场景看起来类似于以下截图:

实际上,那个圆形的白色网格是一个包含 alpha 的漫反射纹理的平面。漫反射纹理可以在示例文件中找到,命名为sun.png。
让我们创建 VLS 后处理,如下所示:
var vls = new BABYLON.VolumetricLightScatteringPostProcess(
"vls", // The name of the post-process
1.O, // The ratio of the post-process
camera, // The camera to attach to
null, // The mesh used as light source
100 // Number of samples. Means the quality of the post-process
);
在 VLS 构造函数中,如果网格参数为 null,后处理将创建一个默认网格,该网格是一个渲染为广告牌的平面。在任何时候,如果你想,你可以像以下所示访问该方法:
BABYLON.VolumetricLightScatteringPostProcess.CreateDefaultMesh(
"vlsMesh", // The name of the billboard plane mesh
scene // The scene where to add the billboard plane mesh
);
例如,Babylon.js 的豪宅场景使用了 VLS 后处理来散射月光的射线。结果如下所示图像:

该方法相当简单,他们只是获取了月球网格参考,并通过将月球网格参考作为参数创建 VLS 后处理,如下所示:
var moon = scene.getMeshByName("moon");
var vls = new BABYLON.VolumetricLightScatteringPostProcess(
"vls", // The name of the post-process
1.O, // The ratio of the post-process
camera, // The camera to attach to
moon, // The mesh used as light source
75 // Number of samples. Means the quality of the post-process
);
样本数通常在[30, 100]区间内,并定义了后处理结果的品质。在 Babylon.js 的豪宅场景中,样本数设置为65以节省性能,因为该场景已经由引擎渲染得相当大且非常漂亮。
为了节省更多性能,后处理的比率可以更加定制化。实际上,VLS 后处理使用一个内部遍历,在纹理(渲染目标纹理)中渲染场景以创建光线的散射。你可以轻松配置内部遍历以在较低分辨率下渲染。只需将一个对象作为参数传递给比率,如下面的代码片段所示:
var ratio = {
passRatio: 0.25, // Ratio of the internal pass. Render in a texture
// with a size divided per 4
postProcessRatio: 1.0 // Ratio of the post-process
};
var vls = new BABYLON.VolumetricLightScatteringPostProcess(
"vls", // The name of the post-process
ratio, // The ratio object
camera, // The camera to attach to
moon, // The mesh used as light source
100 // Number of samples. Means the quality of the post-process
);
你还可以自定义与 VLS 后处理本身相关的参数。
曝光控制效果的整体强度(默认0.3),如下所示:
vls.exposure = 0.7; // Exaggerated value

衰减会消散每个样本的贡献(默认0.96815),如下面的代码所示:
vls.decay = 0.9;

权重控制每个样本的整体强度(默认0.58767),如下面的代码所示:
vls.weight = 0.8;

密度控制每个样本的密度(默认0.926),如下面的代码所示:
vls.density = 0.7;

为了给你一个顺序,Babylon.js 的宅邸场景配置如下:
var moon = scene.getMeshByName("Moon");
var vls = new BABYLON.VolumetricLightScatteringPostProcess(
"vls",
1.0,
scene.activeCamera,
moon,
65,
);
vls.exposure = 0.15;
vls.weight = 0.54;
SSAO 渲染管线
SSAO 效果是一个渲染管线,它计算以下五个后处理步骤:
-
传递后处理(将场景保存到纹理中)
-
SSAO 后处理
-
水平模糊后处理
-
垂直模糊后处理
-
组合后处理
SSAO 因其仅使用屏幕空间(后处理)来计算环境遮蔽而闻名,这与需要 3D 艺术家在他们的纹理中计算环境遮蔽的更经典方法形成对比。最后,SSAO 是节省纹理和项目权重的好方法(不再需要纹理)。
让我们看看这个场景中的 SSAO 效果,没有 SSAO 的场景,有 SSAO 的场景,以及最后只有 SSAO 启用的场景:



结果特别微妙;然而,它可以极大地增强场景的真实感。环境遮蔽是表示光线在其不同点到达物体的能力的方式,或者更具体地说,在 SSAO 中,是那些无法到达物体不同点的光线。
从不同的视角来看,效果可以很容易地感知,如下面的图像所示:


SSAO 是一个渲染管线,并且可以很容易地创建,如下所示:
var ssao = new BABYLON.SSAORenderingPipeline(
"ssao", // The name of the render pipeline
scene, // The scene where to add the render pipeline
1.0 // The ratio of SSAO post-process
);
// Attach the render pipeline to your camera
scene.postProcessRenderPipelineManager.attachCamerasToRenderPipeline(
"ssao", // Name of the render pipeline
camera // The camera to attach to
);
这就是全部。幸运的是,SSAO(屏幕空间模糊)、水平模糊和垂直模糊后处理可以在比屏幕分辨率低的分辨率下完成。至于 VLS 后处理,你可以传递一个对象给比率参数,如下所示:
var ratio = {
ssaoRatio: 0.25, // Size divided per 4, saves a lot of performances!
combineRatio: 1.0 // The final output that mixes SSAO with the scene
};
var ssao = new BABYLON.SSAORenderingPipeline(
"ssao", // The name of the render pipeline
scene, // The scene where to add the render pipeline
ratio // The ratio of SSAO post-process
);
在每个场景的函数中,你可能需要配置 SSAO 以实现完美的渲染。有几个参数你可以自定义,如下所示:
-
总强度:这控制效果的整体强度(默认
1.0)。.totalStrength属性很少或稍微修改,因为它可能会创建一些伪影。 -
半径:这表示 SSAO(屏幕空间模糊)围绕分析像素的半径(默认
0.0002)。为了计算当前像素的 Ambient Occlusion(环境遮蔽,简称 AO),SSAO 效果会在指定的.radius属性周围计算当前像素的 16 个样本。 -
区域:
.area(默认0.0075)属性用于根据每个像素的遮挡差异插值 SSAO 样本。换句话说,区域用于平滑每个像素每个样本的环境遮挡。以下是一个基于线的平滑函数示例(来自维基百科):

在示例文件中,你可以按2键禁用 SSAO,按1键启用 SSAO,按3键仅绘制 SSAO 通道。结果(按顺序)如下所示:



HDR 渲染管线
高动态范围(HDR)很长时间都是一个热门词汇。这个概念特别有趣,它倾向于模拟视网膜在现实世界中的工作方式。它包括亮度的适应(刺眼)和物体高亮表面的亮度伪影。
要理解眩光的效果,想象你在一个完全黑暗的房间里。突然,有人打开了灯;你的眼睛适应亮度(泛光和模糊)所需的时间代表眩光效果。
让我们比较启用和未启用 HDR 渲染管线(HDR 会调整亮度并在高亮区域创建伪影)的同一场景,如下所示:


要创建 HDR 渲染管线,过程与 SSAO 渲染管线相同,如下所示:
var hdr = new BABYLON.HDRRenderingPipeline(
"hdr", // The name of the render pipeline
scene, // The scene where to add the render pipeline
1.0, // ratio of the render pipeline. Here, the ratio is a number
);
// Finally, attach the render pipeline to a camera
scene.postProcessRenderPipelineManager.attachCamerasToRenderPipeline(
"hdr", // The name of the render pipeline
camera // The camera to attach to
);
HDR 渲染管线可以高度定制,因为你可以根据需要定制创建伪影所需的最小亮度、创建伪影的模糊效果、亮度适应速度等。
曝光(hdr.exposure)控制场景的整体亮度(默认 1.0),如下所示:
hdr.exposure = 1.8;

明亮阈值(hdr.brightThreshold)控制创建伪影所需的最小亮度(默认0.8),如下所示:
hdr.brightThreshold = 0.2;

最小亮度(hdr.minimumLuminance)代表较暗区域的视网膜适应(默认1.0)。值越小(>= 0.0),场景越突出,如下所示:
hdr.minimumLuminance = 0.0;

亮度降低率(hdr.luminanceDecreaseRate)和亮度增加率(hdr.luminanceIncreaserate)代表视网膜对亮度的适应速度(默认两者均为0.5)。值越高,适应越快。在大多数情况下,该值介于0.5和1.0之间。
高斯模糊乘数(hdr.gaussMultiplier)增强了模糊宽度(默认4)。它作用于BABYLON.BlurPostProcess的.blurWidth属性,如下所示:
hdr.gaussMultiplier = 8;

高斯系数(hdr.gaussCoeff)控制整体高斯模糊效果(默认0.3)。实际上,高斯模糊的输出是output * gaussCoeff,如下所示:
hdr.gaussCoeff = 0.8;

高斯标准差(hdr.gaussStandDev)控制效果的整体模糊强度(默认 0.8),如下所示:
hdr.gaussStandDev = 0.2;

.gausCoeff和.gaussStandDev属性是相互关联的。它们必须相互平衡。为了给你一个顺序,以下场景是这样配置的:
var hdr = new BABYLON.HDRRenderingPipeline("hdr", scene, 1.0);
hdr.brightThreshold = 0.5;
hdr.gaussCoeff = 0.7;
hdr.gaussMean = 1.0;
hdr.gaussStandDev = 7.5;
hdr.minimumLuminance = 0.7;
hdr.luminanceDecreaseRate = 1.0;
hdr.luminanceIncreaserate = 1.0;
hdr.exposure = 1.3;
hdr.gaussMultiplier = 4;
它看起来如下:

在大多数情况下,在 Babylon.js 中实现 HDR 的突出场景中,遵循高斯模糊方程,高斯标准差等于10.0 * 高斯系数。
摘要
在本章中,你学习了如何仅使用后处理来美化你的场景。Babylon.js 主页上的 Mansion 演示展示了 Volumetric Light Scattering 后处理的一个很好的应用。不幸的是,由于硬件限制,手机上没有可用的后处理。即使后处理在移动设备上不受支持,你的项目仍然可以在没有后处理渲染的情况下运行。Babylon.js 的力量在于它可以在所有设备上工作。即使某个功能在设备上不受支持,该功能也会简单地被禁用。
在下一章中,让我们以动画结束。Babylon.js 框架允许创建和管理动画。这些动画将允许你借助框架和 3D 艺术家来动画化角色、对象等!
第九章。创建和播放动画
在前面的章节中,您学习了创建酷炫、美观和完整的 3D 应用程序所需的所有内容。这是最后一章,您需要学习的最后一件事是如何在场景中动画化对象。然后,最终,您将获得一个完全动态的场景。
Babylon.js 框架提供了一种无需通过代码管理即可创建动画的方法。例如,您想创建一个旋转动画,该动画将影响场景中的五个对象(节点);Babylon.js 将允许您创建一个动画对象,您可以在五个节点之间轻松共享。在本章中,我们将涵盖以下主题:
-
使用 Babylon.js 创建动画
-
使用缓动函数实现平滑动画
-
导入和管理动画模型
使用 Babylon.js 创建动画
对于这个第一个主题,让我们讨论如何简单地使用代码动画化一个盒子,以及如何使用 Babylon.js 工具(如 BABYLON.Animation 类)创建动画。您将很快理解使用提供的工具而不是用代码处理动画的重要性。
使用代码动画化对象
让我们从以下场景开始(一个平面和一个盒子):

让我们让盒子围绕其中心(x = 0, y = 0, 和 z = 0)旋转。这个过程应该是增加一个值(角度)随时间变化,并设置盒子的新位置。
通常,(x = 半径 * Cos(角度), y = 0, 和 z = 半径 * Sin(角度))。
要执行此操作,您可以在场景上调用一个 .registerBeforeRender 函数。此函数接受一个匿名函数作为参数,并且这个匿名函数将自动在每一帧被调用,如下所示:
var angle = 0.0;
var radius = 10.0;
scene.registerBeforeRender(() => {
angle += 0.01; // Increment the angle
// Set the new position of the box
box.position.x = radius * Math.cos(angle);
box.position.z = radius * Math.sin(angle);
});
这个函数相当简单;然而,它仅应用于盒子。如果您想通过共享相同的代码来动画化另一个对象怎么办?解决方案很简单;只需将节点作为参数传递给一个函数,该函数将在每次渲染前注册一个新的函数,如下所示:
var createAnimation = function(node) {
var angle = 0.0;
var radius = 10.0;
scene.registerBeforeRender(() => {
angle += 0.01; // Increment the angle
// Set the new position of the box
node.position.x = radius * Math.cos(angle);
node.position.z = radius * Math.sin(angle);
});
}
如您所见,通过代码管理动画并不一定是个问题。真正的问题出现在您需要将动画与时间同步(管理动画速度)、停止或暂停动画等情况。这些功能要求您创建一个完整的管理器,而这并不是您想要做的。
下一个子主题将向您展示如何使用 Babylon.js 的动画管理器,并且不用担心时间、停止和暂停功能等。
使用 Babylon.js 的动画管理器
让我们使用相同的场景,仅使用 Babylon.js 的动画管理器创建一个简单的动画。您只需要理解一件事:动画的帧键。
要创建一个动画,只需使用 BABYLON.Animation 类并将其附加到一个节点(或多个节点)上。BABYLON.Animation 类并不难理解,它需要一个名称,一个要在节点上动画化的属性(不一定是节点的属性),每秒帧数,数据类型和循环模式,如下面的代码片段所示:
var simpleAnimation = new BABYLON.Animation(
"simpleAnimation", // Name of the animation
"rotation", // The property to modify (node.rotation)
1, // Frames per second
BABYLON.Animation.ANIMATIONTYPE_VECTOR3, // The type of property
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE // The loop mode
);
rotation 参数表示动画管理器将动画化附加到动画的对象的 .rotation 属性。旋转是 BABYLON.Vector3 类型,这也是为什么数据类型是 BABYLON.Animation.ANIMATIONTYPE_VECTOR3 的原因。最后,我们希望动画循环。
根据你正在动画化的属性(在之前的例子中是 旋转,它是一个 BABYLON.Vector3 对象),你必须提供一个有效的数据类型,它可以是以下之一:
-
ANIMATIONTYPE_FLOAT:当属性是float类型时。 -
ANIMATIONTYPE_VECTOR2:当属性是BABYLON.Vector2类型时。 -
ANIMATIONTYPE_VECTOR3:当属性是BABYLON.Vector3类型时。 -
ANIMATIONTYPE_QUATERNION:当属性是BABYLON.Quaternion类型。这是一个可以用来表示节点旋转(node.rotationQuaternion)的数学对象。 -
ANIMATIONTYPE_MATRIX:当属性是BABYLON.Matrix类型时。 -
ANIMATIONTYPE_COLOR3:当属性是BABYLON.Color3类型时。
在这种情况下,我们使用 Vector3 数据类型,因为 .rotation 属性是 BABYLON.Vector3。
下一步是创建一个键数组。键(或关键帧)代表动画属性在特定帧的状态。在之前创建的动画中,每秒帧数设置为 1。然后,每秒,键数组中的一个键被达到。动画的键是一个对象数组,每个对象由两个属性组成:帧号,以及根据动画化的属性,关联的值(类型为 float,BABYLON.Vector2,BABYLON.Vector3 等),如下所示:
{
frame: number;
value: any;
}
让我们创建两个键,将盒子从位置(x = 0,y = 2.5,和 z = 0)转换到(x = 10,y = 10,和 z = 10)在第 20 帧,如下所示:
var keys = [
{
frame: 0,
value: new BABYLON.Vector3(0, 0, 0)
},
{
frame: 20,
value: new BABYLON.Vector3(10, 10, 10)
}
];
如果每秒帧数设置为 1,则第二个键(frame = 20)意味着盒子位置将在 20 秒后到达(x = 10,y = 10,和 z = 10)。
让我们为 simpleAnimation 动画管理器设置键,如下所示:
simpleAnimation.setKeys(keys);
最后,让我们将动画管理器附加到盒子上,并在场景中开始动画,如下所示:
// Add animation to the box
// Every BABYLON.Node object has the ".animations" property
box.animations.push(simpleAnimation);
// Start animation
scene.beginAnimation(
box, // Start animation(s) of the box
0, // Start key. Here 0
20, // End key. Here 20
true, // Loop the animation
1, // Speed ratio. Controls the speed of animation
() => { // Callback. Called when animation finished
console.log("Finished");
}
);
结果在开始时看起来类似于以下内容:

最后,一旦动画完成,结果看起来如下:

要重现之前的例子(围绕中心旋转的盒子),解决方案很简单;只需添加 360 个键,代表圆的每个度数,如下所示:
// Create animation
var complexAnimation = new BABYLON.Animation(
"boxAnimationComplex",
"position",
60, // 60 frames per second
BABYLON.Animation.ANIMATIONTYPE_VECTOR3,
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);
// Create keys
var complexKeys = [];
for (var i=0; i < 360; i++) {
// Transform the degrees into radians
var angle = BABYLON.Tools.ToRadians(i);
complexKeys.push({
frame: i,
value: new BABYLON.Vector3(10 * Math.cos(angle),
2.5,
10 * Math.sin(angle))
});
}
// Set the keys
complexAnimation.setKeys(complexKeys);
// Finally run the animation(s)
scene.beginAnimation(this._box, 0, 360, false, 1.0, () => {
console.log("Animation Finished");
});
要控制你的动画(开始、暂停、重新开始和停止),.beginAnimation函数返回一个BABYLON.Animatable类型的对象。返回的对象是即时创建的,允许你控制你的动画。让我们考虑一个与之前相同的例子:
var anim = scene.beginAnimation(this._box, 0, 360, false, 1.0, () => {
console.log("Animation Finished");
});
// Pause animation
anim.pause();
// Start the animation (when stopped or paused)
anim.start();
// Restart animation
anim.restart();
// Stop animation
anim.stop();
// Go to a specific frame
anim.goToFrame(180);
创建一个简单的动画
第一个例子,只有两个帧,可能会很重,因为我知道你很快就会创建一个辅助工具,一次性和永久。Babylon.js 考虑到了这一点,并提供了一个静态的CreateAndStartAnimation函数,该函数为你创建两个帧并开始动画。让我们只使用以下函数创建相同的动画:
var anim: BABYLON.Animation = BABYLON.Animation.CreateAndStartAnimation(
"quickAnimation", // name of the animation
box, // The mesh to animate
"position", // The property to animate
1, // frames per second
20, // number of frames of the animation
new BABYLON.Vector3(0, 0, 0), // The start value
new BABYLON.Vector3(10, 10, 10), // The end value
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE // The loop mode
);
如你所猜,这种方法由 Babylon.js 用于创建BABYLON.InterpolateValueAction(第七章,在对象上定义动作)。
事件管理
Babylon.js 动画的高级用法之一是在达到特定帧时调用一个函数。BABYLON.AnimationEvent类存在是为了允许你将一个或多个事件附加到特定帧。例如,考虑之前的例子(complexAnimation),如下所示:
var event = new BABYLON.AnimationEvent(
180, // The frame when the callback will be called
() => { // The function executed when the current frame is 180
console.log("My event was called!");
},
false // Only once? False, we want to call the callback every ti me// the frame is reached
);
// Add the event to the animation
complexAnimation.addEvent(event);
使用缓动函数平滑动画
Babylon.js 动画管理的先进功能之一是使用缓动函数来平滑动画并添加行为。
要快速查看缓动函数的外观(我们不一定记得每种缓动函数),你应该点击此链接(easings.net/en)。
将缓动函数应用于动画
要将缓动函数应用于动画,方法仅包括自定义已创建的动画。你只需为具有缓动函数的动画调用.setEasingFunction方法即可。
可用的缓动函数(与时间相关的曲线)如下所示:
- 圆形缓动:
BABYLON.CircleEase()

- 后退缓动:
BABYLON.BackEase(amplitude)

- 弹跳缓动:
BABYLON.BounceEase(bounces, bounciness)

- 三次方缓动:
BABYLON.CubicEase()

- 弹性缓动:
BABYLON.ElasticEase(oscillations, springiness)

- 指数缓动:
BABYLON.ExponentialEase(exponent)

- 二次方缓动:
BABYLON.QuadraticEase()

- 四次方缓动:
BABYLON.QuarticEase()

- 五次方缓动:
BABYLON.QuinticEase()

- 正弦缓动:
BABYLON.SineEase()

让我们从以下简单的例子开始(使用圆形缓动函数动画化盒子的旋转的.x 属性):
// Create and set easing function (circle ease)
var ease = new BABYLON.CircleEase();
easingAnimation.setEasingFunction(ease);
那就结束了,动画现在将遵循圆形缓动函数的公式,如下所示:
// Create animation
var easingAnimation = new BABYLON.Animation(
"easingAnimation",
"rotation.x", // Modify the .x property of rotation
10, // 10 frames per second
BABYLON.Animation.ANIMATIONTYPE_VECTOR3,
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
);
// Create keys
var simpleKeys = [
{
frame: 0,
value: 0
},
{
frame: 20,
value: Math.PI
},
{
frame: 40,
value: 0
}
];
// Set keys
easingAnimation.setKeys(simpleKeys);
// Push animation
this._box.animations.push(easingAnimation);
// Create and set easing function (circle ease)
var ease = new BABYLON.CircleEase();
easingAnimation.setEasingFunction(ease);
// Finally, start the animation(s) of the box
this._scene.beginAnimation(box, 0, 40, true, 1.0, () => {
console.log("Animation Finished");
});
由于很难通过截图来表示动画,您可以尝试自己使用不同的缓动函数。对于所有缓动函数,方法都是相同的,除了几个缓动函数构造函数需要一些参数以进行更多定制。
现在,让我们修改缓动函数的行为。缓动函数由模式组成,如下所示:
-
In,当动画开始时(进入):BABYLON.EasingFunction.EASINGMODE_EASEIN -
Out,当动画完成时(退出):BABYLON.EasingFunction.EASINGMODE_EASEOUT -
In & Out,当动画开始和结束时(进入和退出):BABYLON.EasingFunction.EASINGMODE_EASEINOUT
默认情况下,缓动模式设置为In(BABYLON.EasingFunction.EASINGMODE_EASEIN)。网站easings.net/en显示了在这三种模式下缓动函数的确切外观。
要指定缓动模式,只需在动画上调用.setEasingMode方法,如下所示:
// Create and set easing function (circle ease)
var ease = new BABYLON.CircleEase();
ease.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
easingAnimation.setEasingFunction(ease);
创建您自己的缓动函数
对于关于缓动函数的最后一个子主题,让我们讨论如果 Babylon.js 中没有可用的情况下如何创建自己的缓动函数。这个过程非常简单(除了可能与您的缓动函数相关的可能的困难数学公式)。
只需创建一个继承自BABYLON.EasingFunction类并实现BABYLON.IEasingFunction接口的类。唯一要做的就是实现.easeInCore(gradient: number)函数,该函数接受插值值作为参数。
让我们用一个幂缓动函数的例子来创建一个示例,如下所示:
export class PowerEase extends BABYLON.EasingFunction
{
constructor(public power: number = 2) {
// Call constructor of BABYLON.EasingFunction
super();
}
public easeInCore(gradient: number): number {
var y = Math.max(0.0, this.power);
return Math.pow(gradient, y);
}
}
Somewhere in your code:
// Create and set easing function (circle ease)
var customEase = new BABYLON.PowerEase(4);
customEase.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
easingAnimation.setEasingFunction(customEase);
导入和管理动画模型
为了以一个酷炫的演示结束本章,让我们讨论如何动画化角色或 3D 模型,通常来说。
3D 动画模型是如何工作的
如果您还记得,3D 模型的每个顶点都是由顶点着色器计算并投影到屏幕上的。实际上,动画的 3D 模型(如角色)也是通过相关的骨骼层次结构(该层次结构称为骨骼)进行动画化的。动画的 3D 模型也称为蒙皮网格。在 3D 引擎中,骨骼是无形的节点,它们被动画化(BABYLON.Animation)并发送到与 3D 模型关联的顶点着色器。换句话说,每个骨骼的变换被发送到顶点着色器并应用于相关的顶点。
要在理论方面更进一步,顶点着色器包含以下两个额外的缓冲区:
-
骨骼的矩阵权重(Vector4,表示每个骨骼对当前顶点的影响力强度)。
-
骨骼的矩阵索引(Vector4,直到每个顶点四个骨骼影响)就像索引缓冲区一样,但用于骨骼。换句话说,对于每个顶点,哪些骨骼影响该顶点(骨骼变换数组中的索引)。
注意
注意:静态 3D 模型的常用缓冲区是位置(必需)、索引(必需)、法线(非必需)、UVs(纹理坐标,非必需)和颜色(非必需)。
最后,对于每个顶点,公式(在 GLSL 中)如下所示:
uniform mat4 bones[NUMBER_OF_BONES]; // Sent by Babylon.js
mat4 boneTransform1 = bones[matricesIndices[0]] * matricesWeights[0];
mat4 boneTransform2 = bones[matricesIndices[1]] * matricesWeights[1];
mat4 boneTransform3 = bones[matricesIndices[2]] * matricesWeights[2];
mat4 boneTransform4 = bones[matricesIndices[3]] * matricesWeights[3];
mat4 finalTransform = transformedVertex * (
boneTransform1 + boneTransform2 +
boneTransform3 + boneTransform4
);
当艺术家使用建模软件提供的工具导出他们的 3D 模型(带有动画)时,Babylon.js 导出器(3ds Max 和 Blender)明确地将骨骼的权重缓冲区和骨骼的矩阵索引缓冲区写入导出文件,这些是数字数组。总之,对于动画 3D 模型,您不需要通过代码指定动画,导出器可以为您完成一切。让我们等到下一个子主题,学习如何动画 3D 模型。
导入并播放动画 3D 模型的动画
要导入一个动画 3D 模型,您必须使用BABYLON.SceneLoader类(第三章,创建、加载和绘制 3D 对象在屏幕上)来导入动画 3D 模型。实际上,.Load(和.Append)函数自动加载带有相关骨骼(节点层次结构)的动画 3D 模型,与.ImportMesh函数的回调提供加载的网格、粒子系统和骨骼不同,如下所示:
(meshes: AbstractMesh [], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void;
具体来说,由于动画节点是骨骼的骨骼,.beginAnimation函数的目标只是与您的动画 3D 模型关联的骨骼,而不是节点本身,如下所示:
// myAnimated3DModel is a BABYLON.Mesh;
var skeleton = myAnimated3DModel.skeleton; // get the skeleton
scene.beginAnimation(
skeleton, // Target to animate
0, // The start frame
150, // The end frame
true, // Loop ?
1.0, // Speed ratio
() => { // Animation end callback
console.log("Animation of skeleton finished!");
}
);
让我们通过以下代码片段使用.ImportMesh函数开始一个示例(可在示例文件中找到):
// Import an animated 3D model
BABYLON.SceneLoader.ImportMesh(
"", // Names of the specific
"./", // The root URL
"dude.babylon", // The name of the scene containing the meshes
scene, // The scene where to add the meshes
(meshes, particleSystems, skeletons) => { // callback success
// Simply start the animations of the skeleton associated
// To the mesh
this._scene.beginAnimation(skeletons[0], 0, 150, true, 1.0);
}
);
注意
注意:一个动画 3D 模型可以包含多个网格。这就是为什么ImportMesh函数可以返回多个网格的数组,以及骨骼数组中只有一个骨骼的原因。
结果(人物正在行走,动画从帧 0 播放到 150)如下所示:

使用.Load函数,方法相当不同。因为成功回调只提供加载的场景,您需要找到场景中存储的骨骼。为此,只需在场景上使用.getSkeletonByName函数,如下所示:
BABYLON.SceneLoader.Append(
"./", // The root url
"dude.babylon", // The name of the scene
scene, // The scene where to append
(scene) => { // The success callback
// Get the skeleton
var skeleton = this._scene.getSkeletonByName("Skeleton0");
// Simply animate the skeleton
scene.beginAnimation(skeleton, 0, 150, true, 1.0);
}
);
使用.Load函数的dude.babylon场景的结果如下所示:

摘要
使用 Babylon.js 创建动画可以使您的场景更加生动。如您所见,这些功能也相当简单易用,遵循 Babylon.js 的原则:KISS 原则(保持简单,傻瓜)。
现在,在本书的结尾,你已准备好与艺术家们合作,结合 Babylon.js 所学到的所有特性。使用后处理技术,管理和定制材质,创建和管理动画,加载场景,管理动作等等!
若要更进一步,你可以查看新特性(最近发布),例如材质库(一个包含特定易于使用的材质的库,如标准材质,例如水、地形材质、PBR、熔岩等!)!其中之一的新特性应该是 Babylon.js 自动处理的程序纹理,如火焰、地面、草地等。此外,若要更进一步,你还可以查看用于特定项目(需要创建一些烟雾或火焰,以及更美丽但更昂贵的场景)的粒子系统和阴影生成器。
在 Babylon.js 中享受乐趣!






浙公网安备 33010602011771号