首頁 >web前端 >js教程 >優化 Three.js:ey 技術

優化 Three.js:ey 技術

Linda Hamilton
Linda Hamilton原創
2024-12-03 00:32:12571瀏覽

程式碼可以是藝術。無論是巧妙的語法、優雅的資料結構還是精緻的交互,都有隻有程式設計師才能看到的美——這很好。

但是程式碼也可以創造出視覺上令人驚嘆的東西,每個人都能欣賞的東西。這就是 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

Optimizing Three.js: ey Techniques

軟導航到另一個頁面後:28.6MB(即使該頁面是靜態 HTML)。

Optimizing Three.js: ey Techniques

重複來回導航後:記憶體使用量不斷攀升,直到瀏覽器崩潰。

Optimizing Three.js: ey Techniques

為什麼?因為 Three.js 物件沒有被正確處理。儘管進行了廣泛的研究,我還是找不到在現代框架中完全清理記憶體的可靠方法。

這是我找到的最簡單的解決方案:在離開帶有 Three.js 場景的頁面時強制硬重新加載。硬重載讓瀏覽器:

  1. 建立一個新的頁面上下文。
  2. 在舊頁面上執行垃圾收集(將清理工作留給瀏覽器)。

在 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中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn