fix: slider rewrite, ESC key fix, antialias improvements (#187)
Slider control: - Rewrite drag interaction to use label-based drag instead of track slider - Remove hardcoded min/max bounds, default to unbounded - Add ArrowLeft/ArrowRight key support alongside Up/Down - Cleaner, more compact UI (238 lines vs 332) ESC key cancel fix: - Pressing ESC during mid-action (drawing wall, placing slab) now only cancels the current action, not also switching back to select mode - Tools mark cancel as consumed via markToolCancelConsumed() - Second ESC press switches to select mode as before Window panel: - Remove hardcoded min/max on position, dimension, and frame sliders - Works with new unbounded slider defaults Viewer: - Re-enable default antialias on WebGPU renderer - Remove GroundOccluder (no longer needed) - Add resize debounce (100ms) - Clean up post-processing pipeline code
This commit is contained in:
@@ -21,7 +21,6 @@ import { ScanSystem } from '../../systems/scan/scan-system'
|
||||
import { WallCutout } from '../../systems/wall/wall-cutout'
|
||||
import { ZoneSystem } from '../../systems/zone/zone-system'
|
||||
import { SceneRenderer } from '../renderers/scene-renderer'
|
||||
import { GroundOccluder } from './ground-occluder'
|
||||
import { Lights } from './lights'
|
||||
import { PerfMonitor } from './perf-monitor'
|
||||
import PostProcessing from './post-processing'
|
||||
@@ -110,15 +109,18 @@ const Viewer: React.FC<ViewerProps> = ({
|
||||
const renderer = new THREE.WebGPURenderer(props as any)
|
||||
renderer.toneMapping = THREE.ACESFilmicToneMapping
|
||||
renderer.toneMappingExposure = 0.9
|
||||
// renderer.init() // Only use when using <DebugRenderer />
|
||||
return renderer
|
||||
}}
|
||||
resize={{
|
||||
debounce: 100,
|
||||
}}
|
||||
shadows={{
|
||||
type: THREE.PCFShadowMap,
|
||||
enabled: true,
|
||||
}}
|
||||
>
|
||||
{/* <AnimatedBackground isDark={theme === 'dark'} /> */}
|
||||
<GroundOccluder />
|
||||
<ViewerCamera />
|
||||
|
||||
{/* <directionalLight position={[10, 10, 5]} intensity={0.5} castShadow
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Color, Layers, 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 { denoise } from 'three/examples/jsm/tsl/display/DenoiseNode.js'
|
||||
import {
|
||||
add,
|
||||
@@ -21,7 +20,6 @@ import {
|
||||
time,
|
||||
uniform,
|
||||
vec4,
|
||||
velocity,
|
||||
} from 'three/tsl'
|
||||
import { RenderPipeline, type WebGPURenderer } from 'three/webgpu'
|
||||
import { SCENE_LAYER, ZONE_LAYER } from '../../lib/layers'
|
||||
@@ -129,67 +127,11 @@ const PostProcessingPasses = () => {
|
||||
outliner.hoveredObjects.length = 0
|
||||
|
||||
try {
|
||||
// Scene pass with MRT for SSGI
|
||||
const scenePass = pass(scene, camera)
|
||||
scenePass.setMRT(
|
||||
mrt({
|
||||
output,
|
||||
diffuseColor,
|
||||
normal: directionToColor(normalView),
|
||||
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))
|
||||
})
|
||||
|
||||
const zonePass = pass(scene, camera)
|
||||
zonePass.setLayers(zoneLayers)
|
||||
// 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
|
||||
|
||||
const giTexture = (giPass as any).getTextureNode()
|
||||
|
||||
// DenoiseNode only denoises RGB — alpha is passed through unchanged.
|
||||
// SSGI packs AO into alpha, so we remap it into RGB before denoising.
|
||||
// convertToTexture() inside denoise() will call rtt() on this vec4 node automatically.
|
||||
const aoAsRgb = vec4(giTexture.a, giTexture.a, giTexture.a, float(1))
|
||||
const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, camera)
|
||||
denoisePass.index.value = 0
|
||||
denoisePass.radius.value = 4
|
||||
|
||||
const gi = giPass.rgb
|
||||
const ao = (denoisePass as any).r
|
||||
// const gi = giPass.rgb;
|
||||
// const ao = giPass.a;
|
||||
const scenePassColor = scenePass.getTextureNode('output')
|
||||
|
||||
// Background detection via alpha: renderer clears with alpha=0 (setClearAlpha(0) in useFrame),
|
||||
// so background pixels have scenePassColor.a=0 while geometry pixels have output.a=1.
|
||||
@@ -198,11 +140,62 @@ const PostProcessingPasses = () => {
|
||||
const hasGeometry = scenePassColor.a
|
||||
const contentAlpha = hasGeometry.max(zonePass.a)
|
||||
|
||||
// Composite: scene * AO + diffuse * GI
|
||||
const compositePass = vec4(
|
||||
add(scenePassColor.rgb.mul(ao), add(zonePass.rgb, scenePassDiffuse.rgb.mul(gi))),
|
||||
contentAlpha,
|
||||
)
|
||||
let sceneColor = scenePassColor as unknown as ReturnType<typeof vec4>
|
||||
|
||||
if (SSGI_PARAMS.enabled) {
|
||||
// MRT only needed for SSGI (diffuse for GI, normal for SSGI sampling)
|
||||
scenePass.setMRT(
|
||||
mrt({
|
||||
output,
|
||||
diffuseColor,
|
||||
normal: directionToColor(normalView),
|
||||
}),
|
||||
)
|
||||
|
||||
const scenePassDiffuse = scenePass.getTextureNode('diffuseColor')
|
||||
const scenePassDepth = scenePass.getTextureNode('depth')
|
||||
const scenePassNormal = scenePass.getTextureNode('normal')
|
||||
|
||||
// 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) => colorToDirection(scenePassNormal.sample(uv)))
|
||||
|
||||
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
|
||||
|
||||
const giTexture = (giPass as any).getTextureNode()
|
||||
|
||||
// DenoiseNode only denoises RGB — alpha is passed through unchanged.
|
||||
// SSGI packs AO into alpha, so we remap it into RGB before denoising.
|
||||
const aoAsRgb = vec4(giTexture.a, giTexture.a, giTexture.a, float(1))
|
||||
const denoisePass = denoise(aoAsRgb, scenePassDepth, sceneNormal, camera)
|
||||
denoisePass.index.value = 0
|
||||
denoisePass.radius.value = 4
|
||||
|
||||
const gi = giPass.rgb
|
||||
const ao = (denoisePass as any).r
|
||||
|
||||
// Composite: scene * AO + diffuse * GI
|
||||
sceneColor = vec4(
|
||||
add(scenePassColor.rgb.mul(ao), add(zonePass.rgb, scenePassDiffuse.rgb.mul(gi))),
|
||||
contentAlpha,
|
||||
)
|
||||
}
|
||||
|
||||
function generateSelectedOutlinePass() {
|
||||
const edgeStrength = uniform(3)
|
||||
@@ -256,20 +249,15 @@ const PostProcessingPasses = () => {
|
||||
const selectedOutlinePass = generateSelectedOutlinePass()
|
||||
const hoverOutlinePass = generateHoverOutlinePass()
|
||||
|
||||
// Combine composite with outlines BEFORE applying TRAA
|
||||
const compositeWithOutlines = SSGI_PARAMS.enabled
|
||||
? vec4(add(compositePass.rgb, selectedOutlinePass.add(hoverOutlinePass)), compositePass.a)
|
||||
: vec4(add(scenePassColor.rgb, selectedOutlinePass.add(hoverOutlinePass)), scenePassColor.a)
|
||||
const compositeWithOutlines = vec4(
|
||||
add(sceneColor.rgb, selectedOutlinePass.add(hoverOutlinePass)),
|
||||
sceneColor.a,
|
||||
)
|
||||
|
||||
// TRAA (Temporal Reprojection Anti-Aliasing) - applied AFTER combining everything
|
||||
const traaOutput = traa(compositeWithOutlines, scenePassDepth, scenePassVelocity, camera)
|
||||
|
||||
// For zone-over-background pixels, scenePassDepth=1.0 (no scene geometry) causes TRAA
|
||||
// to output black. Use hasGeometry to blend: geometry pixels use traaRgb, all others
|
||||
// (zones over background, pure background) use compositePass.rgb directly.
|
||||
const traaRgb = (traaOutput as any).rgb
|
||||
const colorSource = mix(compositePass.rgb, traaRgb, hasGeometry)
|
||||
const finalOutput = vec4(mix(bgUniform.current, colorSource, contentAlpha), float(1))
|
||||
const finalOutput = vec4(
|
||||
mix(bgUniform.current, compositeWithOutlines.rgb, contentAlpha),
|
||||
float(1),
|
||||
)
|
||||
|
||||
const renderPipeline = new RenderPipeline(renderer as unknown as WebGPURenderer)
|
||||
renderPipeline.outputNode = finalOutput
|
||||
|
||||
Reference in New Issue
Block a user