程式碼可以是藝術。無論是巧妙的語法、優雅的資料結構還是精緻的交互,都有隻有程式設計師才能看到的美——這很好。
但是程式碼也可以創造出視覺上令人驚嘆的東西,每個人都能欣賞的東西。這就是 Three.js 等工具的閃光點。然而,Three.js 可能很重,尤其是在由具有不同運算能力的裝置存取的動態網頁中使用時。
如果您像我一樣,在您的網站上添加多個 Three.js 場景(就像我在 didof.dev 上所做的那樣),您將需要最佳化。以下是控制性能的三種實用技巧。
最初發佈在我的部落格上。
如果場景不可見,請勿載入。這適用於任何重型圖形組件。最好的工具是 IntersectionObserver,它可以偵測元素何時進入視窗。以下是我在 SvelteKit 中的處理方式:
<script lang="ts"> import { browser } from '$app/environment'; import { onMount } from 'svelte'; let ref: HTMLDivElement; let download = $state(false); if (browser) onMount(() => { const observer = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) { download = true; // we need this once only observer.disconnect(); } }); // ref has been bound by Svelte since we are in onMount observer.observe(ref); return () => observer.disconnect(); }); </script> <div bind:this={ref}> {#if download} <!-- let SvelteKit handle the code splitting --> {#await import('./three-scene.svelte')} Loading {:then module} <module.default /> {:catch error} <div>{error}</div> {/await} {/if} </div>
如果場景不可見,請停止渲染它。大多數教程側重於單一全螢幕場景,但對於具有多個場景的網站,暫停隱藏場景可以節省資源。
這是使用 IntersectionObserver 控制場景動畫循環的片段:
function tick() { const elapsedTime = clock.getElapsedTime(); // Update your scene (e.g. set uniforms, move/rotate geometries...) renderer.render(scene, camera); } // Start the rendering renderer.setAnimationLoop(tick);
我們的朋友 IntersectionObserver 再次為我們提供幫助。
let clock: THREE.Clock; let renderer: THREE.WebGLRenderer; if (browser) onMount(() => { const observer = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) { clock.start(); renderer.setAnimationLoop(tick); // resume } else { clock.stop(); renderer.setAnimationLoop(null); // pause } }); observer.observe(canvas); // Scene setup... return () => { observer.disconnect(); // Other cleanup... }; });
螢幕較小的裝置通常功能較弱。相應地調整著色器的計算工作量。例如,根據視窗寬度減少分形著色器中使用的八度音階數:
從瀏覽器...
<script lang="ts"> import ThreeScene from "./three-scene.svelte"; import { browser } from '$app/environment'; const octaves = browser ? (window.innerWidth <= 680 ? 2 : 4) : 1 </script> <ThreeScene {octaves} />
...透過 Three.js...
const material = new THREE.ShaderMaterial({ vertexShader, fragmentShader, uniforms: { uOctaves: new Three.Uniform(octaves) // coming as $prop } });
...最後,在著色器中。
uniform float uOctaves; for(float i = 0.0; i <= uOctaves; i++) { elevation += simplexNoise2d(warpedPosition * uPositionFrequency * pow(2.0, i)) / pow(2.0, i + 1.0); }
這種方法動態平衡性能和視覺品質。
這就是事情變得棘手的地方。 Three.js 不會自動清理記憶體,您需要手動追蹤和處置幾何、紋理和材質等物件。如果您跳過此步驟,每次您導航離開和返回時,記憶體使用量都會增加,最終導致瀏覽器崩潰。
讓我分享一下我在主頁上觀察到的情況:
初始記憶體使用量:22.4MB
軟導航到另一個頁面後:28.6MB(即使該頁面是靜態 HTML)。
重複來回導航後:記憶體使用量不斷攀升,直到瀏覽器崩潰。
為什麼?因為 Three.js 物件沒有被正確處理。儘管進行了廣泛的研究,我還是找不到在現代框架中完全清理記憶體的可靠方法。
這是我找到的最簡單的解決方案:在離開帶有 Three.js 場景的頁面時強制硬重新加載。硬重載讓瀏覽器:
在 SvelteKit 中,透過 data-sveltekit-reload 可以輕鬆實現這一點。只需為有場景的頁面啟用它即可:
首頁的 server.page.ts
<script lang="ts"> import { browser } from '$app/environment'; import { onMount } from 'svelte'; let ref: HTMLDivElement; let download = $state(false); if (browser) onMount(() => { const observer = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) { download = true; // we need this once only observer.disconnect(); } }); // ref has been bound by Svelte since we are in onMount observer.observe(ref); return () => observer.disconnect(); }); </script> <div bind:this={ref}> {#if download} <!-- let SvelteKit handle the code splitting --> {#await import('./three-scene.svelte')} Loading {:then module} <module.default /> {:catch error} <div>{error}</div> {/await} {/if} </div>
對於導航鏈接,動態傳遞此值:
function tick() { const elapsedTime = clock.getElapsedTime(); // Update your scene (e.g. set uniforms, move/rotate geometries...) renderer.render(scene, camera); } // Start the rendering renderer.setAnimationLoop(tick);
如果您使用通用的;元件,您只需要實作一次。
這種方法並不完美——它會禁用特定頁面的平滑客戶端路由——但是它可以檢查記憶體並防止崩潰。對我來說,這種權衡是值得的。
這些最佳化對我來說效果很好,但問題仍然存在:我們如何在現代框架中正確清理 Three.js 物件?如果您找到了可靠的解決方案,我很樂意聽取您的意見!
以上是優化 Three.js:ey 技術的詳細內容。更多資訊請關注PHP中文網其他相關文章!