Appearance
模型加载卡顿,分片加载实现
javascript
loader.load('factory.glb', gltf => {
const mesh = gltf.scene.children[0]
const geometry = mesh.geometry
const totalFaces = geometry.index.count / 3
let loadedFaces = 0
const loadChunk = () => {
const end = Math.min(loadedFaces + chunkSize, totalFaces)
// 创建分片BufferGeometry
const subGeometry = new THREE.BufferGeometry()
subGeometry.setIndex(geometry.index.slice(loadedFaces * 3, end * 3))
subGeometry.setAttribute('position', geometry.attributes.position)
scene.add(new THREE.Mesh(subGeometry, material))
loadedFaces = end
if (loadedFaces < totalFaces) {
requestAnimationFrame(loadChunk) // 下一帧继续 } };
loadChunk()
}
}
})
材质切换闪烁,共享材质池方案
javascript
// 创建材质池(避免重复创建)
const materialPool = {
metalRed: new THREE.MeshPhysicalMaterial({
color: 0xff0000,
metalness: 0.8
}),
plasticBlue: new THREE.MeshStandardMaterial({
color: 0x0000ff,
roughness: 0.5
}), // ...其他预定义材质
}
// 动态切换(汽车配置器案例)
function changePartMaterial(partName, materialKey) {
const part = scene.getObjectByName(partName)
if (part && materialPool[materialKey]) {
part.material = materialPool[materialKey] // 直接引用共享材质
part.material.needsUpdate = true
}
}
移动端发热,精准降级策略
javascript
// 设备能力检测
const isLowEndDevice = navigator.hardwareConcurrency < 4 || navigator.deviceMemory < 2
// 动态调整配置
if (isLowEndDevice) {
renderer.setPixelRatio(1) // 分辨率降级
scene.background = null // 移除环境贴图
postprocessingPasses = [] // 关闭后处理
textureLoader.setQuality(0.5) // 纹理压缩
}
点击事件不准,射线检测优化
javascript
// 解决小物体难点击问题
const raycaster = new THREE.Raycaster()
raycaster.params = {
Line: {
threshold: 0.1
}, // 线宽阈值
Points: {
threshold: 12
}, // 点选半径
Mesh: {} // 网格保持默认
}
// 添加点击区域辅助
function addHitBox(mesh, size = 1.2) {
const box = new THREE.BoxHelper(mesh, 0xff0000)
box.visible = false // 调试时可开启
box.geometry.computeBoundingSphere()
mesh.userData.hitBox = box // 存储引用
scene.add(box)
}
// 检测时使用包围盒
raycaster.intersectObject(mesh.userData.hitBox)
内存泄漏,资源销毁清单
javascript
// 场景切换时必须执行
function disposeScene() {
scene.traverse(obj => {
if (obj.isMesh) {
obj.geometry.dispose() // 几何体
if (obj.material) {
if (Array.isArray(obj.material)) {
obj.material.forEach(m => m.dispose())
} else {
obj.material.dispose() // 材质
}
}
}
if (obj.texture) obj.texture.dispose() // 纹理
})
renderer.dispose() // 渲染器
renderer.forceContextLoss() // 强制释放WebGL上下文
}
性能分析三板斧
1、内存快照对比
Chrome DevTools → Memory → Heap snapshot
2、关键指标:Detached DOM trees / GPU memory帧耗时分析
javascript
renderer.info.reset() // 每帧开始重置
renderer.render(scene, camera)
console.log(renderer.info.render) // 输出绘制耗时
3、GPU负载监控
javascript
const ext = renderer.extensions.get('EXT_disjoint_timer_query_webgl2')
const query = ext.createQuery();ext.beginQuery(ext.TIME_ELAPSED_EXT, query)
// ...渲染代码
ext.endQuery(ext.TIME_ELAPSED_EXT)
避坑指南
1. 纹理压缩:iOS必须用PVR格式,Android用ASTC
javascript
const texture = new THREE.CompressedTextureLoader().setPath('textures/').load('wall.astc.ktx') // 体积减少70%
2. 动画卡顿:
javascript
// 错误 ❌
function animate() {
cube.rotation.x += 0.01;
requestAnimationFrame(animate);
}
// 正确 ✅
function animate(timestamp) {
const delta = clock.getDelta(); // 使用时间差
cube.rotation.x += delta * speed;
}
3. 内存黑洞:
javascript
// 慎用!会复制整个几何体
const newGeo = geometry.clone()
// 改用引用 + 矩阵变换
mesh.matrixAutoUpdate = false
mesh.matrix.multiply(transformationMatrix)
babylon.js写法就是用createInstance
javascript
// 假设已有 scene 和原始 mesh
const original = BABYLON.MeshBuilder.CreateBox('box', {
size: 1
}, scene)
// 禁用自动计算世界矩阵(等价于 matrixAutoUpdate = false)
original.computeWorldMatrix(false)
// 创建一个共享几何体的新实例(不复制顶点数据)
const instance = original.createInstance('instance1')
// 构造一个变换矩阵(例如平移 + 旋转)
const transform = BABYLON.Matrix.Compose(
new BABYLON.Vector3(1, 1, 1), // 缩放
BABYLON.Quaternion.RotationAxis(BABYLON.Vector3.Up(), Math.PI / 4), // 旋转
new BABYLON.Vector3(2, 0, 0) // 平移
)
// 手动设置世界矩阵(避免 clone 几何体)
instance.setPreTransformMatrix(transform)
以下用instancedBuffers来区分颜色,但是不能用各自的shader,因为是共享
javascript
// 1. 创建场景、相机、灯光(略)
const scene = new BABYLON.Scene(engine)
const camera = new BABYLON.ArcRotateCamera("cam", 0, 0, 10, BABYLON.Vector3.Zero(), scene)
camera.attachControl(canvas, true)
new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene)
// 2. 创建“母版”网格(original)——这里用一个 1×1×1 的方块
const original = BABYLON.MeshBuilder.CreateBox('masterBox', {
size: 1
}, scene)
// 3. 启用顶点色(如果你后面想给每个实例独立的颜色)
original.hasVertexAlpha = true
// 4. 创建材质(简单 PBR 即可,也可以用 StandardMaterial)
const mat = new BABYLON.StandardMaterial('mat', scene)
mat.useVertexColors = true // 启用顶点色
original.material = mat
// 5. 注册实例化颜色缓冲
original.registerInstancedBuffer('color', 4) // 4 个分量:r,g,b,a
// 4. 给原始网格设置颜色(默认颜色)
original.instancedBuffers.color = new BABYLON.Color4(1, 0, 1, 1) // 白色
// 6. 开始生成实例
const instance1 = original.createInstance('i1')
const instance2 = original.createInstance('i2')
console.log(instance1)
// 7. 给 instance1 实例单独设置颜色
instance1.instancedBuffers.color = new BABYLON.Color4(1, 0, 0, 1) // 红
// 8. 位置区分一下,方便观察
instance1.position.x = -1.2
instance2.position.x = 1.2