
HexaZen - EP09:WebGPU 與效能優化
最後一章,來聊聊那些讓 HexaZen 跑得更順暢的技術細節。
從渲染引擎的選擇到鏡頭限制,這些看似微小的優化加在一起,就是「流暢」和「卡頓」的差別。੭ ˙ᗜ˙ )੭
WebGPU 優先,WebGL 降級
Babylon.js 支援 WebGPU,效能比 WebGL 好不少。我們優先使用 WebGPU,不支援的裝置自動降級。
content\aquarium\hexazen\composables\use-babylon-scene.ts
async createEngine({ canvas }) {
const webGPUSupported = await WebGPUEngine.IsSupportedAsync
if (webGPUSupported) {
const engine = new WebGPUEngine(canvas, {
antialias: true,
stencil: true,
})
await engine.initAsync()
return engine
}
return new Engine(canvas, true, {
antialias: true,
alpha: false,
stencil: true,
})
},WebGPUEngine.IsSupportedAsync 是非同步檢測,因為瀏覽器需要時間確認 GPU 支援狀態。
場景初始化
useBabylonScene composable 封裝了完整的場景生命週期:
export function useBabylonScene(param?: UseBabylonSceneParam) {
const canvasRef = ref<HTMLCanvasElement>()
const engine = shallowRef<BabylonEngine>()
const scene = shallowRef<Scene>()
const camera = shallowRef<ArcRotateCamera>()
onMounted(async () => {
if (!canvasRef.value)
return
engine.value = await createEngine({
canvas: canvasRef.value,
})
// 適配高 DPI 螢幕
engine.value.setHardwareScalingLevel(1 / (window?.devicePixelRatio ?? 1))
scene.value = createScene({
canvas: canvasRef.value,
engine: engine.value,
})
camera.value = createCamera({
canvas: canvasRef.value,
engine: engine.value,
scene: scene.value,
})
window.addEventListener('resize', handleResize)
engine.value.runRenderLoop(() => {
scene.value?.render()
})
await init({ canvas, engine, scene, camera })
setupSmartCameraLimits(engine, camera, scene)
})
onBeforeUnmount(() => {
engine.value?.dispose()
scene.value?.dispose()
window.removeEventListener('resize', handleResize)
})
return { canvasRef, engine, scene, camera }
}setHardwareScalingLevel 根據 devicePixelRatio 調整渲染解析度,Retina 螢幕上會更清晰。
預設光照與霧氣
createScene({ engine }) {
const scene = new Scene(engine)
scene.createDefaultLight()
const defaultLight = scene.lights.at(-1)
if (defaultLight instanceof HemisphericLight) {
defaultLight.diffuse = new Color3(1.0, 0.98, 0.95)
defaultLight.direction = new Vector3(0.5, 1, 0)
defaultLight.groundColor = new Color3(0.64, 0.56, 0.78)
}
scene.clearColor = new Color4(0.97, 0.97, 0.96, 1)
scene.fogMode = Scene.FOGMODE_LINEAR
scene.fogColor = new Color3(0.55, 0.6, 0.65)
scene.fogStart = 10
scene.fogEnd = 100
return scene
},暖色光源搭配偏紫的地面反射光,讓積木看起來有柔和的手繪感。
智慧鏡頭限制
鏡頭需要根據裝置螢幕比例自動調整:
function setupSmartCameraLimits(
engine: BabylonEngine,
camera: ArcRotateCamera,
scene: Scene,
) {
const width = engine.getRenderWidth()
const height = engine.getRenderHeight()
// 直式螢幕使用垂直 FOV,橫式使用水平 FOV
if (width < height) {
camera.fovMode = Camera.FOVMODE_VERTICAL_FIXED
}
else {
camera.fovMode = Camera.FOVMODE_HORIZONTAL_FIXED
}
const finalRadius = 4
const safeDistance = finalRadius / Math.sin(camera.fov / 2)
camera.lowerRadiusLimit = safeDistance * 0.2
camera.upperRadiusLimit = safeDistance * 1
camera.minZ = finalRadius * 0.1
}手機直式螢幕和電腦橫式螢幕的 FOV 模式不同,這樣積木在任何裝置上都能完整顯示在畫面中。
鏡頭約束
createCamera({ scene, canvas }) {
const camera = new ArcRotateCamera(
'camera', 0, Math.PI / 3 * 2, 5,
new Vector3(0, 0, 0), scene,
)
camera.attachControl(canvas, true)
// 禁止平移,只能旋轉和縮放
camera.panningSensibility = 0
camera.wheelDeltaPercentage = 0.01
// 限制仰角,永遠從上方俯瞰
camera.lowerBetaLimit = Math.PI / 3
camera.upperBetaLimit = Math.PI / 3
return camera
},禁止平移(panningSensibility = 0)和固定仰角(beta 鎖定在 60°),讓使用者只能水平旋轉和縮放,保持最佳觀賞角度。
模型材質優化
載入 GLB 模型時,強制使用三線性過濾避免紋理閃爍:
model.meshes.forEach((mesh) => {
if (mesh.material instanceof PBRMaterial) {
mesh.material.metallic = 0
mesh.material.roughness = 0.4
const texture = mesh.material.albedoTexture
if (texture instanceof Texture) {
texture.updateSamplingMode(Texture.TRILINEAR_SAMPLINGMODE)
}
}
})TRILINEAR_SAMPLINGMODE 讓紋理邊緣被柔化,旋轉時不會有像素閃爍。
一開始看到的時候真的是快被閃瞎眼。(◞‸◟ )
陰影生成
function createShadowGenerator(scene: Scene) {
const light = new DirectionalLight('dir01', new Vector3(-3, -5, -2), scene)
light.intensity = 0.8
const shadowGenerator = new ShadowGenerator(2048, light)
shadowGenerator.bias = 0.000001
shadowGenerator.normalBias = 0.0001
shadowGenerator.forceBackFacesOnly = true
return shadowGenerator
}2048×2048 的陰影貼圖確保陰影邊緣銳利。forceBackFacesOnly = true 防止自身陰影產生的瑕疵(shadow acne)。
整體架構回顧
經過 9 篇文章,HexaZen 的架構如下:
hexazen/
├── hexazen.vue # 應用根元件(狀態管理、UI 佈局)
├── main-scene.vue # 3D 場景(互動、粒子、後製)
├── composables/
│ ├── use-babylon-scene # 引擎初始化與生命週期
│ ├── use-thumbnail-gen # 多執行緒縮圖生成
│ ├── use-simple-i18n # 輕量國際化
│ └── use-font-loader # 字體動態載入
└── domains/
├── block/ # 積木定義、建立、縮圖
├── hex-grid/ # 六角格數學
├── soundscape/ # 聲景規則、播放引擎、混音器
└── share/codec/ # URL 編碼/解碼總結 🐟
以上程式碼可以在此取得
- WebGPU 優先、WebGL 自動降級
- 高 DPI 螢幕適配與智慧鏡頭限制
- 三線性過濾消除紋理閃爍
- 2048×2048 高品質陰影
感謝看到這裡的你!希望這系列文章能給你一些靈感。ヾ(◍'౪`◍)ノ゙
如果你也想體驗看看,歡迎到 HexaZen 玩玩看,打造屬於你自己的音景吧!
TIP
因為篇幅問題,某些部分簡單帶過。
若想看到更詳細的解釋,請不吝留言或寫信給我喔!(*´∀`)~♥