diff --git a/examples/_shared/setup.js b/examples/_shared/setup.js new file mode 100644 index 0000000..c98a96b --- /dev/null +++ b/examples/_shared/setup.js @@ -0,0 +1,31 @@ +// examples/_shared/setup.js +import * as THREE from "three/webgpu"; +import { OrbitControls } from "three/addons/controls/OrbitControls.js"; + +export function setup({ fov = 60 } = {}) { + const canvas = document.getElementById("canvas"); + const renderer = new THREE.WebGPURenderer({ canvas, antialias: true }); + renderer.setPixelRatio(devicePixelRatio); + renderer.setSize(innerWidth, innerHeight); + + const scene = new THREE.Scene(); + const camera = new THREE.PerspectiveCamera(fov, innerWidth / innerHeight, 0.1, 1000); + const controls = new OrbitControls(camera, renderer.domElement); + + function resize() { + camera.aspect = innerWidth / innerHeight; + camera.updateProjectionMatrix(); + renderer.setSize(innerWidth, innerHeight); + } + addEventListener("resize", resize); + + function loop(fn) { + renderer.setAnimationLoop((t) => { + controls.update(); + fn?.(t / 1000, renderer, scene, camera); + renderer.render(scene, camera); + }); + } + + return { THREE, renderer, scene, camera, controls, loop }; +} diff --git a/examples/anisotropic-fbm-streaks/main.js b/examples/anisotropic-fbm-streaks/main.js index 83f55d8..fe7a2eb 100644 --- a/examples/anisotropic-fbm-streaks/main.js +++ b/examples/anisotropic-fbm-streaks/main.js @@ -1,4 +1,4 @@ -import { OrbitControls } from "three/addons/controls/OrbitControls.js"; +import { setup } from "../_shared/setup.js"; import { Inspector } from "three/addons/inspector/Inspector.js"; import { Fn, @@ -13,28 +13,13 @@ import { vec3, vec4, } from "three/tsl"; -import * as THREE from "three/webgpu"; import { simplexNoise3 } from "../../src/index.js"; // Scene setup -const scene = new THREE.Scene(); -const camera = new THREE.PerspectiveCamera( - 60, - window.innerWidth / window.innerHeight, - 0.1, - 1000 -); +const { THREE, renderer, scene, camera, loop } = setup({ fov: 60 }); camera.position.set(0, 0, 2.2); - -const renderer = new THREE.WebGPURenderer({ - canvas: document.getElementById("canvas"), - antialias: true, -}); -renderer.setSize(window.innerWidth, window.innerHeight); -renderer.setPixelRatio(window.devicePixelRatio); renderer.setClearColor(0x000000); renderer.inspector = new Inspector(); -new OrbitControls(camera, renderer.domElement); // Controls - define uniforms outside the shader function so Inspector can access them const scaleX = uniform(0.25); @@ -123,13 +108,4 @@ gui.add(fbmLacunarity, "value", 0.1, 4.0, 0.1).name("fbmLacunarity"); gui.add(fbmGain, "value", 0.25, 1.0, 0.01).name("fbmGain"); gui.add(animate, "value", 0, 1, 1).name("animate"); -function onResize() { - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - renderer.setSize(window.innerWidth, window.innerHeight); -} -window.addEventListener("resize", onResize); - -renderer.setAnimationLoop(() => { - renderer.render(scene, camera); -}); +loop(); diff --git a/examples/cinematic-gallery/main.js b/examples/cinematic-gallery/main.js index f98c9c2..5fbf0b5 100644 --- a/examples/cinematic-gallery/main.js +++ b/examples/cinematic-gallery/main.js @@ -1,5 +1,5 @@ +import { setup } from "../_shared/setup.js"; import headURL from "@assets/head256x256x109.zip?url"; -import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { unzipSync } from "three/addons/libs/fflate.module.js"; import { texture3D, uniform, pass, color, screenUV } from "three/tsl"; import { knotMorphPosition } from "../../src/knotMorph.js"; @@ -7,7 +7,6 @@ import { buildSphericalWaveCopyKernel, averageIntensityProjection, } from "../../src/index.js"; -import * as THREE from "three/webgpu"; // Choose cinematic aspect based on viewport orientation: 16:9 (landscape) or 9:16 (portrait) function getCinematicAspect(w, h) { @@ -16,14 +15,11 @@ function getCinematicAspect(w, h) { let childAspect = getCinematicAspect(window.innerWidth, window.innerHeight); // Parent (main) scene and camera -const parentScene = new THREE.Scene(); -const parentCamera = new THREE.PerspectiveCamera( - 60, - window.innerWidth / window.innerHeight, - 0.1, - 1000 -); +const { THREE, renderer, camera: parentCamera, controls } = setup({ fov: 60 }); parentCamera.position.set(0, 1.25, 2.5); +controls.update(); + +const parentScene = new THREE.Scene(); // Child A (offscreen) scene and camera - Raymarch Head const childScene = new THREE.Scene(); @@ -37,15 +33,7 @@ const childCamera2 = new THREE.PerspectiveCamera(60, childAspect, 0.05, 1000); childCamera2.position.set(0, 0, 5); childCamera2.updateProjectionMatrix(); -// Renderer -const renderer = new THREE.WebGPURenderer({ - canvas: document.getElementById("canvas"), -}); -renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0x000000); - -// Parent controls (user controls) -const controls = new OrbitControls(parentCamera, renderer.domElement); controls.target.set(0, 1.0, 0); controls.update(); @@ -469,11 +457,6 @@ new THREE.FileLoader() childCamera2.lookAt(0, 0, 0); } - // Resize handling for renderer and parent camera, then update layout - window.addEventListener("resize", () => { - renderer.setSize(window.innerWidth, window.innerHeight); - parentCamera.aspect = window.innerWidth / window.innerHeight; - parentCamera.updateProjectionMatrix(); - updateLayout(); - }); + // Additional resize handling for layout updates + window.addEventListener("resize", updateLayout); }); diff --git a/examples/hello-world/main.js b/examples/hello-world/main.js index 9c25f8c..ec12ad0 100644 --- a/examples/hello-world/main.js +++ b/examples/hello-world/main.js @@ -1,24 +1,9 @@ // examples/main.js -import * as THREE from "three/webgpu"; +import { setup } from "../_shared/setup.js"; import { float } from "three/tsl"; -import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { exampleTSLFunction } from "../../src/index.js"; // Import locally from src/ for dev -// Set up scene, camera, renderer... -const scene = new THREE.Scene(); -const camera = new THREE.PerspectiveCamera( - 75, - window.innerWidth / window.innerHeight, - 0.1, - 1000 -); -const renderer = new THREE.WebGPURenderer({ - canvas: document.getElementById("canvas"), -}); -renderer.setSize(window.innerWidth, window.innerHeight); -document.body.appendChild(renderer.domElement); - -new OrbitControls(camera, renderer.domElement); +const { THREE, scene, camera, loop } = setup({ fov: 75 }); // Use your TSL function in a material (uniforms will get auto-GUI) const material = new THREE.MeshBasicNodeMaterial(); // Or whatever TSL material @@ -27,9 +12,5 @@ material.colorNode = exampleTSLFunction(float(1.0)); const mesh = new THREE.Mesh(new THREE.BoxGeometry(), material); scene.add(mesh); -// Animate/render loop -async function animate() { - requestAnimationFrame(animate); - await renderer.renderAsync(scene, camera); -} -animate(); +// Start animation loop +loop(); diff --git a/examples/knot-morph/main.js b/examples/knot-morph/main.js index 760f0bb..5f5e3a6 100644 --- a/examples/knot-morph/main.js +++ b/examples/knot-morph/main.js @@ -1,26 +1,12 @@ // examples/knot-morph/main.js -import * as THREE from "three/webgpu"; +import { setup } from "../_shared/setup.js"; import { color } from "three/tsl"; -import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { knotMorphPosition } from "../../src/knotMorph.js"; // Import from dedicated module // Set up scene, camera, renderer -const scene = new THREE.Scene(); -const camera = new THREE.PerspectiveCamera( - 75, - window.innerWidth / window.innerHeight, - 0.1, - 1000 -); +const { THREE, renderer, scene, camera, loop } = setup({ fov: 75 }); camera.position.z = 5; // Adjusted for better view of knot scale -const canvas = document.getElementById("canvas"); -const renderer = new THREE.WebGPURenderer({ canvas, antialias: true }); -renderer.setSize(window.innerWidth, window.innerHeight); - -// Controls for interaction -new OrbitControls(camera, renderer.domElement); - // Create starting geometry (p=2, q=3 for trefoil knot) const startGeo = new THREE.TorusKnotGeometry(1, 0.4, 128, 32, 2, 3); @@ -45,21 +31,17 @@ material.colorNode = color(0x00ff00); const mesh = new THREE.Mesh(startGeo, material); scene.add(mesh); -// Animation loop -let time = 0; -function animate() { - requestAnimationFrame(animate); - time += 0.01; - +// Start animation loop +loop((t) => { // Ping-pong mix factor for looping morph (0 to 1 and back) - const mixFactor = Math.abs(Math.sin(time * 0.5)); + const mixFactor = Math.abs(Math.sin(t * 0.5)); material.positionNode = knotMorphPosition({ mixFactor }); // Dynamic rotation (ported from original) const quaternion = new THREE.Quaternion(); - const slowTime = time * 0.3; - const fastTime = time * 1.2; + const slowTime = t * 0.3; + const fastTime = t * 1.2; // Y-axis rotation const ySpeedMultiplier = Math.sin(slowTime) * Math.sin(slowTime * 0.7); @@ -92,18 +74,4 @@ function animate() { // Combine and apply rotations quaternion.multiply(yQuat).multiply(xQuat).multiply(zQuat); mesh.quaternion.multiply(quaternion); - - renderer.render(scene, camera); -} - -// Initialize renderer and start animation -renderer.init().then(() => { - animate(); -}); - -// Handle resize -window.addEventListener("resize", () => { - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - renderer.setSize(window.innerWidth, window.innerHeight); }); diff --git a/examples/portal-door-transition/main.js b/examples/portal-door-transition/main.js index a2f3920..05599d2 100644 --- a/examples/portal-door-transition/main.js +++ b/examples/portal-door-transition/main.js @@ -1,11 +1,17 @@ -import * as THREE from "three/webgpu"; +import { setup } from "../_shared/setup.js"; import { pass, uniform, mix, Fn } from "three/tsl"; -import { OrbitControls } from "three/addons/controls/OrbitControls.js"; // ============================================================================ // Scene Setup // ============================================================================ +const { THREE, renderer, camera: mainCamera, controls } = setup({ fov: 60 }); +mainCamera.position.set(0, 1.5, 5); +controls.target.set(0, 1, 0); +controls.enableDamping = true; +controls.dampingFactor = 0.05; +controls.update(); + // Main gallery scene const mainScene = new THREE.Scene(); mainScene.background = new THREE.Color(0x222222); @@ -18,17 +24,9 @@ const portalSceneB = new THREE.Scene(); portalSceneB.background = new THREE.Color(0x001133); // Cool // ============================================================================ -// Cameras +// Additional Cameras for Portals // ============================================================================ -const mainCamera = new THREE.PerspectiveCamera( - 60, - window.innerWidth / window.innerHeight, - 0.1, - 100 -); -mainCamera.position.set(0, 1.5, 5); - const portalCameraA = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, @@ -46,33 +44,16 @@ const portalCameraB = new THREE.PerspectiveCamera( portalCameraB.position.set(0, 2, 5); // ============================================================================ -// Renderer +// Additional Controls for Portals // ============================================================================ -const renderer = new THREE.WebGPURenderer({ - canvas: document.getElementById("canvas"), - antialias: true, -}); -renderer.setSize(window.innerWidth, window.innerHeight); -renderer.setPixelRatio(window.devicePixelRatio); - -// ============================================================================ -// Controls -// ============================================================================ - -const controls = new OrbitControls(mainCamera, renderer.domElement); -controls.target.set(0, 1, 0); -controls.enableDamping = true; -controls.dampingFactor = 0.05; -controls.update(); - -const controlsA = new OrbitControls(portalCameraA, renderer.domElement); +const controlsA = new THREE.OrbitControls(portalCameraA, renderer.domElement); controlsA.target.set(0, 0, 0); controlsA.enableDamping = true; controlsA.dampingFactor = 0.05; controlsA.enabled = false; // Start disabled -const controlsB = new OrbitControls(portalCameraB, renderer.domElement); +const controlsB = new THREE.OrbitControls(portalCameraB, renderer.domElement); controlsB.target.set(0, 0, 0); controlsB.enableDamping = true; controlsB.dampingFactor = 0.05; @@ -468,22 +449,13 @@ renderer.setAnimationLoop(() => { }); // ============================================================================ -// Window Resize Handler +// Additional Resize Handler for Portal Cameras // ============================================================================ window.addEventListener("resize", () => { - const width = window.innerWidth; - const height = window.innerHeight; - const aspect = width / height; - - mainCamera.aspect = aspect; - mainCamera.updateProjectionMatrix(); - + const aspect = window.innerWidth / window.innerHeight; portalCameraA.aspect = aspect; portalCameraA.updateProjectionMatrix(); - portalCameraB.aspect = aspect; portalCameraB.updateProjectionMatrix(); - - renderer.setSize(width, height); }); diff --git a/examples/projector-dual/main.js b/examples/projector-dual/main.js index 04dadad..4820a69 100644 --- a/examples/projector-dual/main.js +++ b/examples/projector-dual/main.js @@ -1,9 +1,9 @@ +import { setup } from "../_shared/setup.js"; import atlasURL from "@videos/atlas-demo-3x.mp4"; import buildxURL from "@videos/buildx-demo-5x.mp4"; import diffuseURL from "@textures/plastered_stone_wall_diff_4k.jpg?url"; import normalURL from "@textures/plastered_stone_wall_nor_gl_4k.exr?url"; import roughnessURL from "@textures/plastered_stone_wall_rough_4k.exr?url"; -import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { TransformControls } from "three/addons/controls/TransformControls.js"; import { Inspector } from "three/addons/inspector/Inspector.js"; import { EXRLoader } from "three/addons/loaders/EXRLoader.js"; @@ -27,35 +27,21 @@ import { vec3, vec4, } from "three/tsl"; -import * as THREE from "three/webgpu"; // Phase 1: No passes/compute/morphs // --- Renderer / Scene / Camera --- -const canvas = document.getElementById("canvas"); -const renderer = new THREE.WebGPURenderer({ canvas, antialias: true }); -renderer.setSize(window.innerWidth, window.innerHeight); -renderer.setPixelRatio(window.devicePixelRatio); +const { THREE, renderer, scene, camera, controls } = setup({ fov: 50 }); +camera.position.set(6, 3, 10); +controls.target.set(0, 2, 0); +controls.update(); + renderer.toneMapping = THREE.NeutralToneMapping; renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; renderer.inspector = new Inspector(); - -const scene = new THREE.Scene(); scene.background = new THREE.Color(0x0); -const camera = new THREE.PerspectiveCamera( - 50, - window.innerWidth / window.innerHeight, - 0.1, - 100 -); -camera.position.set(6, 3, 10); - -const controls = new OrbitControls(camera, renderer.domElement); -controls.target.set(0, 2, 0); -controls.update(); - // --- Ground + Wall --- const floorMat = new THREE.MeshStandardNodeMaterial({ color: 0x111111 }); const floor = new THREE.Mesh(new THREE.PlaneGeometry(40, 40), floorMat); @@ -544,13 +530,6 @@ volFolder.add(densityScale, "value", 0, 2, 0.01).name("density scale"); volFolder.add(threshold, "value", 0, 1, 0.01).name("threshold"); volFolder.add(range, "value", 0, 1, 0.01).name("range"); -// --- Resize --- -window.addEventListener("resize", () => { - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - renderer.setSize(window.innerWidth, window.innerHeight); -}); - // --- Initialize renderer and start animation --- async function startAnimation() { await renderer.init(); diff --git a/examples/raymarch-comparison/main.js b/examples/raymarch-comparison/main.js index e5e3921..3f2c5fa 100644 --- a/examples/raymarch-comparison/main.js +++ b/examples/raymarch-comparison/main.js @@ -1,24 +1,12 @@ -import { OrbitControls } from "three/addons/controls/OrbitControls.js"; +import { setup } from "../_shared/setup.js"; import { Inspector } from "three/addons/inspector/Inspector.js"; import { RaymarchingBox } from "three/addons/tsl/utils/Raymarching.js"; import { select, float, Fn, vec3, If, vec2, uniform } from "three/tsl"; -import * as THREE from "three/webgpu"; import { adaptiveRaymarch } from "../../src/raymarch.js"; -const scene = new THREE.Scene(); -const camera = new THREE.PerspectiveCamera( - 75, - window.innerWidth / window.innerHeight, - 0.1, - 1000 -); +const { THREE, renderer, scene, camera } = setup({ fov: 75 }); camera.position.z = 3; -const renderer = new THREE.WebGPURenderer({ - canvas: document.getElementById("canvas"), -}); -renderer.setSize(window.innerWidth, window.innerHeight); renderer.inspector = new Inspector(); -new OrbitControls(camera, renderer.domElement); // Torus SDF (thin ring shape to show precision differences) const torusSDF = Fn(({ pos }) => { @@ -79,8 +67,7 @@ const fixedFolder = gui.addFolder("Fixed (Right - Green)"); fixedFolder.add(fixedMaxSteps, "value", 1, 100, 1).name("maxSteps"); fixedFolder.add(fixedThreshold, "value", 0.001, 0.1, 0.001).name("threshold"); -async function animate() { - requestAnimationFrame(animate); +// Start animation loop (use async renderAsync instead of sync render) +renderer.setAnimationLoop(async () => { await renderer.renderAsync(scene, camera); -} -animate(); +}); diff --git a/examples/raymarch-head-compute-texture/main.js b/examples/raymarch-head-compute-texture/main.js index 7b0e68a..0ca84dd 100644 --- a/examples/raymarch-head-compute-texture/main.js +++ b/examples/raymarch-head-compute-texture/main.js @@ -1,5 +1,5 @@ +import { setup } from "../_shared/setup.js"; import headURL from "@assets/head256x256x109.zip?url"; -import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { Inspector } from "three/addons/inspector/Inspector.js"; import { unzipSync } from "three/addons/libs/fflate.module.js"; import { RaymarchingBox } from "three/addons/tsl/utils/Raymarching.js"; @@ -13,23 +13,11 @@ import { instanceIndex, textureStore, } from "three/tsl"; -import * as THREE from "three/webgpu"; - -const scene = new THREE.Scene(); -const camera = new THREE.PerspectiveCamera( - 75, - window.innerWidth / window.innerHeight, - 0.1, - 1000 -); + +const { THREE, renderer, scene, camera, loop } = setup({ fov: 75 }); camera.position.z = 3; -const renderer = new THREE.WebGPURenderer({ - canvas: document.getElementById("canvas"), -}); -renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0x000000); renderer.inspector = new Inspector(); -new OrbitControls(camera, renderer.domElement); new THREE.FileLoader() .setResponseType("arraybuffer") .load(headURL, async function (data) { @@ -133,8 +121,6 @@ new THREE.FileLoader() gui.add(steps, "value", 1, 150, 1).name("steps"); gui.add(intensityScale, "value", 0.1, 5.0, 0.1).name("intensityScale"); - function animate() { - renderer.render(scene, camera); - } - renderer.setAnimationLoop(animate); + // Start animation loop + loop(); }); diff --git a/examples/raymarch-head-wave-displacement/main.js b/examples/raymarch-head-wave-displacement/main.js index 431e41d..55659be 100644 --- a/examples/raymarch-head-wave-displacement/main.js +++ b/examples/raymarch-head-wave-displacement/main.js @@ -1,5 +1,5 @@ +import { setup } from "../_shared/setup.js"; import headURL from "@assets/head256x256x109.zip?url"; -import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { Inspector } from "three/addons/inspector/Inspector.js"; import { unzipSync } from "three/addons/libs/fflate.module.js"; import { texture3D, uniform } from "three/tsl"; @@ -7,23 +7,11 @@ import { buildSphericalWaveCopyKernel, averageIntensityProjection, } from "../../src/index.js"; -import * as THREE from "three/webgpu"; - -const scene = new THREE.Scene(); -const camera = new THREE.PerspectiveCamera( - 75, - window.innerWidth / window.innerHeight, - 0.1, - 1000 -); + +const { THREE, renderer, scene, camera } = setup({ fov: 75 }); camera.position.z = 1; -const renderer = new THREE.WebGPURenderer({ - canvas: document.getElementById("canvas"), -}); -renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0x000000); renderer.inspector = new Inspector(); -new OrbitControls(camera, renderer.domElement); new THREE.FileLoader() .setResponseType("arraybuffer") diff --git a/examples/raymarch-head/main.js b/examples/raymarch-head/main.js index 14fa86d..65bd48b 100644 --- a/examples/raymarch-head/main.js +++ b/examples/raymarch-head/main.js @@ -1,26 +1,14 @@ +import { setup } from "../_shared/setup.js"; import headURL from "@assets/head256x256x109.zip?url"; -import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { Inspector } from "three/addons/inspector/Inspector.js"; import { unzipSync } from "three/addons/libs/fflate.module.js"; import { RaymarchingBox } from "three/addons/tsl/utils/Raymarching.js"; import { Fn, texture3D, uniform, vec3, vec4, float } from "three/tsl"; -import * as THREE from "three/webgpu"; -const scene = new THREE.Scene(); -const camera = new THREE.PerspectiveCamera( - 75, - window.innerWidth / window.innerHeight, - 0.1, - 1000 -); +const { THREE, renderer, scene, camera, loop } = setup({ fov: 75 }); camera.position.z = 3; -const renderer = new THREE.WebGPURenderer({ - canvas: document.getElementById("canvas"), -}); -renderer.setSize(window.innerWidth, window.innerHeight); renderer.setClearColor(0x000000); renderer.inspector = new Inspector(); -new OrbitControls(camera, renderer.domElement); new THREE.FileLoader() .setResponseType("arraybuffer") .load(headURL, function (data) { @@ -87,8 +75,6 @@ new THREE.FileLoader() gui.add(steps, "value", 1, 150, 1).name("steps"); gui.add(intensityScale, "value", 0.1, 5.0, 0.1).name("intensityScale"); - function animate() { - renderer.render(scene, camera); - } - renderer.setAnimationLoop(animate); + // Start animation loop + loop(); }); diff --git a/examples/rectarea-video-light/main.js b/examples/rectarea-video-light/main.js index 0c27d1e..014427c 100644 --- a/examples/rectarea-video-light/main.js +++ b/examples/rectarea-video-light/main.js @@ -1,24 +1,21 @@ -import * as THREE from "three/webgpu"; +import { setup } from "../_shared/setup.js"; import { texture, uniform, sin, time, uv, vec3, Fn } from "three/tsl"; -import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { RectAreaLightTexturesLib } from "three/addons/lights/RectAreaLightTexturesLib.js"; -let renderer, scene, camera, controls; let rectLight1, rectLight2; let videoTexture, canvas2d, ctx; init(); async function init() { - // Initialize WebGPU Renderer - renderer = new THREE.WebGPURenderer({ - antialias: true, - canvas: document.getElementById("canvas"), - }); - renderer.setPixelRatio(window.devicePixelRatio); - renderer.setSize(window.innerWidth, window.innerHeight); + const { THREE, renderer, scene, camera, controls } = setup({ fov: 60 }); + camera.position.set(0, 3, 8); + controls.target.set(0, 1, 0); + controls.update(); + renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.2; + scene.background = new THREE.Color(0x111111); // Initialize WebGPU backend await renderer.init(); @@ -26,24 +23,6 @@ async function init() { // Initialize RectAreaLight textures for WebGPU THREE.RectAreaLightNode.setLTC(RectAreaLightTexturesLib.init()); - // Scene setup - scene = new THREE.Scene(); - scene.background = new THREE.Color(0x111111); - - // Camera setup - camera = new THREE.PerspectiveCamera( - 60, - window.innerWidth / window.innerHeight, - 0.1, - 100 - ); - camera.position.set(0, 3, 8); - - // Controls - controls = new OrbitControls(camera, renderer.domElement); - controls.target.set(0, 1, 0); - controls.update(); - // Create a procedural video texture using canvas createProceduralVideoTexture(); @@ -151,11 +130,22 @@ async function init() { wall.position.set(0, 5, -5); scene.add(wall); - // Handle window resize - window.addEventListener("resize", onWindowResize); - // Start animation loop after renderer is initialized animate(); + + function animate() { + requestAnimationFrame(animate); + + // Update procedural texture + updateProceduralTexture(); + + // Rotate lights slightly + rectLight1.rotation.y = Math.sin(performance.now() * 0.0005) * 0.3; + rectLight2.rotation.y = Math.cos(performance.now() * 0.0003) * 0.3; + + controls.update(); + renderer.render(scene, camera); + } } function createProceduralVideoTexture() { @@ -207,23 +197,3 @@ function updateProceduralTexture() { ctx.putImageData(imageData, 0, 0); videoTexture.needsUpdate = true; } - -function onWindowResize() { - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - renderer.setSize(window.innerWidth, window.innerHeight); -} - -function animate() { - requestAnimationFrame(animate); - - // Update procedural texture - updateProceduralTexture(); - - // Rotate lights slightly - rectLight1.rotation.y = Math.sin(performance.now() * 0.0005) * 0.3; - rectLight2.rotation.y = Math.cos(performance.now() * 0.0003) * 0.3; - - controls.update(); - renderer.render(scene, camera); -} diff --git a/examples/volumetric-dust-configurator/main.js b/examples/volumetric-dust-configurator/main.js index d387149..45f6c84 100644 --- a/examples/volumetric-dust-configurator/main.js +++ b/examples/volumetric-dust-configurator/main.js @@ -1,4 +1,4 @@ -import * as THREE from "three/webgpu"; +import { setup } from "../_shared/setup.js"; import { Fn, vec3, @@ -16,7 +16,6 @@ import { shapeCircle, positionWorld, } from "three/tsl"; -import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { Inspector } from "three/addons/inspector/Inspector.js"; import { ImprovedNoise } from "three/addons/math/ImprovedNoise.js"; import { bayer16 } from "three/addons/tsl/math/Bayer.js"; @@ -25,36 +24,21 @@ import { gaussianBlur } from "three/addons/tsl/display/GaussianBlurNode.js"; // === Constants === const LAYER_VOLUMETRIC = 10; -// === Renderer Setup === -const canvas = document.getElementById("canvas"); -const renderer = new THREE.WebGPURenderer({ canvas, antialias: true }); -renderer.setSize(window.innerWidth, window.innerHeight); -renderer.setPixelRatio(window.devicePixelRatio); +// === Renderer / Scene / Camera Setup === +const { THREE, renderer, scene, camera, controls } = setup({ fov: 60 }); +camera.position.set(8, 6, 12); +controls.target.set(0, 3, 0); +controls.maxDistance = 40; +controls.minDistance = 2; +controls.update(); + renderer.toneMapping = THREE.NeutralToneMapping; renderer.toneMappingExposure = 1.2; renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; renderer.inspector = new Inspector(); - -// === Scene Setup === -const scene = new THREE.Scene(); scene.background = new THREE.Color(0x0a0a0a); -// === Camera Setup === -const camera = new THREE.PerspectiveCamera( - 60, - window.innerWidth / window.innerHeight, - 0.1, - 200 -); -camera.position.set(8, 6, 12); - -const controls = new OrbitControls(camera, renderer.domElement); -controls.target.set(0, 3, 0); -controls.maxDistance = 40; -controls.minDistance = 2; -controls.update(); - // === Room Environment === // Floor const floorGeometry = new THREE.PlaneGeometry(30, 30); @@ -596,7 +580,7 @@ sceneFolder.addColor(sceneParams, "backgroundColor").onChange((value) => { sceneFolder.add(sceneParams, "resetCamera").name("reset camera"); // === Animation Loop === -function animate() { +renderer.setAnimationLoop(() => { // Animate objects slightly torusKnot.rotation.y += 0.005; torusKnot.rotation.x += 0.002; @@ -610,13 +594,4 @@ function animate() { // Render postProcessing.render(); -} - -renderer.setAnimationLoop(animate); - -// === Window Resize === -window.addEventListener("resize", () => { - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - renderer.setSize(window.innerWidth, window.innerHeight); }); diff --git a/examples/wispy-projector-beams/main.js b/examples/wispy-projector-beams/main.js index 2ad7d53..54b8dfc 100644 --- a/examples/wispy-projector-beams/main.js +++ b/examples/wispy-projector-beams/main.js @@ -1,4 +1,4 @@ -import * as THREE from "three/webgpu"; +import { setup } from "../_shared/setup.js"; import { abs, negate, @@ -20,7 +20,6 @@ import { exp, float, } from "three/tsl"; -import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { Inspector } from "three/addons/inspector/Inspector.js"; import { ImprovedNoise } from "three/addons/math/ImprovedNoise.js"; import { bayer16 } from "three/addons/tsl/math/Bayer.js"; @@ -30,32 +29,19 @@ import { gaussianBlur } from "three/addons/tsl/display/GaussianBlurNode.js"; const LAYER_VOLUMETRIC = 10; // === Renderer / Scene / Camera Setup === -const canvas = document.getElementById("canvas"); -const renderer = new THREE.WebGPURenderer({ canvas, antialias: true }); -renderer.setSize(window.innerWidth, window.innerHeight); -renderer.setPixelRatio(window.devicePixelRatio); -renderer.toneMapping = THREE.NeutralToneMapping; -renderer.shadowMap.enabled = true; -renderer.shadowMap.type = THREE.PCFSoftShadowMap; -renderer.inspector = new Inspector(); - -const scene = new THREE.Scene(); -scene.background = new THREE.Color(0x0a0a0a); - -const camera = new THREE.PerspectiveCamera( - 60, - window.innerWidth / window.innerHeight, - 0.1, - 200 -); +const { THREE, renderer, scene, camera, controls } = setup({ fov: 60 }); camera.position.set(8, 6, 12); - -const controls = new OrbitControls(camera, renderer.domElement); controls.target.set(0, 3, 0); controls.maxDistance = 40; controls.minDistance = 2; controls.update(); +renderer.toneMapping = THREE.NeutralToneMapping; +renderer.shadowMap.enabled = true; +renderer.shadowMap.type = THREE.PCFSoftShadowMap; +renderer.inspector = new Inspector(); +scene.background = new THREE.Color(0x0a0a0a); + // === Environment Setup === const floorGeometry = new THREE.PlaneGeometry(30, 30); const floorMaterial = new THREE.MeshStandardNodeMaterial({ @@ -382,7 +368,7 @@ projectorFolder }); // === Animation Loop === -function animate() { +renderer.setAnimationLoop(() => { // Update projector uniforms proj.projector.getWorldPosition(projectorPosition.value); new THREE.Vector3(0, 0, -1) @@ -395,13 +381,4 @@ function animate() { // Render postProcessing.render(); -} - -renderer.setAnimationLoop(animate); - -// === Window Resize === -window.addEventListener("resize", () => { - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - renderer.setSize(window.innerWidth, window.innerHeight); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3eb219f..354aebf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,27 +12,12 @@ importers: specifier: github:mrdoob/three.js version: https://codeload.github.com/mrdoob/three.js/tar.gz/71c6a2b701c94ad62f1494b6dad2f2292999b847 devDependencies: - '@tweakpane/core': - specifier: ^2.0.5 - version: 2.0.5 - '@tweakpane/plugin-essentials': - specifier: ^0.2.1 - version: 0.2.1(tweakpane@4.0.5) '@types/three': specifier: ^0.180.0 version: 0.180.0 fast-glob: specifier: ^3.3.3 version: 3.3.3 - tsl-uniform-ui-vite-plugin: - specifier: bhushan6/tsl-Uniform-UI-Vite-Plugin#pull/1/head&path:package - version: https://codeload.github.com/bhushan6/tsl-Uniform-UI-Vite-Plugin/tar.gz/e10bb39f1a89591ffde93b361a7e18be7e16ac59#path:package - tweakpane: - specifier: ^4.0.5 - version: 4.0.5 - tweakpane-plugin-file-import: - specifier: ^1.1.1 - version: 1.1.1(tweakpane@4.0.5) vite: specifier: ^7.1.5 version: 7.1.5 @@ -42,18 +27,6 @@ importers: packages: - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -67,14 +40,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.28.4': - resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} - engines: {node: '>=6.9.0'} - '@babel/types@7.28.4': resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} @@ -246,19 +211,9 @@ packages: resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} engines: {node: 20 || >=22} - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@microsoft/api-extractor-model@7.30.7': resolution: {integrity: sha512-TBbmSI2/BHpfR9YhQA7nH0nqVmGgJ0xH0Ex4D99/qBDAUpnhA2oikGmdXanbw9AWWY/ExBYIpkmY8dBHdla3YQ==} @@ -420,14 +375,6 @@ packages: '@rushstack/ts-command-line@5.0.3': resolution: {integrity: sha512-bgPhQEqLVv/2hwKLYv/XvsTWNZ9B/+X1zJ7WgQE9rO5oiLzrOZvkIW4pk13yOQBhHyjcND5qMOa6p83t+Z66iQ==} - '@tweakpane/core@2.0.5': - resolution: {integrity: sha512-punBgD5rKCF5vcNo6BsSOXiDR/NSs9VM7SG65QSLJIxfRaGgj54ree9zQW6bO3pNFf3AogiGgaNODUVQRk9YqQ==} - - '@tweakpane/plugin-essentials@0.2.1': - resolution: {integrity: sha512-VbFU1/uD+CJNFQdfLXUOLjeG5HyUZH97Ox9CxmyVetg1hqjVun3C83HAGFULyhKzl8tSgii8jr304r8QpdHwzQ==} - peerDependencies: - tweakpane: ^4.0.0 - '@tweenjs/tween.js@23.1.3': resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} @@ -637,14 +584,6 @@ packages: jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} @@ -806,18 +745,6 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - tsl-uniform-ui-vite-plugin@https://codeload.github.com/bhushan6/tsl-Uniform-UI-Vite-Plugin/tar.gz/e10bb39f1a89591ffde93b361a7e18be7e16ac59#path:package: - resolution: {path: package, tarball: https://codeload.github.com/bhushan6/tsl-Uniform-UI-Vite-Plugin/tar.gz/e10bb39f1a89591ffde93b361a7e18be7e16ac59} - version: 0.7.2 - - tweakpane-plugin-file-import@1.1.1: - resolution: {integrity: sha512-7QbLToRTPYprzrZWJ1BWefIDJOyxj/9IHzVE/wZwK1+z1oUFtZ+ptzMxKm6apeZNoQTG7zS6opEaJi5tJ0igaQ==} - peerDependencies: - tweakpane: ^4.0.0 - - tweakpane@4.0.5: - resolution: {integrity: sha512-rxEXdSI+ArlG1RyO6FghC4ZUX8JkEfz8F3v1JuteXSV0pEtHJzyo07fcDG+NsJfN5L39kSbCYbB9cBGHyuI/tQ==} - typescript@5.8.2: resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} engines: {node: '>=14.17'} @@ -890,22 +817,6 @@ packages: snapshots: - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/generator@7.28.3': - dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-globals@7.28.0': {} - '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {} @@ -914,24 +825,6 @@ snapshots: dependencies: '@babel/types': 7.28.4 - '@babel/template@7.27.2': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 - - '@babel/traverse@7.28.4': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.4 - '@babel/template': 7.27.2 - '@babel/types': 7.28.4 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - '@babel/types@7.28.4': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -1023,20 +916,8 @@ snapshots: dependencies: '@isaacs/balanced-match': 4.0.1 - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@microsoft/api-extractor-model@7.30.7': dependencies: '@microsoft/tsdoc': 0.15.1 @@ -1185,12 +1066,6 @@ snapshots: transitivePeerDependencies: - '@types/node' - '@tweakpane/core@2.0.5': {} - - '@tweakpane/plugin-essentials@0.2.1(tweakpane@4.0.5)': - dependencies: - tweakpane: 4.0.5 - '@tweenjs/tween.js@23.1.3': {} '@types/argparse@1.0.38': {} @@ -1410,10 +1285,6 @@ snapshots: jju@1.4.0: {} - js-tokens@4.0.0: {} - - jsesc@3.1.0: {} - json-schema-traverse@1.0.0: {} jsonfile@6.2.0: @@ -1578,20 +1449,6 @@ snapshots: dependencies: is-number: 7.0.0 - tsl-uniform-ui-vite-plugin@https://codeload.github.com/bhushan6/tsl-Uniform-UI-Vite-Plugin/tar.gz/e10bb39f1a89591ffde93b361a7e18be7e16ac59#path:package: - dependencies: - '@babel/parser': 7.28.4 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 - transitivePeerDependencies: - - supports-color - - tweakpane-plugin-file-import@1.1.1(tweakpane@4.0.5): - dependencies: - tweakpane: 4.0.5 - - tweakpane@4.0.5: {} - typescript@5.8.2: {} ufo@1.6.1: {}