一、问题
babylonjs场景,需要动态加载glb,但是发现glb会出现mesh面闪烁问题。
经过研究后发现:babylonjs 先import glb Mesh再添加灯光、相机,那glb不会闪。反之先创建灯光、相机 后 import glb Mesh ,那么glb就会很闪烁。
经过与大佬们沟通发现,是因为每次创建Camera时,相机会自动计算maxZ/minZ,所以会避免闪烁。但如何先加载glb,再创建mesh时,就会闪烁,因为Camera的计算精度不对。
解决方法:
- 每次加载glb后,都自动计算scene.activeCamera 的maxZ/minZ 。具体参考最下面的Babylonjs源代码;
- 手动设置,针对大场景minZ建议在5~30之间,maxZ >10万。
二、测试记录



var delayCreateScene = function () {
var scene = new BABYLON.Scene(engine);
scene.createDefaultCameraOrLight(true, true, true);
scene.createDefaultEnvironment();
scene.activeCamera.alpha = 1.610775814317132;
scene.activeCamera.beta = 1.52000034522929;
scene.activeCamera.radius = 10;
scene.activeCamera.wheelPrecision = 30;
BABYLON.SceneLoader.ImportMesh("", "https://www.baidu.cn/iios-minio/attachment/64/libraries/iios-insight/%E4%B8%89%E7%BB%B4%E6%A8%A1%E5%9E%8B/", "0826133216.glb", scene, function (meshes) {
scene.activeCamera.maxZ=100000;
scene.activeCamera.minZ=100;
console.log("minZ:", scene.activeCamera.minZ, "maxZ:", scene.activeCamera.maxZ);
});
return scene;
};
三、babylonjs自动计算maxz逻辑
源代码位置:
packages\dev\core\src\scene.ts 5766行
packages\dev\core\src\Helpers\sceneHelpers.ts 124行
/**
* 获取场景中所有网格的世界范围(包围盒的最小点和最大点)
*
* @param filterPredicate 可选的过滤函数 —— 用来决定哪些网格会被计算在世界范围中
* @returns {{ min: Vector3; max: Vector3 }} 返回计算得到的最小点和最大点(世界坐标系下)
*/
public getWorldExtends(filterPredicate?: (mesh: AbstractMesh) => boolean): { min: Vector3; max: Vector3 } {
// 初始化最小值向量,先设为无穷大(保证任何实际值都比它小)
const min = new Vector3(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
// 初始化最大值向量,先设为负无穷大(保证任何实际值都比它大)
const max = new Vector3(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
// 如果没有传入过滤函数,就默认全部网格都参与计算
filterPredicate = filterPredicate || (() => true);
// 获取通过过滤条件的网格
const meshes = this.meshes.filter(filterPredicate);
// 遍历每个网格
for (const mesh of meshes) {
// 计算网格的世界矩阵,保证后续包围盒是最新的
mesh.computeWorldMatrix(true);
// 如果网格没有子网格,或者是无限远(不会计算包围盒),则跳过
if (!mesh.subMeshes || mesh.subMeshes.length === 0 || mesh.infiniteDistance) {
continue;
}
// 获取网格的包围信息(含包围盒和包围球)
const boundingInfo = mesh.getBoundingInfo();
// 获取包围盒的世界最小点和最大点
const minBox = boundingInfo.boundingBox.minimumWorld;
const maxBox = boundingInfo.boundingBox.maximumWorld;
// 更新全局的最小点和最大点(检查并扩展)
Vector3.CheckExtends(minBox, min, max);
Vector3.CheckExtends(maxBox, min, max);
}
// 返回计算好的整体世界范围
return {
min: min,
max: max,
};
}
Scene.prototype.createDefaultCamera = function (
createArcRotateCamera = false, // 是否创建 ArcRotateCamera(默认是 FreeCamera)
replace = false, // 是否替换已有相机
attachCameraControls = false // 是否自动绑定相机控制(鼠标/触摸控制)
): void {
// 如果 replace = true,则先销毁现有的活动相机
if (replace) {
if (this.activeCamera) {
this.activeCamera.dispose(); // 释放现有相机资源
this.activeCamera = null; // 清空引用
}
}
// 如果当前没有激活相机,则创建一个默认相机
if (!this.activeCamera) {
// 获取场景中可见并启用的网格的整体包围盒(最小点和最大点)
const worldExtends = this.getWorldExtends((mesh) => mesh.isVisible && mesh.isEnabled());
// 计算整个场景的范围大小(max - min 向量)
const worldSize = worldExtends.max.subtract(worldExtends.min);
// 计算场景的中心点(min + size的一半)
const worldCenter = worldExtends.min.add(worldSize.scale(0.5));
let camera: TargetCamera;
// 计算默认相机的半径(取整个场景对角线长度的 1.5 倍)
let radius = worldSize.length() * 1.5;
// 如果场景为空(没有网格),则 radius = 1,并设置世界中心为 (0,0,0)
if (!isFinite(radius)) {
radius = 1;
worldCenter.copyFromFloats(0, 0, 0);
}
// 判断相机类型:ArcRotateCamera 或 FreeCamera
if (createArcRotateCamera) {
// 创建弧形旋转相机(可围绕目标旋转)
const arcRotateCamera = new ArcRotateCamera(
"default camera",
-(Math.PI / 2), // 初始水平角度
Math.PI / 2, // 初始垂直角度
radius, // 相机到目标的距离
worldCenter, // 相机的观察目标(场景中心)
this // 当前场景
);
// 设置相机的最小缩放限制(避免无限靠近)
arcRotateCamera.lowerRadiusLimit = radius * 0.01;
// 设置鼠标滚轮缩放灵敏度(场景越大,滚轮越敏感)
arcRotateCamera.wheelPrecision = 100 / radius;
camera = arcRotateCamera;
} else {
// 创建自由相机(可以在空间中自由移动)
const freeCamera = new FreeCamera(
"default camera",
new Vector3(worldCenter.x, worldCenter.y, -radius), // 初始位置:Z 轴负方向,远离中心
this
);
// 让相机朝向场景中心
freeCamera.setTarget(worldCenter);
camera = freeCamera;
}
// 设置相机的近裁剪面和远裁剪面
camera.minZ = radius * 0.01; // 最近可见距离
camera.maxZ = radius * 1000; // 最远可见距离
// 设置相机移动速度(与场景大小成比例)
camera.speed = radius * 0.2;
// 将创建的相机设置为场景的活动相机
this.activeCamera = camera;
// 如果需要,自动绑定相机控制(支持鼠标/键盘/触摸操作)
if (attachCameraControls) {
camera.attachControl();
}
}
};