mirror of
https://github.com/galacean/engine.git
synced 2026-06-01 00:05:56 +08:00
从 PR https://github.com/galacean/engine/pull/2957 (feat/gpu-instancing) cherry-pick 69 个 commit 到 fix/shaderlab 分支,压缩为单次提交。 ## 核心改动 ### 新增文件 - `InstanceBuffer.ts` — UBO-based 实例数据管理,将多个 renderer 的 worldMatrix/shaderData 打包到一个 UBO 中 - `VertexMergeBatcher.ts` — 替代旧 BatchUtils,统一 2D/3D 合批入口 - `ShaderBlockProperty.ts` — UBO block 属性描述 - `ShaderProgramMap.ts` — 替代旧 ShaderProgramPool,支持 instancing layout 缓存 - `ConstantBufferBindingPoint.ts` — UBO binding point 枚举 ### 修改文件 - `MeshRenderer._canBatch/_batch` — 同 mesh+material+macros 自动合批判定 - `SkinnedMeshRenderer` — 标记不可 GPU instance - `RenderQueue` — 按 material/primitive 排序(替代按距离排序), 渲染时检测 instanced batch 并通过 InstanceBuffer 一次 draw - `RenderElement` — 扁平化(移除 SubRenderElement),支持 instancedRenderers 列表 - `ShaderFactory` — UBO 布局计算、instancing GLSL 注入(RENDERER_GPU_INSTANCE macro) - `ShaderPass` — 编译时检测 GPU instance macro,注入 UBO 声明 - `ShaderProgram` — 存储 instanceLayout - `Renderer` — 移除 batched 相关字段 ### 删除文件 - `BatchUtils.ts` → 替换为 `VertexMergeBatcher.ts` - `SubRenderElement.ts` → 合并到 `RenderElement.ts` - `ShaderProgramPool.ts` → 替换为 `ShaderProgramMap.ts` ## cherry-pick 冲突解决记录 fix/shaderlab 分支和 PR 基线 (dev/2.0) 的差异主要在以下文件: 1. **ShaderPass.ts** — fix/shaderlab 使用 `Shader._shaderLab._parseMacros` 处理 ShaderLab 宏,而 PR 使用 `ShaderMacroProcessor.evaluate`(fix/shaderlab 上不存在)。解决方式:保留 fix/shaderlab 的宏处理,加入 PR 的 instancing UBO 注入逻辑。 2. **Transform.glsl** — fix/shaderlab 已有 `camera_VPMat` 声明,PR 也添加了。 解决方式:合并两边声明。 3. **UIRenderer.ts** — PR 将 `BatchUtils` 重命名为 `VertexMergeBatcher`, `batchFor2D` 重命名为 `batch`。fix/shaderlab 的 UI 包未同步。 解决方式:手动更新 UI 包的 import 和调用。 4. **GLSLIfdefResolver.ts** — PR 早期 commit 新增此文件,后续 commit 删除。 cherry-pick 后 ShaderPass.ts 残留了 import。解决方式:删除无用 import。 ## 验证结果 CarParking 游戏 DrawCall 从 905 降至 ~80(同 mesh+material 的座椅、轮子等 自动合批)。
101 lines
2.7 KiB
TypeScript
101 lines
2.7 KiB
TypeScript
/**
|
|
* @title GPU Instancing Custom Data
|
|
* @category Mesh
|
|
*/
|
|
import {
|
|
Camera,
|
|
Color,
|
|
DirectLight,
|
|
Logger,
|
|
Material,
|
|
MeshRenderer,
|
|
PrimitiveMesh,
|
|
Shader,
|
|
ShaderProperty,
|
|
Vector3,
|
|
Vector4,
|
|
WebGLEngine
|
|
} from "@galacean/engine";
|
|
import { initScreenshot, updateForE2E } from "./.mockForE2E";
|
|
|
|
Logger.enable();
|
|
|
|
// Custom shader: uses renderer_CustomColor (per-instance) for fragment output
|
|
Shader.create(
|
|
"CustomInstanceShader",
|
|
`
|
|
#include <transform_declare>
|
|
attribute vec3 POSITION;
|
|
attribute vec3 NORMAL;
|
|
|
|
varying vec3 v_normal;
|
|
|
|
void main() {
|
|
gl_Position = renderer_MVPMat * vec4(POSITION, 1.0);
|
|
v_normal = normalize((renderer_NormalMat * vec4(NORMAL, 0.0)).xyz);
|
|
}
|
|
`,
|
|
`
|
|
uniform vec4 renderer_CustomColor;
|
|
|
|
varying vec3 v_normal;
|
|
|
|
void main() {
|
|
vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
|
|
float NdotL = max(dot(v_normal, lightDir), 0.2);
|
|
gl_FragColor = vec4(renderer_CustomColor.rgb * NdotL, 1.0);
|
|
}
|
|
`
|
|
);
|
|
|
|
WebGLEngine.create({ canvas: "canvas" }).then((engine) => {
|
|
engine.canvas.resizeByClientSize(2);
|
|
|
|
const scene = engine.sceneManager.activeScene;
|
|
const rootEntity = scene.createRootEntity("Root");
|
|
|
|
// Camera
|
|
const cameraEntity = rootEntity.createChild("Camera");
|
|
cameraEntity.transform.setPosition(0, 10, 80);
|
|
cameraEntity.transform.lookAt(new Vector3(0, 0, 0));
|
|
const camera = cameraEntity.addComponent(Camera);
|
|
camera.farClipPlane = 300;
|
|
|
|
// Light
|
|
const lightEntity = rootEntity.createChild("Light");
|
|
lightEntity.transform.setRotation(-45, -45, 0);
|
|
lightEntity.addComponent(DirectLight).color = new Color(1, 1, 1, 1);
|
|
|
|
// Shared mesh and material
|
|
const mesh = PrimitiveMesh.createCuboid(engine, 1, 1, 1);
|
|
const material = new Material(engine, Shader.find("CustomInstanceShader"));
|
|
const customColorProperty = ShaderProperty.getByName("renderer_CustomColor");
|
|
|
|
// Create cubes with deterministic positions and colors
|
|
const count = 500;
|
|
const spread = 50;
|
|
for (let i = 0; i < count; i++) {
|
|
const entity = rootEntity.createChild("Cube" + i);
|
|
const t = i / count;
|
|
entity.transform.setPosition(
|
|
Math.sin(t * 137.5) * spread * 0.5,
|
|
Math.cos(t * 97.3) * spread * 0.5,
|
|
Math.sin(t * 59.1) * spread * 0.5
|
|
);
|
|
entity.transform.setRotation(t * 360, t * 720, t * 1080);
|
|
|
|
const renderer = entity.addComponent(MeshRenderer);
|
|
renderer.mesh = mesh;
|
|
renderer.setMaterial(material);
|
|
|
|
// Deterministic colors based on index
|
|
renderer.shaderData.setVector4(
|
|
customColorProperty,
|
|
new Vector4(Math.sin(t * 6.28) * 0.5 + 0.5, Math.cos(t * 4.71) * 0.5 + 0.5, Math.sin(t * 3.14) * 0.5 + 0.5, 1.0)
|
|
);
|
|
}
|
|
|
|
updateForE2E(engine);
|
|
initScreenshot(engine, camera);
|
|
});
|