第一节完成了模型的自定义生成,第二节开始完成导航盒场景的制作,这里我使用多场景的方式制作导航盒,这种方式可以使导航盒场景和原场景解耦,避免产生各种各样的光照,深度,后处理等方面的冲突。首先进行场景的创建和初始化工作,这里会单独会创建一个Scene,Camera和DirectBox。这里和主场景一致创建了一个ArcRotateCamera,方便两个场景摄影机的同步,注意这个摄像机是正交摄像机,radius的大小对视角的没有影响,radius的正负会影响旋转。
class DirectBoxScene {
......
constructor(engine: BABYLON.Engine, bindCamera: BABYLON.ArcRotateCamera) {
this.engine = engine;
//创建导航盒场景
this.scene = new B.Scene(engine);
//将场景的自动清除关闭,该场景将在主场景渲染结束后渲染,关闭autoClear避免将主场景渲染的图像清除。
this.scene.autoClear = false;
// //将场景的clearColor设置为透明
// this.scene.clearColor.a = 0;
this.scene.clearColor.set(0, 0, 0, 1);
this.selfCamera = new B.ArcRotateCamera(
"DTC", -Math.PI / 2, Math.PI / 2, 10, B.Vector3.Zero(), this.scene
);
this.selfCamera.mode = B.Camera.ORTHOGRAPHIC_CAMERA;
this.selfCamera.orthoBottom = this.selfCamera.orthoLeft = -1
this.selfCamera.orthoTop = this.selfCamera.orthoRight = 1;
//Box
const directBox = DirectBoxCreator.create(this.scene, 1, 0.3);
this.directBox = directBox.mesh;
//因为屏幕小导致edge过于细
this.directBox.edgesWidth *= 2;
this.directBox.material.zOffset *= 5;
......
}
......
}
初始化过程中有一个重点关注的点,就是将副场景的autoClear设置为false,避免渲染副场景的时候将原场景的像素信息从Buffer里面清除掉。接下来是摄像机的同步和窗口的初始化,前者将副场景的摄像机与主场景的摄像机根据旋转方向进行同步,后者将设置副窗口在整个页面的位置,这里我以屏幕右上角为基准进行的计算:
class DirectBoxScene {
//窗口大小 单位pixel
windowSize: number = 200;
//窗口右上角离window右上角的距离
windowOffset: BABYLON.Vector2 = new BABYLON.Vector2(50, 50);
......
constructor(engine: BABYLON.Engine, bindCamera: BABYLON.ArcRotateCamera) {
......
this.alignCameraToBindCamera();
this.resizeWindow();
......
}
......
//将摄像机与绑定摄像机同步
alignCameraToBindCamera() {
this.selfCamera.alpha = this.bindCamera.alpha;
this.selfCamera.beta = this.bindCamera.beta;
if (this.bindCamera.radius * this.selfCamera.radius < 0) {
this.selfCamera.radius *= -1;
}
}
//重置场景区域
resizeWindow() {
const engine = this.engine;
const width = engine.getRenderWidth(true);
const height = engine.getRenderHeight(true);
this.selfCamera.viewport.x = 1 - (this.windowSize + this.windowOffset.x) / width;
this.selfCamera.viewport.y = 1 - (this.windowSize + this.windowOffset.y) / height;
this.selfCamera.viewport.width = this.windowSize / width;
this.selfCamera.viewport.height = this.windowSize / height;
}
}
这一步需要注意的是,ArcRotateCamera如果滚动滚轮使得radius为负,旋转的方向会发生反转,所以需要对正负进行同步,当然也可以直接设置radius的上下界,禁止radius为负值,另外一点就是viewport是以屏幕左下角为原点,它的x,y,width,height的范围均为0-1。最后是进行事件的绑定,一般建议写事件绑定的时候对应着写一个dispose函数,确保每一个事件都在销毁前注销掉,避免组件被销毁后,事件依旧触发导致报错崩溃。
class DirectBoxScene {
......
//绑定摄像机旋转Observer
_onCameraViewMatrixChangedObserver: BABYLON.Nullable<BABYLON.Observer<BABYLON.Camera>> = null;
//场景尺寸改变Observer
_onEngineResizeObserver: BABYLON.Nullable<BABYLON.Observer<BABYLON.Engine>> = null;
//绑定场景渲染Observer
_onSceneRenderObserver: BABYLON.Nullable<BABYLON.Observer<BABYLON.Scene>> = null;
constructor(engine: BABYLON.Engine, bindCamera: BABYLON.ArcRotateCamera) {
......
//绑定摄像机矩阵改变事件
this._onCameraViewMatrixChangedObserver = this.bindCamera.onViewMatrixChangedObservable.add((camera) => {
this.alignCameraToBindCamera();
})
//绑定窗口尺寸改变事件
this._onEngineResizeObserver = this.engine.onResizeObservable.add((engine) => {
this.resizeWindow();
})
const bindScene = bindCamera.getScene();
//场景渲染事件
this._onSceneRenderObserver = bindScene.onAfterRenderObservable.add((scene) => {
this.scene.render();
})
......
}
......
dispose() {
this.bindCamera.onViewMatrixChangedObservable.remove(this._onCameraViewMatrixChangedObserver);
this.engine.onResizeObservable.remove(this._onEngineResizeObserver);
this.bindCamera.getScene().onAfterRenderObservable.remove(this._onSceneRenderObserver);
this.scene.dispose();
}
}
至此导航盒场景已经可以显现出来,之后将为导航盒添加新的功能。
PS:本节结束PG(https://playground.babylonjs.com/?#ENABP9#10)