Hey!
- When creating the engine (use WebGPU is available) ask for a high-performance device. Drains more battery power but who cares if it’s a game?!
new WebGPUEngine(canvas, {
powerPreference: "high-performance"
})
or
new Engine(canvas, true, {
powerPreference: "high-performance",
});
WebGPU:
GPU/requestAdapter
WebGL:
WebGLRenderingContext/getContextAttributes
If this fails, fall back to powerPreference: "default"
or just omit it.
- Use instances of even better go with thin instances wherever possible
- WebGPU - Use snapshot rendering
- Use
mesh.freezeWorldMatrix()
on your static meshes - Use
mesh.doNotSyncBoundingInfo = true
on your static meshes - Freeze your static materials with
material.freeze()
- Remove faces from your meshes which will never become visible (for example back side of the buildings on a street)
- Join the meshes in your 3D software or merge with Mesh.mergeMeshes. Be sure to dispose the old meshes and allow 32 bit indices if needed otherwise the meshes might fail to merge. Check the console for warnings. See post #9 by @Joe_Kerr below.
- If you use shadows and your
IShadowLight
is not moving render the shadows only once:
const shadowGenerator = new ShadowGenerator(4096, shadowLight);
const shadowMap = shadowGenerator.getShadowMap();
if (shadowMap) {
shadowMap.refreshRate = RenderTargetTexture.REFRESHRATE_RENDER_ONCE;
}
- In case of dynamic shadows use a smaller RT texture and set the shadows quality to low:
shadowGenerator.filteringQuality = ShadowGenerator.QUALITY_LOW;
. Try to reduce the number of shadow casters and receivers as possible. - Don’t use the SSAO2 pipeline, bake your AOs
- Try to avoid rendering pipelines unless necessary
- Agressively memoize everything you can to free up CPU cycles - What the heck is memoization?
- Use
scene.autoClear = false
when your meshes always covers the whole viewport - Use
scene.autoClearDepthAndStencil = false
if you don’t use the depth buffer - Use
scene.skipPointerMovePicking = true
if you don’t need hover interactivity - Stick to the left handed system
- Set
scene.blockMaterialDirtyMechanism = true
if your materials doesn’t need to be synced - Do not use alpha blended materials
- Use LODs. You can precreate your owns or use the LOD features of the framework to create your LOD meshes. For additional info see post #22 by @Joe_Kerr
You can try out the auto-LOD feature as well. - Use low poly meshes ( quite obvious isn’t it?
) or you can try
mesh.simplify
- …smaller textures - 4k → 2k → 1k → smaller - make them smaller until they starts to look shi*tty
- Don’t overuse reflections
- Complex GUIs could be faster in plain HTML overlayed over the render canvas. Stick to one HTML layer. If you use React, you can end up with tens or even more layers, which need to be individually composited by the browser’s rendering engine, hurting performance especially on mobile devices
- Limit
camera.maxZ
- Use compressed textures (KTX2 / Basis / ASTC) - you can use this online tool called GLB Batch Optimizer by @labris
- Load you assets asynchronously for better startup times (
LoadAssetContainerAsync
or your proprietary solution) See Asset Manager - Remove all
console.log
s in production - Use a bundler with treeshaking to reduce bundle sizes and thus loading times (don’t include the inspector in your prod build)
- Try to play with
engine.setHardwareScalingLevel()
- explanation here - Bake your animations
- Use SolidParticleSystem aka SPS - see post #14 by @Pryme8
- If you use particles prefer GPU particles
- Use Imposters - see post #22 by @Joe_Kerr
This a list of optimization approaches I thought of so far. I’ll update it as new ideas come to mind.
Try them out one by one and see what works, not every optimization fits every situation.
Happy coding!