UNPKG

@threlte/core

Version:

A 3D framework for the web, built on top of Svelte and Three.js

130 lines (129 loc) 4.57 kB
import { getContext, onDestroy, setContext } from 'svelte'; import { AgXToneMapping, ColorManagement, PCFSoftShadowMap, WebGLRenderer } from 'three'; import { useTask } from '../../hooks/useTask.svelte.js'; import { currentWritable, watch } from '../../utilities/index.js'; import { useCamera } from './camera.js'; import { useDisposal } from './disposal.js'; import { useDOM } from './dom.js'; import { useScene } from './scene.js'; import { useScheduler } from './scheduler.svelte.js'; export const createRendererContext = (options) => { const { dispose } = useDisposal(); const { camera } = useCamera(); const { scene } = useScene(); const { invalidate, renderStage, autoRender, scheduler, resetFrameInvalidation } = useScheduler(); const { size, canvas } = useDOM(); const renderer = options.createRenderer ? options.createRenderer(canvas) : new WebGLRenderer({ canvas, powerPreference: 'high-performance', antialias: true, alpha: true }); const autoRenderTask = renderStage.createTask(Symbol('threlte-auto-render-task'), () => { renderer.render(scene, camera.current); }); const context = { renderer: renderer, colorManagementEnabled: currentWritable(options.colorManagementEnabled ?? true), colorSpace: currentWritable(options.colorSpace ?? 'srgb'), dpr: currentWritable(options.dpr ?? window.devicePixelRatio), shadows: currentWritable(options.shadows ?? PCFSoftShadowMap), toneMapping: currentWritable(options.toneMapping ?? AgXToneMapping), autoRenderTask }; setContext('threlte-renderer-context', context); watch([context.colorManagementEnabled], ([colorManagementEnabled]) => { ColorManagement.enabled = colorManagementEnabled; }); watch([context.colorSpace], ([colorSpace]) => { if ('outputColorSpace' in renderer) { renderer.outputColorSpace = colorSpace; } }); watch([context.dpr], ([dpr]) => { if ('setPixelRatio' in renderer) { renderer.setPixelRatio(dpr); } }); // Resize the renderer when the size changes const { start, stop } = useTask(() => { if (!('xr' in renderer) || renderer.xr?.isPresenting) return; renderer.setSize(size.current.width, size.current.height); invalidate(); stop(); }, { before: autoRenderTask, autoStart: false, autoInvalidate: false }); watch([size], () => { start(); }); watch([context.shadows], ([shadows]) => { if (!('shadowMap' in renderer)) return; renderer.shadowMap.enabled = !!shadows; if (shadows && shadows !== true) { renderer.shadowMap.type = shadows; } else if (shadows === true) { renderer.shadowMap.type = PCFSoftShadowMap; } }); watch([context.toneMapping], ([toneMapping]) => { if (!('toneMapping' in renderer)) return; renderer.toneMapping = toneMapping; }); watch([autoRender], ([autoRender]) => { if (autoRender) { context.autoRenderTask.start(); } else { context.autoRenderTask.stop(); } return () => { context.autoRenderTask.stop(); }; }); if ('setAnimationLoop' in context.renderer) { const renderer = context.renderer; renderer.setAnimationLoop((time) => { dispose(); scheduler.run(time); resetFrameInvalidation(); }); } onDestroy(() => { if ('dispose' in context.renderer) { const dispose = context.renderer.dispose; dispose(); } }); $effect.pre(() => { context.colorManagementEnabled.set(options.colorManagementEnabled ?? true); }); $effect.pre(() => { context.colorSpace.set(options.colorSpace ?? 'srgb'); }); $effect.pre(() => { context.toneMapping.set(options.toneMapping ?? AgXToneMapping); }); $effect.pre(() => { context.shadows.set(options.shadows ?? PCFSoftShadowMap); }); $effect.pre(() => { context.dpr.set(options.dpr ?? window.devicePixelRatio); }); return context; }; export const useRenderer = () => { const context = getContext('threlte-renderer-context'); if (!context) { throw new Error('useRenderer can only be used in a child component to <Canvas>.'); } return context; };