ssgi + ao

This commit is contained in:
wass08
2026-02-03 15:50:09 +09:00
parent 30653b49c9
commit 66463f3526
6 changed files with 190 additions and 86 deletions
+7 -3
View File
@@ -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 */}
<ZoneSystem />
{/* <Stats /> */}
<Grid cellColor="#666" sectionColor="#999" fadeDistance={30} />
<Grid cellColor="#aaa" sectionColor="#ccc" fadeDistance={30} />
<ToolManager />
<CustomCameraControls />
</Viewer>
@@ -51,7 +56,6 @@ export default function Editor() {
)
}
const Grid = ({
cellSize = 0.5,
cellThickness = 0.5,
@@ -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,
})
@@ -28,12 +28,12 @@ interface ViewerProps {
const Viewer: React.FC<ViewerProps> = ({ children, selectionManager = 'default' }) => {
return (
<Canvas
className={'bg-[#303035]'}
className={'bg-[#fafafa]'}
gl={async (props) => {
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<ViewerProps> = ({ children, selectionManager = 'default'
}}
camera={{ position: [50, 50, 50], fov: 50 }}
>
<color attach="background" args={['#ececec']} />
<color attach="background" args={['#fafafa']} />
<ViewerCamera />
{/* <directionalLight position={[10, 10, 5]} intensity={0.5} castShadow
@@ -1,4 +1,3 @@
import { Environment } from '@react-three/drei'
import { useRef } from 'react'
import type { DirectionalLight, OrthographicCamera } from 'three/webgpu'
@@ -12,30 +11,42 @@ export function Lights() {
return (
<>
<directionalLight
ref={lightRef}
position={[10, 10, 10]}
castShadow
intensity={1}
shadow-bias={-0.002}
shadow-normalBias={0.3}
shadow-mapSize={[1024, 1024]}
shadow-radius={3}
>
<orthographicCamera
ref={shadowCamera}
attach="shadow-camera"
near={1}
far={100}
left={-shadowCameraSize}
right={shadowCameraSize}
top={shadowCameraSize}
bottom={-shadowCameraSize}
/>
</directionalLight>
<directionalLight
ref={lightRef}
position={[10, 10, 10]}
castShadow
intensity={4}
shadow-bias={-0.002}
shadow-normalBias={0.3}
shadow-mapSize={[1024, 1024]}
shadow-radius={3}
shadow-intensity={0.4}
>
<orthographicCamera
ref={shadowCamera}
attach="shadow-camera"
near={1}
far={100}
left={-shadowCameraSize}
right={shadowCameraSize}
top={shadowCameraSize}
bottom={-shadowCameraSize}
/>
</directionalLight>
<ambientLight intensity={0.2} />
<Environment preset="sunset" environmentIntensity={0.4} />
<directionalLight
position={[-10, 10, -10]}
intensity={0.75}
/>
<directionalLight
position={[-10, 10, 10]}
intensity={1}
/>
<ambientLight intensity={0.5}
color='white' />
{/* <Environment preset="sunset" environmentIntensity={0.4} /> */}
</>
)
}
@@ -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<PostProcessing | null>(null);
const { gl: renderer, scene, camera } = useThree()
const postProcessingRef = useRef<PostProcessing | null>(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
@@ -42,6 +42,8 @@ const invsibleWallMaterial = new MeshStandardNodeMaterial({
})
const wallMaterial = new MeshStandardNodeMaterial({
color: 'white',
roughness: 1,
metalness: 0,
})
export const WallCutout = () => {