Babylon.js 多灯场景在 Windows 上报错:VERTEX shader uniform block count exceeds GL_MAX_VERTEX_UNIFORM_BUFFERS
适用:Babylon.js 7.x / 8.x · WebGL2 · PBR 材质 · glTF 点光/射灯\
报错长什么样
控制台或 shader 编译日志中常见:
text
BJS - [xx:xx:xx]: Error: VERTEX shader uniform block count exceeds GL_MAX_VERTEX_UNIFORM_BUFFERS (12)
有时伴随 PBR 顶点/片元 shader 编译失败,日志里能看到 uniform Material { ... } 以及多个 Light 相关的 uniform block。
典型特征:
- 同一页面在 macOS 正常,在 Windows 失败;
- 场景里 glTF 带了 大量 KHR_lights_punctual 点光;
- 或代码里把
material.maxSimultaneousLights设得很大(例如 16、32)。

这条报错在说什么
WebGL2 对 顶点着色器 能绑定的 Uniform Buffer Object(UBO) 数量有上限。
查询参数名为 GL_MAX_VERTEX_UNIFORM_BUFFERS;规范 最小值是 12,不少 Windows 集显/核显正好卡在这个下限,而 macOS 或部分独显可能是 16、24 等。
这不是「JavaScript 里 scene.lights.length 太多」那么简单,而是:编译出的 shader 里声明了多少个 uniform block,是否超过 GPU 上限。

Babylon.js PBR 为什么会超
在 WebGL2 下,标准 forward PBR 路径大致为每个 mesh 编译一份带 N 个「灯光槽」的 shader。近似关系:
text
顶点着色器 UBO 数量 ≈ 2 + maxSimultaneousLights
| UBO | 含义 |
|---|---|
| 1 | Scene(相机、矩阵等) |
| 1 | Material(材质参数) |
| N | 每个同时生效的灯光槽各占 1 个 |
举例:
maxSimultaneousLights = 4→ 约 6 个 UBO,Windows(上限 12)通常安全;maxSimultaneousLights = 32→ 约 34 个 UBO,在上限 12 的机器上必失败。
把 maxSimultaneousLights 调大,本意往往是「让一层里很多盏灯都参与 PBR」,但在 Windows 上会直接触发 shader 编译失败,而不是单纯变慢。

为什么压低上限后又「灯不全亮」
若只为消除报错,把 maxSimultaneousLights 改成 8:
- 场景里仍可以有很多
Light对象; - 但 每个 mesh 的 shader 一次最多处理 N 盏 (按距离等规则从
lightSources里选); - 超出槽位的灯即使
setEnabled(true),也 不会参与该 mesh 的光照计算。
因此会出现两种极端:
| 做法 | 结果 |
|---|---|
| 上限设很大(如 32) | Windows shader 编译失败 |
| 上限设很小(如 4~8) | 能跑,但同屏只有部分灯真正照亮模型 |
若业务要求 保留每一盏灯、且可单独开关,不能只靠调一个数字解决。

无效方案:combineUniformBuffers 等
网上偶尔能看到:
javascript
engine.combineUniformBuffers = true;
scene.useCompactMaterialUniformBuffers = true;
在 Babylon.js 官方 Engine / Scene API 中不存在上述属性 (至少 7.x、8.x 如此)。
它们无法合并 UBO,也绕不过 GL_MAX_VERTEX_UNIFORM_BUFFERS 的硬件/驱动限制。不要依赖这类写法。

可行方案
方案 1:ClusteredLightContainer(多灯首选)
Babylon 8 提供 聚类灯光容器 :把大量 点光 / 射灯 收进一个 ClusteredLightContainer,在 shader 里只占 1 个灯光槽,内部用 Clustered / Forward+ 在 GPU 上批量算光。
思路:
text
几十盏 PointLight → 1 个 ClusteredLightContainer(1 槽)
+ 少量 DirectionalLight / 半球光 → 各若干槽
→ maxSimultaneousLights 保持 4~8 即可在 Windows 编译通过

示例(概念代码):
typescript
import { ClusteredLightContainer, Light, Scene } from "@babylonjs/core";
function clusterPointLights(scene: Scene, lights: Light[]) {
const container = new ClusteredLightContainer("clusteredLights", [], scene);
if (!container.isSupported) {
console.warn("当前环境不支持聚类灯光");
return null;
}
for (const light of lights) {
light.shadowEnabled = false;
if (light.falloffType !== Light.FALLOFF_DEFAULT) {
light.falloffType = Light.FALLOFF_DEFAULT;
}
if (ClusteredLightContainer.IsLightSupported(light)) {
container.addLight(light); // 从 scene.lights 移入容器
}
}
return container;
}
优点
- 不删灯,灯对象仍在容器内,可逐个
setEnabled或调intensity; - 从根上避免「一盏灯一个 UBO」。
限制
- 仅 点光 / 射灯 ;平行光、半球光仍单独放在
scene.lights; - 聚类内 不支持阴影(阴影继续用平行光等常规方案);
- 需要 WebGL2,且 GPU 支持聚类所需能力(如浮点混合等);
- glTF 常见的
FALLOFF_GLTF需改为FALLOFF_DEFAULT才能入簇。
开关注意
聚类更新路径对 isEnabled 的处理与普通 forward 灯不完全一致。实践中常见做法:关灯时将 intensity 置 0 并备份原值,开灯时恢复,以保证逐盏开关可见。
方案 2:按 mesh 限制 lightSources
Babylon 允许为 mesh 指定 lightSources。若模型按 房间/区域 拆成多个 mesh,可为每个 mesh 只绑定本区域最多 8 盏灯:
typescript
roomMesh.lightSources = lightsInThisRoom.slice(0, 8);
material.maxSimultaneousLights = 8;
全层总灯数可以很多,但 单个 mesh 的 shader 槽位不超限 。
需要模型或前端有区域划分,不要求删灯。
方案 3:视觉与照明分离(双轨)
- 视觉 :灯体 mesh 用
emissive(或 UI 标注)表示开/关; - 照明:保留少量实时点光,或配合聚类容器。
适合「每一盏都要在界面上看见状态,但不必每一盏都做完整 PBR 贡献」的场景。
方案 4:减少实时灯、用烘焙(改内容管线)
在 Blender 等 DCC 中把静态照明 烘焙进 lightmap / AO / emissive ,glTF 里只保留需要交互的少量灯。
这是内容侧方案,不是引擎参数能替代的;若产品要求 每盏都可动态开关且不能删灯,需与方案 1~3 组合。
方案 5:WebGL1 降级(兜底)
创建引擎时关闭 WebGL2:
typescript
new Engine(canvas, true, { disableWebGL2Support: true });
WebGL1 不使用 UBO 传灯,上限通常更高,但 性能更差,仅作老设备兜底。
如何确认是不是这个问题
在浏览器控制台(WebGL2 上下文):
javascript
const gl = canvas.getContext("webgl2");
console.log(gl.getParameter(gl.MAX_VERTEX_UNIFORM_BUFFERS));
查看材质:
javascript
console.log(pbrMaterial.maxSimultaneousLights);
查看灯数量与类型:
javascript
console.log(scene.lights.map(l => [l.name, l.getClassName(), l.isEnabled()]));
若 maxSimultaneousLights 很大且点光很多,基本可认定是 UBO 槽位问题。
选型建议
| 需求 | 建议 |
|---|---|
| 很多点光、要逐盏开关、Windows 必过 | ClusteredLightContainer |
| 大空间、按房间分 mesh | lightSources 分区 + 合理 maxSimultaneousLights |
| 控制态比物理照明更重要 | emissive / UI 标注 + 少量实时灯 |
| 纯展示、少交互 | 烘焙 + 少量动态灯 |
| 仅消除报错、可接受部分灯不算光 | 降低 maxSimultaneousLights(权宜) |
核心结论 :WebGL2 限制的是 shader 灯光槽(UBO) ,不是场景里 Light 的个数;要在 Windows 上既 不报错 又 保留多盏动态灯 ,应优先 聚类灯光 ,而不是虚构的「合并 UBO」配置或无限增大 maxSimultaneousLights。