コードは芸術になり得ます。巧妙な構文、エレガントなデータ構造、洗練されたインタラクションのいずれであっても、プログラマーだけが認識できる美しさがあります。それはそれで問題ありません。
しかし、コードは視覚的に素晴らしいもの、誰もが認めるものを作成することもできます。ここで、Three.js のようなツールが威力を発揮します。ただし、Three.js は、特にさまざまな計算能力を持つデバイスからアクセスされる動的な Web ページで使用する場合、重くなる可能性があります。
あなたが私と同じように、サイトに複数の Three.js シーンを追加する場合 (didof.dev で行うように)、最適化が必要になります。ここでは、パフォーマンスを維持するための 3 つの実践的なテクニックを紹介します。
元々は私のブログに投稿されました。
シーンが表示されていない場合は、シーンをロードしないでください。これは、重いグラフィック コンポーネントに当てはまります。これに最適なツールは 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>
シーンが表示されない場合は、レンダリングを停止します。ほとんどのチュートリアルは 1 つの全画面シーンに焦点を当てていますが、複数のシーンがあるサイトの場合は、非表示のシーンを一時停止するとリソースが節約されます。
これは、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);
一般的な を使用する場合は、コンポーネントの場合、これを実装する必要があるのは 1 回だけです。
このアプローチは完璧ではありません。特定のページに対するスムーズなクライアント側ルーティングが無効になります。しかし、メモリを抑制し、クラッシュを防ぎます。私にとって、そのトレードオフにはそれだけの価値があります。
これらの最適化は私にとってはうまくいきましたが、疑問は残ります。最新のフレームワークで Three.js オブジェクトを適切にクリーンアップするにはどうすればよいでしょうか?信頼できる解決策を見つけた場合は、ぜひご連絡ください!
以上がThree.js の最適化: テクニックの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。