babylon
一 基本使用
- 引入
<script src="https://preview.babylonjs.com/babylon.js"></script>
<script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
import * as BABYLON from "babylonjs";
import "babylonjs-gui";
import "babylonjs-loaders";
? 2.使用
var canvas = document.getElementById("renderCanvas"); // 得到canvas对象的引用
var engine = new BABYLON.Engine(canvas, true); // 初始化 BABYLON 3D engine
// 创建场景函数
var createScene = function() {// 创建一个场景scenevar scene = new BABYLON.Scene(engine);// 添加一个相机,并绑定鼠标事件var camera = new BABYLON.ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 2, 2, new BABYLON.Vector3(0, 0, 5),scene);camera.attachControl(canvas, true);// 添加一组灯光到场景var light1 = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(1, 1, 0), scene);var light2 = new BABYLON.PointLight("light2", new BABYLON.Vector3(0, 1, -1), scene);// 添加一个球体到场景中var sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 2}, scene);return scene;};var scene = createScene(); //Call the createScene function// 最后一步调用engine的runRenderLoop方案,执行scene.render(),让我们的3d场景渲染起来engine.runRenderLoop(function() {scene.render();}
// 监听浏览器改变大小的事件,通过调用engine.resize()来自适应窗口大小
window.addEventListener("resize", function() {engine.resize();
});
Vect3和Vect4
向量是3D世界中的基本单位,Vect3表示三维向量,也叫作三元数。Vect4表示四维向量,也叫作四元数。Vect3用得比较多,例如位置、旋转、放大缩小都使用了Vect3,用它来表示X、Y、Z 这3个不同的坐标。而Vect4一般用来做三维空间的旋转变换,具体可以看看知乎的一篇科普文章如何形象地理解四元数?
var position = new BABYLON.Vect3(1,1,1);//三维向量
var quaternion = new BABYLON.Vect4(1,1,1,1);//四维向量
Color3和Color4
顾名思义,这代表了颜色,Color3表示rgb,Color4表示rgba,只是Babylon中的Color值都是用0至1表示的,例如Color3(0.5,0.5,0.5),就相当于rgb(128,128,128)。
var rgb = new BABYLON.Color3(0.5, 0.5, 0.5);//颜色
var rgba = new BABYLON.Color3(0.5, 0.5, 0.5, 1);//透明度颜色
二 创建模型
1. MeshBuilder 方法
创建模型的方法一般是:
var shape = BABYLON.MeshBuilder.Create Shape(名称,配置项,场景);
配置项的参数允许你设置形状大小以及是否可以更新它之类的操作。
2.创建立方体
- 创建一个默认的立方体
var box = BABYLON.MeshBuilder.CreateBox("box", {}, scene);
- 创建一个带有配置项的立方体
var myBox = BABYLON.MeshBuilder.CreateBox("myBox", {height: 5, width: 2, depth: 0.5}, scene);
选项 | 值 | 默认值 |
---|---|---|
size | (number)每个边的长度 | 1 |
height | (number)立方体的高度 | size |
width | (number)立方体的宽度 | size |
depth | (number)立方体的深度 | size |
faceColors | (Color4[])六个颜色对象组成的数组,每个颜色代表一个面的显示颜色 | 每个面默认 Color(1,1,1,1) |
faceUV | (Vector4[]) 由六个四维向量组成的数组,每个代表一个面的uv映射 | 每个面的uv映射从 0,0 到 1,1 |
updatable | (boolean)如果网格是可更新的,则设置为true | false |
sideOrientation | (number)面的显示方向 | DEFAULTSIDE |
var rgb = new BABYLON.Color3(0.5, 0.5, 0.5);//颜色 new BABYLON.Color3()颜色对象
new BABYLON.Color3(0, 1, 0);
3.创建球体
- 创建默认的球体
var sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {}, scene); //默认的球体
- 创建一个设置配置项的球体
var mySphere = BABYLON.MeshBuilder.CreateSphere("mySphere", {diameter: 2, diameterX: 3}, scene);
选项 | 值 | 默认值 |
---|---|---|
segments | (number)水平的分段数 | 32 |
diameter | (number)球体的直径 | 1 |
diameterX | (number)X轴上的球的直径 | diameter |
diameterY | (number) Y轴上的直径 | diameter |
diameterZ | (number)Z轴上的直径 | diameter |
arc | (number)沿纬度线显示区域 值介于0到1 | 1 |
slice | (number)沿经度绘制显示 值介于0到1 | 1 |
updatable | (boolean)如果网格是可更新的,则设置为true | false |
sideOrientation | (number)面的显示方向 | DEFAULTSIDE |
4.创建平面
- 创建一个默认的平面
var plane = BABYLON.MeshBuilder.CreatePlane("plane", {}, scene);
- 创建一个带有配置项的平面
var myPlane = BABYLON.MeshBuilder.CreatePlane("myPlane", {width: 5, height: 2}, scene);
选项 | 值 | 默认值 |
---|---|---|
size | (number)平面每个边的长度 | 1 |
height | (number)平面的高度 | size |
width | (number)平面的宽度 | size |
updatable | (boolean)如果网格是可更新的,则设置为true | false |
sideOrientation | (number)面的显示方向 | DEFAULTSIDE |
frontUVs | (Vector4[])正面UV映射,只有在sideOrientation 设置为双面(BABYLON.Mesh.DOUBLESIDE)时使用 | Vector4(0,0,1,1) |
backUVs | (Vector4[])背面UV映射,只有在sideOrientation 设置为双面(BABYLON.Mesh.DOUBLESIDE)时使用 | Vector4(0,0,1,1) |
sourcePlane | (Plane)这是数学平面,用于修改平面网格的位置朝向 | 空值 |
sourcePlane是平面网格独有的选项,在创建数学平面时,前三个值代码当位置点在面的方向,第四个值为面与位置点的距离。不创建则面的默认朝向Z轴正方向 (0, 0, 1),如果需要修改朝向:
var sourcePlane = new BABYLON.Plane(0, -1, 1, 0);
sourcePlane.normalize();
上面将面的朝向修改为 (0, -1, 1)。
5.创建地面
创建一个默认的地面
var ground = BABYLON.MeshBuilder.CreateGround("ground", {}, scene);
创建一个自定义的地面
var myGround = BABYLON.MeshBuilder.CreateGround("myGround", {width: 6, height: 4, subdivsions: 4}, scene);
选项 | 值 | 默认值 |
---|---|---|
size | (number)地面每个边的长度 | 1 |
height | (number)地面的高度 | size |
width | (number)地面的宽度 | size |
updatable | (boolean)如果网格是可更新的,则设置为true | false |
subdivisions | (number)将地面分成的几块 | 1 |
地面创建有一个扩展是CreateGroundFromHeightMap
,它可以允许你创建起伏的地面而不是平面,后面我将更新相关的文章。
6.平面Plane
// 创建默认平面
var plane = BABYLON.MeshBuilder.CreatePlane("plane", {}, scene);
// 设置宽高并创建
var myPlane = BABYLON.MeshBuilder.CreatePlane("myPlane", {width: 5, height: 2}, scene);
option | 值类型&说明 | 默认值 |
---|---|---|
size | (number) 平面长度和高度,统一都是这个值,默认是个正方形 | 1 |
width | (number) 单独设置长度X,覆盖size的高度 | size |
height | (number) 单独设置高度Y,覆盖size的高度 | size |
updatable | (boolean) 如果设置为true,则表示该物体的顶点数据可以被更新 | false |
sideOrientation | (number) 物体可被看到的面,3D世界的计算比较耗费资源,一般相机移动到物体的背面,为了节省资源就默认不显示这个物体了 | 默认0,正面,可选:0 正面,1 背面,2 双面 |
frontUVs | (Vector4) 仅当sideOrientation设置为双面时可用,和faceUV的作用类似,因为平面没有深度只有2个面,只能按照这个方法来设置正面,类似background-position | Vector4(0,0, 1,1) |
backUVs | (Vector4) 仅当sideOrientation设置为双面时可用,和faceUV的作用类似,因为平面没有深度只有2个面,只能按照这个方法来设置背面,类似background-position | Vector4(0,0, 1,1) |
sourcePlane | (Plane) 一种平面的定位方法,可以更改平台的位置和角度,这也可以用position和rotation来实现 | null |
Playground 创建平面的实例
只有平面有sourcePlane这个选项,它提供了一种方法,用于修改平面的位置和方向,如果想让平面朝向某一个固定方向,这个方法挺有用。举个例子,现在我们只考虑平面的方向,在初始化时平面默认的方向是vector3(0,0,1)。如果希望把方向调整为vector3(0,-1,1),则使用如下代码:
var sourcePlane = new BABYLON.Plane(0, -1, 1, 0);
sourcePlane.normalize();
以上代码创建了一个用于定位方向的计算辅助平面。第4个参数是在方向轨道上移动的距离,在例子中设置为0,表示不移动。
三 创建线
1. 创建方法
用点的位置创建出Babylon.js可以识别的Vector3对象,并将这些Vector3放入按顺序放入一个数组内供Babylon.js使用。
var myPoints =[new BABYLON.Vector3(0, 0, 0),new BABYLON.Vector3(0, 1, 1),new BABYLON.Vector3(0, 1, 0)
];
-
- 用MeshBuilder.CreateLines函数,并将数组作为参数传入,实现线的创建:
var lines = BABYLON.MeshBuilder.CreateLines("lines", {points: myPoints}, scene);
属性 | 值 | 默认值 |
---|---|---|
points | (Vector3[ ]) 一个由Vector3对象组成数组 | |
updatable | (boolean) 如果当前线可以更新,需将此值设置为true | false |
instance | (LineMesh)创建一个更新实例的线时,需要将需更新实例对象传入 | null |
colors | (Color4[ ]) Color4组成的数组,对应每一个点的颜色 | null |
useVertexAlpha | (boolean) alpha混合,如果设置为false,将由更快的渲染速度 | tru |
-
- 用MeshBuilder.CreateDashedLines函数来创建虚线
var line = BABYLON.MeshBuilder.CreateDashedLines("lines", {points: dashedPoints,dashNb:dashedPoints.length, //预期所有的实现的个数dashSize:2, //实线的大小gapSize:2 //间隔的大小}, scene);
-
创建曲线
其实曲线的实现是由无限的直线拼接组成。接下来我们将通过CreateLines实现画一个圆。
首先我们使用三角函数将顶点创建出来:
//存储线的点的集合
var myPoints = [];
for(let i=0; i<=360; i++){myPoints.push(new BABYLON.Vector3(Math.sin(i/180*Math.PI), Math.cos(i/180*Math.PI), 0));
}
上面我们创建了361个顶点组成的数组,这个数组顶点刚好是沿Z轴旋转一周的顶点的集合。
然后,使用CreateLines添加到场景当中即可
//创建一条实线
var lines = BABYLON.MeshBuilder.CreateLines("lines", {points: myPoints}, scene);
四 物体的位置和旋转
1.坐标系
Babylon.js使用了两种 坐标参照系 ,即世界坐标系world和局部坐标系local,世界坐标系可以理解为:全局坐标系。世界坐标系的原点位置任何时候都是(0, 0, 0) 不会被改变,而局部坐标系会随着物体位置、旋转、缩放的改变而改变。例如:一个物体的初始位置是(0, 0, 0),它世界坐标系和局部坐标系就都是(0, 0, 0),当这个物体移动到位置(0, 10, 0)的时候,也就是向上移动了10的距离,这个时候它世界坐标系还是(0, 0, 0)和局部坐标系是(0, 10, 0)。
2.向量Vectors
所有位置,旋转和缩放比例均使用三维矢量分别设置,设置的格式为:BABYLON.Vector3(x,y,z)
3.位置Position
位置Position参照世界坐标轴来放置实验体,使用的是三维向量vector3 (x, y, z),局部坐标轴随着实验体的移动而移动。
//常规设置
实例对象.position = new BABYLON.Vector3(2, 3, 4);//或者,独立设置
实例对象.position.x = 2;
实例对象.position.y = 3;
实例对象.position.z = 4;
虽然局部坐标轴的位置发生了变化,但是它和世界坐标轴的方向是一致的。
4.旋转Rotation
//常规设置
实例对象.rotation = new BABYLON.Vector3(alpha, beta, gamma);//分开设置
实例对象.rotation.x = alpha; //围绕X轴旋转
实例对象pilot.rotation.y = beta; //围绕Y轴旋转
实例对象.rotation.z = gamma; //围绕Z轴旋转
5.缩放Scaling
缩放相对来说比较简单,不存在需要处理世界坐标的问题,我们只需要按照如下方法来沿着局部坐标X、Y、Z轴进行旋转即可。
//常规设置
实例对象.scaling = new BABYLON.Vector3(scale_x, scale_y, scale_z);//或者,独立设置
实例对象.scaling.x = 5; //参数为缩放的倍数
实例对象.scaling.y = 5;
实例对象.scaling.z = 5;
五 材质material
1. 材质说明与创建
-
材质是覆盖在物体上,给物体上色或者贴图,材质要配合灯光才能显示出效果,同一个材质是可以应用到多个物体上面去的
-
材质还可以设置一个透明度,会产生一个半透明的效果,通过设置材质的alpha属性来实现,取值范围是[0, 1]。
myMaterial.alpha = 0.5; //透明度
创建一个材质:
//第一个参数是材质名称,第二个是场景实例
var myMaterial = new BABYLON.StandardMaterial("myMaterial", scene);
2.材质和光线
无论是使用颜色还是纹理贴图来设置材质,它对光线的反射都有着不同的表现。
- 漫反射 - 在灯光下看到的材质基本颜色或纹理
- 镜面反射 - 在灯光照射下,高光给与材质的效果
- 自发光 - 材质的颜色或纹理就像一个灯光那样可以对外发出效果
- 环境 - 环境背光所照射出来的材质颜色或纹理
要看到漫反射和镜面反射的材质效果,必须要求创建至少一个光源。
要让环境背光照射出材质的环境颜色效果,还需要为场景设置一个环境颜色,如下所示:
3.为场景设置环境颜色
scene.ambientColor = new BABYLON.Color3(1, 1, 1);
4. 纹理贴图Texture
可以使用一张图片来生成纹理,纹理必须配合材质来使用,首先新建一个材质:
var myMaterial = new BABYLON.StandardMaterial("myMaterial", scene);
漫反射纹理diffuseTexture
镜面反射纹理specularTexture
自发光纹理emissiveTexture
环境纹理ambientTexture
来设置材质纹理贴图,这四个纹理可以同时设置也可以只设置其中某一个。注意:如果没有设置场景的环境颜色,环境纹理将没有效果。设置材质纹理的案例如下:
var myMaterial = new BABYLON.StandardMaterial("myMaterial", scene);
//PATH TO IMAGE,表示图片的路径,其实也可以使用base64格式的图片。
myMaterial.diffuseTexture = new BABYLON.Texture("PATH TO IMAGE", scene);
myMaterial.specularTexture = new BABYLON.Texture("PATH TO IMAGE", scene);
myMaterial.emissiveTexture = new BABYLON.Texture("PATH TO IMAGE", scene);
myMaterial.ambientTexture = new BABYLON.Texture("PATH TO IMAGE", scene);mesh.material = myMaterial; //记得设置物体的材质属性
六 相机Cameras
在Babylon.js提供的许多相机中,常用的目前是2个:
-
通用相机UniversalCamera,模仿第一人称的移动视角;
-
弧形旋转相机ArcRotateCamera,它是一种轨道相机,可以控制远近和角度。
-
如果要让用户对相机进行控制,就需要把输入事件绑定到初始化的canvas对象上,具体代码如下:
camera.attachControl(canvas, true);
第二个参数是可选的,默认false。当false的时候,canvas的默认行为被禁止,也就是调用了event.preventDefault(),如果为true,canvas保留默认行为。
0.触控相机 TouchCamera
使用触控设备来在场景中移动它
- 参数: 名称,位置,场景
var camera = new BABYLON.TouchCamera("TouchCamera", new BABYLON.Vector3(0, 1, -15), scene);
//使用 setPosition() 把相机准确地放置到我们期望的三维空间中的位置. 便捷的 setPosition() 方法将会做好剩下的事情
camera.setPosition(new BABYLON.Vector3(0, 15, -30));
- 限制相机参数
//对象上下移动范围
camera.lowerBetaLimit = Math.PI/2-Math.PI/6;
camera.upperBetaLimit = Math.PI/2+Math.PI/6;
//对象左右移动范围
camera.lowerAlphaLimit = -Math.PI/2-Math.PI/5;
camera.upperAlphaLimit = -Math.PI/2+Math.PI/5;
//对象远近移动范围
camera.lowerRadiusLimit = -Math.PI/3;
camera.upperRadiusLimit = Math.PI/3;camera.wheelPrecision = 0.5; //电脑滚轮速度 越小灵敏都越高
camera.pinchPrecision = 1; //手机放大缩小速度 越小灵敏都越高
- 相机相关设置
//可旋转相机
camera.attachControl(canvas, true);//不可旋转相机
camera.detachControl(canvas);camera.inertia //相机惯性
camera.targetScreenOffset.x=40; //相机X轴偏移量
camera.targetScreenOffset.y=-70; //相机Y轴偏移量
1.通用相机Universal Camera
通用相机最早在Babylon.js2.3版本的时候被引入,可以根据输入设备响应键盘、鼠标、触摸和游戏手柄的控制操作,不需要再特别指定控制器。通用相机扩展并且替代了自由相机Free Camera、触摸相机Touch Camera和游戏手柄相机Gamepad Camera,但是考虑到兼容性问题,以上三种相机任然可以使用。
在使用函数快速创建整个场景时,通用相机会作为默认相机添加到场景中,假如想像第一人称射击游戏那样来控制我们的场景视角,通用相机也是最好的选择。babylonjs首页的所有案例演示都使用了通用相机。就算是在电脑上使用Xbox的游戏手柄,首页中的大多数案例也能够由手柄操作来查看效果。
通用相机的默认控制:
- 键盘 - 左右方向键能够左右移动相机的视角,而上下方向键能够把相机拉近或推远。
- 鼠标 - 按住鼠标左键或右键进行拖动,可以控制相机的视角。
- 触摸屏 - 左右滑动可左右移动相机,上下滑动可拉近或推远;
- 游戏手柄 - 和设备相关,常规手柄就是上下左右键控制。
1.1 构建通用相机
// 参数顺序 : name相机名称, position相机放置的位置, scene场景实例
var camera = new BABYLON.UniversalCamera("UniversalCamera", new BABYLON.Vector3(0, 0, -10), scene);//相机观察的目标,在这里表示:相机放在(0,0,-10),镜头对准观察 (0,0,0)
camera.setTarget(BABYLON.Vector3.Zero());// 让相机响应用户操作
camera.attachControl(canvas, true);
2. 弧形旋转相机Arc Rotate Camera
弧形旋转相机总是指向一个给定的目标位置,而且能够以此目标位置作为中心进行360°旋转。它的操作体验和观察角度比通过相机更灵活,同样能够响应鼠标、键盘、游戏手柄和触摸事件。
可将弧形旋转相机视为一个围绕其目标位置进行观察的摄像机,或者想象一下,它更像是一个环绕地球轨道运行的卫星。相对于目标(地球)位置,相机自身的位置可以设置三个参数:即alpha是横向轴(单位弧度),beta是纵向轴(单位弧度),radius是目标target(地球) 核心的高度。 这是一个实例:
由于技术原因,将beta设置为0或者PI(180°)会引起问题,在这种情况下可以将beta在0或PI的基础上偏移0.1弧度(0.6°)。
增加alpha和beta的值,相机会沿顺时针方向变化。
也可以使用setPosition直接设置弧度旋转相机的向量位置,这样的情况下,alpha、beta、radius的值将会被覆盖。这比计算所需角度要容易,但是理解起来比较抽象。
camera.setPosition(new BABYLON.Vector3(0, 15, -30));
在使用setPosition()方法时,我们无需关心alpha,beta和radius。我们只需确保有一个target被设置,然后使用setPosition方法将camera放置到我们所希望的3D空间位置。setPostion()方法非常方便。
使用键盘左右键、鼠标或触摸屏向左或向右滑动可以更改Alpha值,而上下方向可以更改Beta值。
2.1 构建弧形旋转相机
// 参数: alpha, beta, radius, 目标位置position, scene场景实例var camera = new BABYLON.ArcRotateCamera("Camera", 0, 0, 10, new BABYLON.Vector3(0, 0, 0), scene);// 设置后会覆盖alpha, beta, radiuscamera.setPosition(new BABYLON.Vector3(0, 0, 20));// 让相机响应用户操作camera.attachControl(canvas, true);camera.lowerRadiusLimit = 2; //相机缩小半径上限 限制相机距离焦点的距离camera.upperRadiusLimit = 10; //相机放大半径上限 upperRadiusLimit的值不应小于lowerRadiusLimit,避免出现错误或不起作用。camera.wheelDeltaPercentage = 0.09; //鼠标滚轮灵敏度camera.checkCollisions = true; // 开启视角和场景物体的碰撞camera.upperBetaLimit = (Math.PI / 2) * 0.9; // 视角最大beta角度camera.lowerRadiusLimit = 0.1; // 视角最小距离camera.upperRadiusLimit = 1000; // 视角最大距离camera.radius = 1; // 初始化视角距离camera.setTarget(BABYLON.Vector3.Zero()); // 设置视角中心
可以使用 Ctrl + 鼠标左键 或 鼠标右键 让弧形旋转相机进行平移,也就是修改相机观察的目标位置,如果要禁用 Ctrl + 鼠标左键 平移,可以在attachControl中将useCtrlForPanning设置为false,代码如下:
camera.attachControl(canvas, noPreventDefault, useCtrlForPanning);
如果有需要,也可以完全取消平移操作:
//这个值用来控制平移的灵敏度,为0就是完全不响应平移
scene.activeCamera.panningSensibility = 0;
2.2相机自旋转
自动旋转效果是弧形旋转相机在没有用户交互时的一种自动围绕焦点旋转的效果。通过BABYLON.AutoRotationBehavior
实现。
你可以通过以下代码直接开启:
camera.useAutoRotationBehavior = true;
你也可以通过设置camera.autoRotationBehavior
对象一些属性来定义自动旋转的细节:
camera.autoRotationBehavior.idleRotationSpeed = 1; //自动旋转速度
camera.autoRotationBehavior.idleRotationWaitTime = 1000; //用户交互后多少时间开启自动旋转(毫秒)
camera.autoRotationBehavior.idleRotationSpinupTime = 1000; //从开始自动旋转到设置的旋转速度所需要的时间(毫秒)
camera.autoRotationBehavior.zoomStopsAnimation = true; //设置缩放是否会停止自动旋转
2.3 改变场景背景颜色
场景默为蓝色背景,通过clearColor可以改变背景颜色,此处我将背景颜色opacity值设为0,达到透明背景的效果。
scene.clearColor = new BABYLON.Color4(0, 0, 0, 0);
2.4 禁止镜头缩放
默然情况下,场景镜头是可以缩放的。在移动端上通过双指触摸可看到放大缩小效果。如果想禁止此功能,可以通过camera镜头上的属性进行设置。
let myCamera = scene.cameras[0];
myCamera.lowerRadiusLimit = myCamera.radius;
myCamera.upperRadiusLimit = myCamera.radius;
2.5 禁止镜头移动
默认情况下,场景镜头是可以移动的。在移动端上通过双指触摸可看到画布移动效果。
scene.activeCamera.panningSensibility = 0;
3. 跟随相机FollowCamera
跟随相机可以一直守望着ta。只要给它一个物体作为跟随目标,跟随相机能够从任何位置移动过来,一直紧盯目标物体,一旦物体移动了,跟随相机也会跟着移动。
创建跟随相机时需要设置它的初始位置,然后目标位置根据以下三个参数进行控制:
-
camera.radius - 相机距离目标的距离
-
camera.heightOffset - 相机朝目标上方的偏移
-
camera.rotationOffset - 相机与目标在水平面的角度偏移,默认相机和目标在同一水平面:rotationOffset增加则相机会朝着顺时针方向围绕物体水平转动。
通过设置camera.cameraAcceleration加速度和camera.maxCameraSpeed最大速度,可以控制相机移动到目标位置的动态效果。
3.1 构建跟随相机
// 参数: name相机名称, position相机位置, scene场景实例
var camera = new BABYLON.FollowCamera("FollowCam", new BABYLON.Vector3(0, 10, -10), scene);// 相机距离目标的距离
camera.radius = 30;// 相机朝目标上方的偏移
camera.heightOffset = 10;// 相机与目标在水平面的角度偏移,默认相机和目标在同一水平面
camera.rotationOffset = 0;// 相机靠近目标位置的加速度
camera.cameraAcceleration = 0.005// 相机达到的最大速度
camera.maxCameraSpeed = 10// 让相机响应用户操作
camera.attachControl(canvas, true);// 注意:设置相机目标,注意BabylonJS V 2.5的变化
// targetMesh为目标物体
camera.target = targetMesh; // babylon 2.4 版本以前
camera.lockedTarget = targetMesh; //babylon 2.5 版本以后
Playground 跟着目标物体移动的跟随相机示例
4. 立体相机AnaglyphCameras
立体相机由通用相机和弧形旋转相机扩展而来,专门用来给3D眼镜(红色和青色镜片的那种眼镜)使用,它们用到了后期滤波器技术。
4.1 构建立体通用相机
// 参数 : name, position, eyeSpace, scene
var camera = new BABYLON.AnaglyphUniversalCamera("af_cam", new BABYLON.Vector3(0, 1, -15), 0.033, scene);
4.2 构建立体弧形旋转相机
// 参数 : name, alpha, beta, radius, target, eyeSpace, scene
var camera = new BABYLON.AnaglyphArcRotateCamera("aar_cam", -Math.PI/2, Math.PI/4, 20, new BABYLON.Vector3.Zero(), 0.033, scene);
eyeSpace参数设置左眼视图和右眼视图之间的偏移量。 戴好3D眼镜后,可以尝试调整这个数值,取值为float。
可以通过访问Wikipedia全面了解立体技术。
5. 设备方向相机Device Orientation Camera
这是专门设计用于对手持设备方向事件做出响应的相机,例如智能手机向前或向后,向左或向右倾斜,陀螺仪会改变方向,最后让屏幕显示横屏或者竖屏。
5.1 构建设备方向相机
// 参数 : name, position, scenevar camera = new BABYLON.DeviceOrientationCamera("DevOr_camera", new BABYLON.Vector3(0, 0, 0), scene);// 设置相机的观察目标位置
camera.setTarget(new BABYLON.Vector3(0, 0, -10));// 设置相机响应移动和旋转的敏感度
camera.angularSensibility = 10;
camera.moveSensibility = 10;// 让相机响应用户操作
camera.attachControl(canvas, true);
Playground 设备方向相机示例
6 虚拟摇杆相机
这是特别为手持设备设计的,可以有一个虚拟摇杆显示在屏幕上,就像在手机上吃鸡的虚拟方向操纵杆那样。虚拟操纵杆是显示屏幕上的二维图形,用于控制相机的观察角度以及其他情况。
依赖
由于为触摸屏设备设计,所以需要依赖第三方插件hand.js
七 灯光
- 灯光的类型
有四种类型的灯光可以使用,并且它们有自己的配置属性,可以实现很多效果。
1.点光Point Light
点光被定义为在3D世界中一个唯一的点,然后光从这个点向各个方向发射。有一个很形象的例子来比喻点光,那就是电灯泡。
//第二个参数是点光的位置。
var light = new BABYLON.PointLight("pointLight", new BABYLON.Vector3(1, 10, 1), scene);
2.平行光Directional Light
平行光由方向定义,光从指定方向的任意位置发出,具有无限的范围。平行光的一个形象例子就是太阳光,就好比太阳照射到地球一样,会把地球面对太阳的方向全部照亮。
//第二个参数表示平行光的方向
var light = new BABYLON.DirectionalLight("DirectionalLight", new BABYLON.Vector3(0, -1, 0), scene); // 方向y: 1 从下往上
3.聚光灯Spot Light
聚光灯由位置position,方向direction,角度angle和指数exponent定义。上面的数值决定了圆锥形光束该从什么位置开始,然后朝哪个方向照射。
角度angle(以弧度为单位)定义了聚光灯圆锥形光束的大小(照亮的区域),指数exponent决定了光在到达目标的途中时,按距离衰减的速度。
聚光灯的简单使用如下:参数(位置,方向,角度,指数)
//第2个参数是位置、第3个参数是方向,第4个参数角度angle,第5个参数指数exponent
var light = new BABYLON.SpotLight("spotLight", new BABYLON.Vector3(0, 30, -10), new BABYLON.Vector3(0, -1, 0), Math.PI / 3, 2, scene);
4.半球光Hemispheric Light
半球光是模拟环境光的简便方法,它由一个方向参数定义,位于世界坐标中心(0,0,0),一般设置灯光方向朝正上方。而灯光只有设置了颜色属性,才能看到完整的效果。
//第2个参数是方向
var light = new BABYLON.HemisphericLight("HemiLight", new BABYLON.Vector3(0, 1, 0), scene);
- 灯光颜色属性
灯光具有三个会影响颜色的属性,其中漫反射diffuse和镜面反射specular对上述4种灯光都会起作用,而底色groundColor仅适用于半球光Hemispheric Light。
-
灯光的漫反射给照射到的对象一个基本颜色;
-
镜面反射在物体表面产生一个高光颜色点;
绿色的镜面反射颜色与红色的漫反射颜色同时设置后,物体表面会产生一个黄色的高光。
var createScene = function () {var scene = new BABYLON.Scene(engine);var camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 4, 5, BABYLON.Vector3.Zero(), scene);camera.attachControl(canvas, true);//Light direction is up and leftvar light = new BABYLON.HemisphericLight("hemiLight", new BABYLON.Vector3(-1, 1, 0), scene);light.diffuse = new BABYLON.Color3(1, 0, 0); 漫反射light.specular = new BABYLON.Color3(0, 1, 0); 镜面反射light.groundColor = new BABYLON.Color3(0, 1, 0); 底色var sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {}, scene); return scene;};
- 灯光最大数量限制
Babylon.js可以创建和注册任意数量的灯光,但是我们要注意单个标准材质只能同时处理有限的灯光效果(默认是4个,这意味着只有一开始创建的前4个灯光有作用)。不过我们能够对这个最大数量进行修改。
var material = new BABYLON.StandardMaterial("mat", scene);
material.maxSimultaneousLights = 6;//设置最大响应6个灯光
但是请小心,如果使用过多的动态灯光,Babylon.js将生成更大的着色器,这意味着性能会受到影响,所以对于低配设备,例如手机、小型平板电脑等,将存在不兼容的情况。所以为了增强兼容性,只要一旦出现了上述情况,babylon.js将使用更少的灯光重新编译着色器。
- 开灯、关灯或调整亮度
每一种灯光都能被设置为关闭:
light.setEnabled(false);
scene.lights[0].dispose();
如果要开启可以使用以下代码:
light.setEnabled(true);
想调暗或调亮灯光吗?只要设置强度属性即可(默认值为1,正常亮度)
light0.intensitysetDirectionToTarget = 0.5;
light1.intensity = 2.4;
对于点光和聚光灯,可以使用范围range属性,设置灯光到达目标的距离
light.range = 100;
- 指定照亮哪些物体
当创建一个灯光时,场景中所有的物体都会被照亮。有2种方法可以让某些物体不被照亮:
-
一种是把需要排除的物体加入到excludedMeshes数组中,
-
一种是把需要照亮的物体加入到includedOnlyMeshes数组中,不在数组中的物体将不被照亮。
-
展示两个属性的使用方法,即从light0排除了2个物体,只用light1照亮同样2个物体,最终的效果就是light0照亮其余23个物体,而light1照亮2个物体。
var createScene = function () {var scene = new BABYLON.Scene(engine);var camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, Math.PI / 4, 8, BABYLON.Vector3.Zero(), scene);camera.attachControl(canvas, true);//Light direction is up and leftvar light0 = new BABYLON.HemisphericLight("hemiLight", new BABYLON.Vector3(-1, 1, 0), scene);light0.diffuse = new BABYLON.Color3(1, 0, 0);light0.specular = new BABYLON.Color3(0, 1, 0);light0.groundColor = new BABYLON.Color3(0, 1, 0);var light1 = new BABYLON.HemisphericLight("hemiLight", new BABYLON.Vector3(-1, 1, 0), scene);light1.diffuse = new BABYLON.Color3(1, 1, 1);light1.specular = new BABYLON.Color3(1, 1, 1);light1.groundColor = new BABYLON.Color3(0, 0, 0);var sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 0.5}, scene); var spheres = [];for(var i = 0; i < 25; i++) {spheres[i] = sphere.clone("sphere" +i);spheres[i].position.x = -2 + i%5;spheres[i].position.y = 2 - Math.floor(i/5);} light0.excludedMeshes.push(spheres[7], spheres[18]); //数组添加不照亮的元素light1.includedOnlyMeshes.push(spheres[7], spheres[18]) return scene;};
- 光照贴图Lightmaps
复杂光照效果的计算成本非常高,可能会引起性能问题,所以为了节省资源,我们可以使用光照贴图Lightmaps,它可以提前将计算好的光照信息存储在物体的纹理中,相当于光照不用逐帧计算,以达到增加性能的效果。
var lightmap = new BABYLON.Texture("lightmap.png", scene); //新建纹理,第一个参数为贴的图片,第二个参数为实例场景
var material = new BABYLON.StandardMaterial("material", scene); //新建材质
material.lightmapTexture = lightmap; //将纹理又应用到材质的光照贴图中
- 投影纹理Projection Texture
在某些情况下,从纹理定义灯光效果更好,因为灯光的漫反射只有常规的一个基本颜色,效果不够炫酷。想象一下我们正在模拟灯光透过教堂玻璃反射到地面上的五颜六色效果(中国人可能很少知道教堂玻璃,如下图)。对于投影或者KTV气氛灯,也基本是这种很多颜色的灯光效果。
为了实现以上的效果,可以使用灯光的projectionTexture属性。 不过到目前为止,仅 聚光灯Spotlight 支持此功能。
var spotLight = new BABYLON.SpotLight("spot02", new BABYLON.Vector3(30, 40, 30), //创建聚光灯
new BABYLON.Vector3(-1, -2, -1), 1.1, 16, scene);
spotLight.projectionTexture = new BABYLON.Texture("textures/stainedGlass.png", scene); //创建投影文理
Playground 投影纹理示例
为了控制投影的方向和范围,我们可以调整以下几个灯光属性:
-
projectionTextureLightNear:接近投影纹理的范围。 平面在进入光照范围之前,投影纹理不显示。
-
projectionTextureLightFar:远离投影纹理的范围。平面在进入光照范围之前,投影纹理不显示。
-
projectionTextureUpDirection:帮助定义朝向灯光照射方向的光空间,并与其向上对齐。
将以上的三个投影配置信息与灯光法线值相乘,以便于投影纹理更适合Babylon.js的照明应用。此外,它们仅仅影响漫反射的值,所以如有必要,请修改一下灯光的镜面反射颜色,让投影纹理更好的适配自己的场景。
八 动画Animation
- 第一种方法:定义一组keys,同时在每一个key中设置物体的状态,例如位置、旋转、缩放等。
- 第二种方法:往往是对于更加复杂的动画,例如按照sin曲线运动,那就是通过在场景运行的循环中(runRenderLoop、onBeforeStepObservable)直接设置和改变物体的状态值。
1. 基本动画
动画的创建基于一个Animation对象,由各种属性和一组keys组成。每个key代表其在给定时间的动画状态值。为了完成本章介绍的动画场景,咱们需要创建一个场景并添加若干元素:
创建场景,相机,光照,盒子
//记得先创建: [scene, light, camera]
//创建一个立方体,name为Box1
var box1 = BABYLON.Mesh.CreateBox("Box1", 10.0, scene);
box1.position.x = -20;
创建Animation对象
我们的目标:移动上面创建的立方体“ box1”。 首先,创建我们的Animation对象:
var animationBox = new BABYLON.Animation("myAnimation", "scaling.x", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
让我们对Animation的参数进行详细解读:
-
参数1 - 动画的名称
-
参数2 - 这个与对象的属性相关,可以是任何物体的属性,具体取决于需要更新的内容,上例中我们要在X轴的方向缩放 Box1,所以这里设置为 scaling.x 。
-
参数3 - 每秒请求的帧数:动画中可能达到的最高FPS。
-
参数4 - 数值变化类型。根据参数2的配置,决定要修改的值类型:浮点数(例如x轴位置Position.x),矢量(例如位置Position)还是四元数(例如旋转rotationQuaternion)。 确切的值是:
- 浮点数:BABYLON.Animation.ANIMATIONTYPE_FLOAT
- 二维向量:BABYLON.Animation.ANIMATIONTYPE_VECTOR2
- 三维向量:BABYLON.Animation.ANIMATIONTYPE_VECTOR3
- 四元数:BABYLON.Animation.ANIMATIONTYPE_QUATERNION
- 矩阵:BABYLON.Animation.ANIMATIONTYPE_MATRIX
- 颜色:BABYLON.Animation.ANIMATIONTYPE_COLOR3
请注意有一个坑,也就是设置为 矩阵BABYLON.Animation.ANIMATIONTYPE_MATRIX 的情况下,即使两个key之间的值都是矩阵,也不会进行插值计算,而是把前后两个矩阵中的每个值进行单独的插值计算,这可能不符合咱们的要求。如果需要开启矩阵的插值计算,请使用 Animation.AllowMatricesInterpolation=true 来启用此功能。如果启用了矩阵插值,系统默认使用 matrix.Lerp 方法来进行插值计算,但是这个方法精度不够,所以也可以设置 Animation.AllowMatrixDecomposeForInterpolation=true 来选择 matrix.DecomposeLerp 作为插值计算方法,这个方法精度高,但是效率低,请各位做好取舍。
另外还请注意,如果 animation.loopMode === BABYLON.Animation.ANIMATIONLOOPMODE_RELATIVE 也就是第五个参数的值,则不会开启矩阵插值。
Playground 这是一个虚拟人物切换姿势的动画案例
- 参数5 - 动画的在执行完一个周期后,需要执行的行为模式,例如继续运动、从头开始执行还是立即停止,可选三种模式:
- 相对,相对运动,即:执行完一次,在接着最后的状态,继续执行:BABYLON.Animation.ANIMATIONLOOPMODE_RELATIVE
- 循环,动画循环执行,即:执行完一次,从头开始再执行:BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
- 常量,动画执行一次就停止不动了:BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
创建包含所有动画关键点的数组
var keys = []; //在动画key==0时, 缩放scaling的值是"1"keys.push({frame: 0,value: 1});//在动画key==20时, 缩放scaling的值是"0.2"keys.push({frame: 20,value: 0.2});//在动画key==100时, 缩放scaling的值是"1"keys.push({frame: 100,value: 1});
请注意!加入keys中的frame有顺序要求,必须是升序。
对于二维向量Vector2、 三维向量Vector3 和 四元数Quaternion,动画默认是使用线性插值的计算方法,即均速;我们可以在key中设置正切 inTangent 和 外切outTangent属性,以此来使用spline插值,
- 可以由正切inTangent 和 外切outTangent来设置变速。
var keys = []; keys.push({frame: 0,value: BABYLON.Vector3.Zero(),outTangent: new BABYLON.Vector3(1, 0, 0)});keys.push({frame: 20,inTangent: new BABYLON.Vector3(1, 0, 0),value: new BABYLON.Vector3(1, 1, 1),outTangent: new BABYLON.Vector3(-1, 0, 0)});keys.push({frame: 100,inTangent: new BABYLON.Vector3(-1, 0, 0),value: BABYLON.Vector3.Zero()});
keys动画组加入到Animation对象中
- 把之前定义的keys动画组加入到Animation对象中
animationBox.setKeys(keys);
- 把动画和物体关联起来
box1.animations = [];
box1.animations.push(animationBox);
- 最后,我们需要一行代码来启动自己的动画:
scene.beginAnimation(box1, 0, 100, true);
- 我们还能够直接逆向运行动画,也就是从尾到头的顺序执行动画:
//只需要交换第二个和第三个参数
scene.beginAnimation(box1, 100, 0, true);
以上的函数beginAnimation,它所包含的参数如下表所示:
名称 | 类型 | 描述 | 可选参数 |
---|---|---|---|
target | any | 执行动画的对象,例如物体 | 必选 |
from | number | 开始的帧 | 必选 |
to | number | 结束的帧 | 必选 |
loop | boolean | 假如为true,则动画循环 (依赖BABYLON.Animation.ANIMATIONLOOPMODE) | 可选 |
speedRatio | number | 默认 : 1,动画执行的速度比例,可以减慢或加快动画 | 可选 |
onAnimationEnd | () => void | 在动画执行结束时触发的回调函数, 如果动画是手动停止也会触发 (也依赖于 ANIMATIONLOOPMODE) | 可选 |
animatable | Animatable | 指定执行动画的实例Animatable | 可选 |
stopCurrent | boolean | 调用执行函数时,如果该对象的动画还在运行中,是否停止这个运行中的动画,默认为true | 可选 |
BABYLON.Animatable 对象
beginAnimation函数返回一个 BABYLON.Animatable 对象,代表 执行动画实例 (例如使用getAnimationByTargetProperty函数)。
而BABYLON.Animatable对象,支持以下的方法:
-
pause()暂停
-
restart()重新开始
-
stop()停止
-
reset()重置
下面的例子展示了在执行beginAnimation方法时,用变量把执行动画的实例保存了下来,之后再做进一步的动画控制。
var newAnimation = scene.beginAnimation(box1, 0, 100, true);
然后暂停正在运行的动画
newAnimation.pause();
- 立方体的 属性animations 是一个数组, box1.animations.push(animationBox); ,这意味着一个物体时可以加入多个动画形成动画组合,
- 所以执行 beginAnimation 方法时,形成的 BABYLON.Animatable 对象也可能包含一组动画,它们被存储在 Animatable’s ._animations 数组中,
- 当我们执行BABYLON.Animatable 对象的pause、restart、stop、reset方法时,Animatable’s ._animations 中的所有动画都会响应这些命令。
- 可以发现BABYLON.Animatable 对象非常有用,我们控制动画时常都要访问这些它们,可以通过 **scene.getAnimatableByTarget() **这个场景的方法来枚举所有的这些对象。
2.动画和异步promises
自从Babylon.js3.3版本以后,我们能使用promises等待动画实例animatable执行完成:
var anim = scene.beginAnimation(box1, 0, 100, false);console.log("before");
await anim.waitAsync();
console.log("after");
?
3.控制动画-差值函数Controlling animations
每一个BABYLON.Animation对象都有一个被称为 当前帧 的属性,对应当前动画中keys数组中的一个key。
对于高级的关键帧动画,我们可以在keys中的两个key之间自定义插值函数,也就是两个关键帧之间定义一个过渡的算法函数,实现特殊的动画过渡效果。在默认情况下的插值函数如下所示:
//表示浮点数的插值,例如scaling.x,position.y
BABYLON.Animation.prototype.floatInterpolateFunction = function (startValue, endValue, gradient) {return startValue + (endValue - startValue) * gradient;
};//表示四元数的插值,例如matrix
BABYLON.Animation.prototype.quaternionInterpolateFunction = function (startValue, endValue, gradient) {return BABYLON.Quaternion.Slerp(startValue, endValue, gradient);
};//表示三维向量Vector3的插值,例如scaling,postion,rotation
BABYLON.Animation.prototype.vector3InterpolateFunction = function (startValue, endValue, gradient) {return BABYLON.Vector3.Lerp(startValue, endValue, gradient);
};
以上的函数,都可以进行重写,定义自己的插值函数,我们可以自定义的所有插值函数如下所示:
- floatInterpolateFunction
- quaternionInterpolateFunction
- quaternionInterpolateFunctionWithTangents
- vector3InterpolateFunction
- vector3InterpolateFunctionWithTangents
- vector2InterpolateFunction
- vector2InterpolateFunctionWithTangents
- sizeInterpolateFunction
- color3InterpolateFunction
- matrixInterpolateFunction
4.动画辅助创建函数Helper function
- 一个扩展函数来快速创建动画:
Animation.CreateAndStartAnimation = function(name, mesh, targetProperty, framePerSecond, totalFrame, from, to, loopMode);
我们来详细剖析一下这个函数:
-
**参数一:**自定义动画名称; **参数二:**绑定动画的目标; **参数三:**要改变的属性(position,rotation,scaling)
-
**参数四:**每秒帧数; **参数五:**总帧数; **参数六:**开始帧时改变属性的值; **参数七:**结束帧时改变属性的值; 参数八:循环模式
-
函数只能定义2个关键帧,也就是对应之前的keys数组,只能插入两个key对象。
-
函数只能针对物体起作用。
-
函数一执行,动画就马上生效,新建和执行两个步骤被统一在一起了。
这是使用 CreateAndStartAnimation() 函数的简单示例:
BABYLON.Animation.CreateAndStartAnimation('boxscale', box1, 'scaling.x', 30, 120, 1.0, 1.5);
以上等价于
var animationBox = new BABYLON.Animation("myAnimation", "scaling.x", 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);var keys = []; keys.push({frame: 30,value: 1
});keys.push({frame: 120,value: 1.5
});animationBox.setKeys(keys);box1.animations = [];
box1.animations.push(animationBox);scene.beginAnimation(box1, 0, 120, true);
5. 动画混合Animation blending
使用 enableBlending = true 来开启动画混合模式,混合模式就是说动画的执行时以当前物体的状态为准,例如物体初始位置是在(0, 0, 0)原点,先执行一个动画改变位置,当运动到(0, 20, 0)的时候,停止原有动画,加入一个新动画,新动画的第一帧从(0,0,0)开始,默认情况下物体会回到(0,0,0)从头执行新动画,但是如果设置了 enableBlending = true ,则新动画会从(0, 20, 0)的当前位置执行。这个特性对动画改变时的平滑过渡,非常有效。
6. 属性重写Overriding properties
当有一个物体,物体上存在多个动画或者一个骨骼动画(所有的骨骼都能运动),我们可以使用animationPropertiesOverride方法来为物体中的所有子动画指定一个默认属性。这些属性将重写子动画的属性值:
var overrides = new BABYLON.AnimationPropertiesOverride();overrides.enableBlending = true;
overrides.blendingSpeed = 0.1;skeleton.animationPropertiesOverride = overrides;
以下是能够被重写的属性清单:
- enableBlending
- blendingSpeed
- loopMode
请注意,如果animation的对象没有包含以上属性,scene.animationPropertiesOverride还是能够生效。
7. 缓动函数Easing functions
我们可以在动画中加入一些缓动函数。如果想了解更多关于缓动函数的信息,可以阅读以下的网站:
-
缓动函数文档
-
缓动函数速查表
所有的缓动函数都已经在Babylon中实现了,当然,定义自己的数学函数也是可以的。
以下是预定义好的缓动函数:
-
BABYLON.CircleEase()
-
BABYLON.BackEase(amplitude)
-
BABYLON.BounceEase(bounces, bounciness)
-
BABYLON.CubicEase()
-
BABYLON.ElasticEase(oscillations, springiness)
-
BABYLON.ExponentialEase(exponent)
-
BABYLON.PowerEase(power)
-
BABYLON.QuadraticEase()
-
BABYLON.QuarticEase()
-
BABYLON.QuinticEase()
-
BABYLON.SineEase()
-
BABYLON.BezierCurveEase()
可以使用EasingMode属性更改缓动函数的行为,即动画的插值更新方式。有三种可选的值:
-
BABYLON.EasingFunction.EASINGMODE_EASEIN:插值遵循与缓动函数关联的数学公式。
-
BABYLON.EasingFunction.EASINGMODE_EASEOUT:插值遵循100%插值减去与缓动函数关联的公式输出值,可以理解为最大值减去运行中的值,相当于EASEIN倒过来。
-
BABYLON.EasingFunction.EASINGMODE_EASEINOUT:插值将EaseIn用于动画的前半部分,将EaseOut用于后半部分。
以下是一个简单的实例,在CircleEase缓动函数中为圆环设置动画:
//创建一个位置的三维向量Vector3动画, FPS为30
var animationTorus = new BABYLON.Animation("torusEasingAnimation", "position", 30, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);// 圆环的目标位置
var nextPos = torus.position.add(new BABYLON.Vector3(-80, 0, 0));// 动画的keys
var keysTorus = [];
keysTorus.push({ frame: 0, value: torus.position });
keysTorus.push({ frame: 120, value: nextPos });
animationTorus.setKeys(keysTorus);// 穿件缓动函数CircleEase
var easingFunction = new BABYLON.CircleEase();//对于每个缓动函数,可以选择 EASEIN (默认), EASEOUT, EASEINOUT三种
easingFunction.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);// 添加缓动函数到动画中
animationTorus.setEasingFunction(easingFunction);// 向圆环中的动画集合中添加动画实例
torus.animations.push(animationTorus);// 最后,执行圆环动画,从0到120帧,循环播放
scene.beginAnimation(torus, 0, 120, true);
8. 复杂动画
复杂的动画可以在每一帧进行内容的修改,所以运行时计算的代码必须在此函数中:
scene.registerBeforeRender(function () {//在这里实现动画计算,这里面的代码每一帧都会执行一次
});
registerBeforeRender() 设置的功能在每帧之前运行(通常fps是60,就是执行60次),所以可以通过对对象的属性进行更改,来实现动画效果。
这里是一个关于复杂动画的简单例子:
复杂动画示例
var createScene = function () {// 创建场景var scene = new BABYLON.Scene(engine);// 创建相机var camera = new BABYLON.FreeCamera("camera1", new BABYLON.Vector3(0, 5, -10), scene);// 设置以摄影机到场景原点为目标camera.setTarget(BABYLON.Vector3.Zero());// 让相机响应用户操作,即挂载到画布上camera.attachControl(canvas, true);//创建光源var light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);// 设置光源强度light.intensity = 0.7;// 创建盒子var box = BABYLON.MeshBuilder.CreateBox("box", {}, scene);// 为动画设置方向标志var direction = true;// 此函数中的代码每秒将运行约60次scene.registerBeforeRender(function () {//检查箱子是否向右移动if (box.position.x < 2 && direction) {// Increment box position to the rightbox.position.x += 0.05;}else {// Swap directions to move leftdirection = false;}// 检查箱子是否向左移动if (box.position.x > -2 && !direction) {// Decrement box position to the leftbox.position.x -= 0.05;}else {// 交换向右移动的方向direction = true;}});return scene;};
此功能对于复杂动画(例如游戏)非常有用,因为游戏中角色的移动,需要依靠许多参数来决定。
将所有动画结合起来,如果做得很好,这个功能将发挥很大作用
9. 为动画添加事件
从Babylon.js V2.3版本开始,我们可以添加一个事件到动画的特定帧。
事件是一个函数,它将在特定的帧时被执行。
使用起来非常简单:
// 用3个参数来创建事件:
// -参数1:事件触发时的帧,一个数值
// -参数2:执行的动作,在这里写代码
// -参数3:事件是否只执行一次,默认是false,每次都执行
var event1 = new BABYLON.AnimationEvent(50, function() { console.log("Yeah!"); }, true);
// 把事件添加到动画中
animation.addEvent(event1);
10. 帧同步Deterministic lockstep
有时确保动画,物理和游戏逻辑代码的同步,是非常重要的事,而且需要通过 帧速率方差frame-rate variance 解耦。这在两方面是非常有用的,首先是能够重放场景的演变过程,其次是在多用户环境下,让各个客户端之间的差异状态最小化,就像王者荣耀、魔兽、星际、CS等游戏一样,双方联机,两边的状态必须是一致的内容。
帧同步的原理是量化状态的执行时间,具体方法为:在固定的频率下,以离散的时间步长来更新状态,并且保留一个累加器,以便将超过的时间带入下一帧更新。
为此,需要通过以下两个选项来初始化Babylon引擎:
this.engine = new BABYLON.Engine(theCanvas, true, {deterministicLockstep: true,lockstepMaxSteps: 4
});
这样做的话,场景将以离散的 时间步长量timeStep amount chunks,来渲染量化的物理和动画步骤。就像在物理引擎中设置的那样,例如:
var physEngine = new BABYLON.CannonJSPlugin(false);
newScene.enablePhysics(this.gravity, physEngine);
physEngine.setTimeStep(1/60);
使用上面的代码,引擎将以60Hz的频率,来运行离散步骤,如果帧渲染时间较晚,引擎将在渲染帧之前,尝试计算最多4个步骤(lockstepMaxSteps)以恢复最终累积的延迟。也就是在用户的客户端保持一致的状态,慢的人会接受最新的状态进行更新。
请注意,当显式创建CannonJSPlugin时,必须在其构造函数中传递false作为_useDeltaForWorldStep参数,以禁用CannonJS内部累加器。
为了与步骤同步运行逻辑代码,场景中有以下两种observables方法:
newScene.onBeforeStepObservable.add(function(theScene){console.log("Performing game logic, BEFORE animations and physics for stepId: "+theScene.getStepId());
});newScene.onAfterStepObservable.add(function(theScene){console.log("Performing game logic, AFTER animations and physics for stepId: "+theScene.getStepId());
});
以上方法在动画和物理运行之前或之后,允许运行任意的逻辑代码。
在以下示例中,可以在控制台console中看到将球体视为静止的stepId以及旋转框的旋转值。 无论帧速率如何,多次运行都将始终产生相同的值。
Babylon_loader基本用法
要加载指定的类型的文件,Babylon.js首先需要引入相应文件的插件。
目前支持的文件格式类型:
- glTF
- OBJ
- STL
如果想要快速添加对所有的文件格式的支持,可以将以下脚本引入到页面中:
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>
SceneLoader.Append
从文件文件夹中加载所有的模型资源并添加到babylon场景中
配置项:文件夹路径,资源名称,场景对象,回调函数
BABYLON.SceneLoader.Append("./", "duck.gltf", scene, function (scene) {// 模型添加成功后,执行场景对象的一些方法
});
- 演示地址
SceneLoader.Load
从文件中加载模型文件并创建一个新的场景对象
配置项:文件夹路径,资源名称,引擎对象,回调函数
BABYLON.SceneLoader.Load("/assets/", "batman.obj", engine, function (newScene) { // ...
});
SceneLoader.ImportMesh
从文件中加载模型并将它们添加到场景中,这个和Append区别在于,回调返回的是模型对象而不是场景对象。我们可以在成功的回调里面对模型再进行进一步的处理。
配置项:将要添加到场景中的模型名称或为空(将所有模型或者骨骼加载),文件夹路径,资源名称,场景对象,回调函数
BABYLON.SceneLoader.ImportMesh(["myMesh1", "myMesh2"], "./", "duck.gltf", scene, function (meshes, particleSystems, skeletons) {// 对骨骼和模型进行进一步操作// 对于glTF文件,粒子系统默认为空
});
- 演示地址
SceneLoader.LoadAssetContainer
从文件夹中添加所有的资源到babylon,但不会自动添加到场景中
配置项:文件夹路径,资源名称,场景对象,回调函数
BABYLON.SceneLoader.LoadAssetContainer("./", "duck.gltf", scene, function (container) {var meshes = container.meshes;var materials = container.materials;//...// Adds all elements to the scenecontainer.addAllToScene();
});
回调函数会返回一个容器对象,里面包含所有模型加载的资源光源,模型,相机等等。如果需要将模型添加到场景,需要使用:
container.addAllToScene();
将添加的容器内容从场景中删除,则使用:
container.removeAllFromScene();
SceneLoader.AppendAsync
我们还可以通过使用Async函数来实现链式回调:
BABYLON.SceneLoader.AppendAsync("./", "duck.gltf", scene).then(function (scene) {// do something with the scene
});
高级用法
使用onPluginActivatedObservable设置属性和调用特定于特定加载器的方法。
BABYLON.SceneLoader.OnPluginActivatedObservable.add(function (loader) {if (loader.name === "gltf") {// do something with the loader// loader.<option1> = <...>// loader.<option2> = <...>// loader.dispose();}
});
或者,静态同步SceneLoader函数返回插件。
var loader = BABYLON.SceneLoader.Load("./", "duck.gltf", engine, function (scene) {// do something with the scene
});// do something with the loader
// loader.<option1> = <...>
// loader.<option2> = <...>
// loader.dispose();