diff --git a/apps/editor/components/editor/index.tsx b/apps/editor/components/editor/index.tsx index 7e531da4..5c495509 100644 --- a/apps/editor/components/editor/index.tsx +++ b/apps/editor/components/editor/index.tsx @@ -1,6 +1,11 @@ 'use client' -import { initSpatialGridSync, initSpaceDetectionSync, sceneRegistry, useScene } from '@pascal-app/core' +import { + initSpaceDetectionSync, + initSpatialGridSync, + sceneRegistry, + useScene, +} from '@pascal-app/core' import { useGridEvents, useViewer, Viewer } from '@pascal-app/viewer' import { useFrame } from '@react-three/fiber' @@ -43,7 +48,7 @@ export default function Editor() { {/* Editor only system to toggle zone visibility */} {/* */} - + @@ -51,7 +56,6 @@ export default function Editor() { ) } - const Grid = ({ cellSize = 0.5, cellThickness = 0.5, diff --git a/packages/viewer/src/components/renderers/item/item-renderer.tsx b/packages/viewer/src/components/renderers/item/item-renderer.tsx index ad9a2528..f247dd29 100644 --- a/packages/viewer/src/components/renderers/item/item-renderer.tsx +++ b/packages/viewer/src/components/renderers/item/item-renderer.tsx @@ -9,17 +9,17 @@ import { useNodeEvents } from '../../../hooks/use-node-events' // Shared materials to avoid creating new instances for every mesh const defaultMaterial = new MeshStandardNodeMaterial({ color: 0xffffff, - roughness: 0.8, + roughness: 1, metalness: 0, }) const glassMaterial = new MeshStandardNodeMaterial({ name: 'glass', - color: 'skyblue', + color: 'lightgray', roughness: 0.8, metalness: 0, transparent: true, - opacity: 0.25, + opacity: 0.35, side: DoubleSide, depthWrite: false, }) diff --git a/packages/viewer/src/components/viewer/index.tsx b/packages/viewer/src/components/viewer/index.tsx index fb5db148..df13ebb8 100644 --- a/packages/viewer/src/components/viewer/index.tsx +++ b/packages/viewer/src/components/viewer/index.tsx @@ -28,12 +28,12 @@ interface ViewerProps { const Viewer: React.FC = ({ children, selectionManager = 'default' }) => { return ( { const renderer = new THREE.WebGPURenderer(props as any) await renderer.init() renderer.toneMapping = THREE.ACESFilmicToneMapping - renderer.toneMappingExposure = 1.2 + renderer.toneMappingExposure = 0.9 return renderer }} shadows={{ @@ -42,7 +42,7 @@ const Viewer: React.FC = ({ children, selectionManager = 'default' }} camera={{ position: [50, 50, 50], fov: 50 }} > - + {/* - - - + + + - - + + + + + + {/* */} ) } diff --git a/packages/viewer/src/components/viewer/post-processing.tsx b/packages/viewer/src/components/viewer/post-processing.tsx index b6c179dc..c4290f73 100644 --- a/packages/viewer/src/components/viewer/post-processing.tsx +++ b/packages/viewer/src/components/viewer/post-processing.tsx @@ -1,99 +1,186 @@ -import { useFrame, useThree } from "@react-three/fiber"; -import { useEffect, useRef } from "react"; -import { Color } from "three"; -import { outline } from "three/addons/tsl/display/OutlineNode.js"; -import { oscSine, pass, time, uniform } from "three/tsl"; -import { PostProcessing, type WebGPURenderer } from "three/webgpu"; -import useViewer from "../../store/use-viewer"; +import { useFrame, useThree } from '@react-three/fiber' +import { useEffect, useRef } from 'react' +import { Color, UnsignedByteType } from 'three' +import { outline } from 'three/addons/tsl/display/OutlineNode.js' +import { ssgi } from 'three/addons/tsl/display/SSGINode.js' +import { traa } from 'three/addons/tsl/display/TRAANode.js' +import { + add, + colorToDirection, + diffuseColor, + directionToColor, + mrt, + normalView, + oscSine, + output, + pass, + sample, + time, + uniform, + vec4, + velocity, +} from 'three/tsl' +import { PostProcessing, type WebGPURenderer } from 'three/webgpu' +import useViewer from '../../store/use-viewer' + +// SSGI Parameters - adjust these to fine-tune global illumination and ambient occlusion +export const SSGI_PARAMS = { + enabled: true, + sliceCount: 2, + stepCount: 8, + radius: 2, + expFactor: 2, + thickness: 0.5, + backfaceLighting: 0.5, + aoIntensity: 1.5, + giIntensity: 1, + useLinearThickness: false, + useScreenSpaceSampling: true, + useTemporalFiltering: true, +} const PostProcessingPasses = () => { - const { gl: renderer, scene, camera } = useThree(); - const postProcessingRef = useRef(null); + const { gl: renderer, scene, camera } = useThree() + const postProcessingRef = useRef(null) useEffect(() => { if (!renderer || !scene || !camera) { - return; + return } - const scenePass = pass(scene, camera); + // Scene pass with MRT for SSGI + const scenePass = pass(scene, camera) + scenePass.setMRT( + mrt({ + output: output, + diffuseColor: diffuseColor, + normal: directionToColor(normalView), + velocity: velocity, + }), + ) + + // Get texture outputs + const scenePassColor = scenePass.getTextureNode('output') + const scenePassDiffuse = scenePass.getTextureNode('diffuseColor') + const scenePassDepth = scenePass.getTextureNode('depth') + const scenePassNormal = scenePass.getTextureNode('normal') + const scenePassVelocity = scenePass.getTextureNode('velocity') + + // Optimize texture bandwidth + const diffuseTexture = scenePass.getTexture('diffuseColor') + diffuseTexture.type = UnsignedByteType + + const normalTexture = scenePass.getTexture('normal') + normalTexture.type = UnsignedByteType + + // Extract normal from color-encoded texture + const sceneNormal = sample((uv) => { + return colorToDirection(scenePassNormal.sample(uv)) + }) + + // SSGI Pass (cast to PerspectiveCamera for SSGI) + const giPass = ssgi(scenePassColor, scenePassDepth, sceneNormal, camera as any) + giPass.sliceCount.value = SSGI_PARAMS.sliceCount + giPass.stepCount.value = SSGI_PARAMS.stepCount + giPass.radius.value = SSGI_PARAMS.radius + giPass.expFactor.value = SSGI_PARAMS.expFactor + giPass.thickness.value = SSGI_PARAMS.thickness + giPass.backfaceLighting.value = SSGI_PARAMS.backfaceLighting + giPass.aoIntensity.value = SSGI_PARAMS.aoIntensity + giPass.giIntensity.value = SSGI_PARAMS.giIntensity + giPass.useLinearThickness.value = SSGI_PARAMS.useLinearThickness + giPass.useScreenSpaceSampling.value = SSGI_PARAMS.useScreenSpaceSampling + giPass.useTemporalFiltering = SSGI_PARAMS.useTemporalFiltering + + // Extract GI and AO from SSGI pass + const gi = giPass.rgb + const ao = giPass.a + + // Composite: scene * AO + diffuse * GI + const compositePass = vec4( + add(scenePassColor.rgb.mul(ao), scenePassDiffuse.rgb.mul(gi)), + scenePassColor.a, + ) + + // TRAA (Temporal Reprojection Anti-Aliasing) + const traaPass = traa(compositePass, scenePassDepth, scenePassVelocity, camera) function generateSelectedOutlinePass() { - const edgeStrength = uniform(3); - const edgeGlow = uniform(0); - const edgeThickness = uniform(1); - const visibleEdgeColor = uniform(new Color(0xffffff)); - const hiddenEdgeColor = uniform(new Color(0xf3ff47)); + const edgeStrength = uniform(3) + const edgeGlow = uniform(0) + const edgeThickness = uniform(1) + const visibleEdgeColor = uniform(new Color(0xffffff)) + const hiddenEdgeColor = uniform(new Color(0xf3ff47)) const outlinePass = outline(scene, camera, { selectedObjects: useViewer.getState().outliner.selectedObjects, edgeGlow, edgeThickness, - }); - const { visibleEdge, hiddenEdge } = outlinePass; + }) + const { visibleEdge, hiddenEdge } = outlinePass const outlineColor = visibleEdge .mul(visibleEdgeColor) .add(hiddenEdge.mul(hiddenEdgeColor)) - .mul(edgeStrength); + .mul(edgeStrength) - return outlineColor; + return outlineColor } function generateHoverOutlinePass() { - const edgeStrength = uniform(5); - const edgeGlow = uniform(0.5); - const edgeThickness = uniform(1.5); - const pulsePeriod = uniform(3); - const visibleEdgeColor = uniform(new Color(0x00aaff)); - const hiddenEdgeColor = uniform(new Color(0xf3ff47)); + const edgeStrength = uniform(5) + const edgeGlow = uniform(0.5) + const edgeThickness = uniform(1.5) + const pulsePeriod = uniform(3) + const visibleEdgeColor = uniform(new Color(0x00aaff)) + const hiddenEdgeColor = uniform(new Color(0xf3ff47)) const outlinePass = outline(scene, camera, { selectedObjects: useViewer.getState().outliner.hoveredObjects, edgeGlow, edgeThickness, - }); - const { visibleEdge, hiddenEdge } = outlinePass; + }) + const { visibleEdge, hiddenEdge } = outlinePass - const period = time.div(pulsePeriod).mul(2); - const osc = oscSine(period).mul(0.5).add(0.5); // osc [ 0.5, 1.0 ] + const period = time.div(pulsePeriod).mul(2) + const osc = oscSine(period).mul(0.5).add(0.5) // osc [ 0.5, 1.0 ] const outlineColor = visibleEdge .mul(visibleEdgeColor) .add(hiddenEdge.mul(hiddenEdgeColor)) - .mul(edgeStrength); - const outlinePulse = pulsePeriod - .greaterThan(0) - .select(outlineColor.mul(osc), outlineColor); - return outlinePulse; + .mul(edgeStrength) + const outlinePulse = pulsePeriod.greaterThan(0).select(outlineColor.mul(osc), outlineColor) + return outlinePulse } // Setup post-processing - const postProcessing = new PostProcessing( - renderer as unknown as WebGPURenderer, - ); + const postProcessing = new PostProcessing(renderer as unknown as WebGPURenderer) - const selectedOutlinePass = generateSelectedOutlinePass(); - const hoverOutlinePass = generateHoverOutlinePass(); + const selectedOutlinePass = generateSelectedOutlinePass() + const hoverOutlinePass = generateHoverOutlinePass() - postProcessing.outputNode = selectedOutlinePass - .add(hoverOutlinePass) - .add(scenePass); - postProcessingRef.current = postProcessing; + // Combine SSGI output with outlines + const finalOutput = SSGI_PARAMS.enabled + ? selectedOutlinePass.add(hoverOutlinePass).add(traaPass) + : selectedOutlinePass.add(hoverOutlinePass).add(scenePassColor) + + postProcessing.outputNode = finalOutput + postProcessingRef.current = postProcessing return () => { if (postProcessingRef.current) { - postProcessingRef.current.dispose(); + postProcessingRef.current.dispose() } - postProcessingRef.current = null; - }; - }, [renderer, scene, camera]); + postProcessingRef.current = null + } + }, [renderer, scene, camera]) useFrame(() => { if (postProcessingRef.current) { - postProcessingRef.current.render(); + postProcessingRef.current.render() } - }, 1); + }, 1) - return null; -}; + return null +} -export default PostProcessingPasses; +export default PostProcessingPasses diff --git a/packages/viewer/src/systems/wall/wall-cutout.tsx b/packages/viewer/src/systems/wall/wall-cutout.tsx index 6a3bd110..b9ee4f53 100644 --- a/packages/viewer/src/systems/wall/wall-cutout.tsx +++ b/packages/viewer/src/systems/wall/wall-cutout.tsx @@ -42,6 +42,8 @@ const invsibleWallMaterial = new MeshStandardNodeMaterial({ }) const wallMaterial = new MeshStandardNodeMaterial({ color: 'white', + roughness: 1, + metalness: 0, }) export const WallCutout = () => {