Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion src/areas/generate/GeneratePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,6 @@ function LightPopover({
{lightRow('Sun', 'mainColor', 'mainIntensity', 4)}
{lightRow('Fill', 'fillColor', 'fillIntensity', 2)}
{plainRow('Ambient', 'ambientIntensity', 1.5)}
{plainRow('Exposure', 'exposure', 3)}
{plainRow('Environment', 'envIntensity', 2)}
<button
onClick={onClose}
Expand Down
27 changes: 4 additions & 23 deletions src/areas/generate/components/Viewer3D.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import type { ReactNode, ErrorInfo, MutableRefObject } from 'react'
import { Canvas, useFrame, useLoader, useThree } from '@react-three/fiber'
import type { ThreeEvent } from '@react-three/fiber'
import { Environment, GizmoHelper, Lightformer, OrbitControls, useGizmoContext, useGLTF } from '@react-three/drei'
import { EffectComposer, Outline, Select, Selection, ToneMapping } from '@react-three/postprocessing'
import { ToneMappingMode } from 'postprocessing'
import { EffectComposer, Outline, Select, Selection } from '@react-three/postprocessing'
import * as THREE from 'three'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'
import { computeBoundsTree, disposeBoundsTree, acceleratedRaycast } from 'three-mesh-bvh'
Expand Down Expand Up @@ -81,16 +80,6 @@ function CanvasCapture({
return null
}

// Live-applies the tone-mapping exposure from the Lighting popover. `gl` props are
// only read at Canvas creation, so exposure must be pushed onto the renderer here.
function RendererSync({ exposure }: { exposure: number }): null {
const gl = useThree((s) => s.gl)
useEffect(() => {
gl.toneMappingExposure = exposure
}, [gl, exposure])
return null
}

// ---------------------------------------------------------------------------
// ModelErrorBoundary — catches useGLTF load failures (e.g. 404)
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -936,9 +925,9 @@ export default function Viewer3D({ lightSettings = DEFAULT_LIGHT_SETTINGS, gizmo
// Memoise the post-processing stack so its children stay referentially stable.
// @react-three/postprocessing rebuilds (recompiles) all EffectPasses whenever the
// <EffectComposer> children identity changes; without this, every Viewer3D re-render
// (e.g. dragging a Lighting slider) recompiles the outline/tone-mapping shaders and
// flashes a white, un-tone-mapped frame. The Outline still tracks selection through
// the <Selection> context, so nothing here needs to depend on render state.
// (e.g. dragging a Lighting slider) recompiles the outline shader. The Outline still
// tracks selection through the <Selection> context, so nothing here needs to depend
// on render state.
const postProcessing = useMemo(() => (
<EffectComposer
autoClear={false}
Expand All @@ -953,11 +942,6 @@ export default function Viewer3D({ lightSettings = DEFAULT_LIGHT_SETTINGS, gizmo
hiddenEdgeColor={SELECTION_OUTLINE_HIDDEN_COLOR}
xRay={false}
/>
{/* Tone mapping must live INSIDE the composer: while mounted it forces
gl.toneMapping = NoToneMapping, so the Lighting "Exposure" control
(RendererSync → gl.toneMappingExposure) only takes effect through
this effect's NeutralToneMapping shader. */}
<ToneMapping mode={ToneMappingMode.NEUTRAL} />
</EffectComposer>
), [])

Expand All @@ -982,13 +966,10 @@ export default function Viewer3D({ lightSettings = DEFAULT_LIGHT_SETTINGS, gizmo
antialias: true,
preserveDrawingBuffer: true,
outputColorSpace: THREE.SRGBColorSpace,
toneMapping: THREE.NeutralToneMapping,
toneMappingExposure: 1.8,
}}
>
<color attach="background" args={['#18181b']} />
<CanvasCapture domRef={canvasRef} />
<RendererSync exposure={lightSettings.exposure ?? DEFAULT_LIGHT_SETTINGS.exposure} />
<ambientLight intensity={lightSettings.ambientIntensity ?? DEFAULT_LIGHT_SETTINGS.ambientIntensity} />
<Environment background={false}>
<Lightformer intensity={2 * (lightSettings.envIntensity ?? DEFAULT_LIGHT_SETTINGS.envIntensity)} position={[0, 4, 4]} scale={8} />
Expand Down
4 changes: 1 addition & 3 deletions src/shared/stores/appStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export interface LightSettings {
fillIntensity: number
fillColor: string
ambientIntensity: number
exposure: number
envIntensity: number
}

Expand All @@ -64,14 +63,13 @@ const DEFAULT_OPTIONS: GenerationOptions = {
export const DEFAULT_LIGHT_SETTINGS: LightSettings = {
// Matches the offline debug renderer's flat studio rig: two soft directional
// lights (key ~0.8 / fill ~0.35) + high ambient (0.45) that lifts dark albedo
// (black cat) out of "void" shadows, NO IBL (envIntensity 0), neutral exposure.
// (black cat) out of "void" shadows, NO IBL (envIntensity 0).
// All live-adjustable from the Lighting popover (Reset returns here).
mainIntensity: 0.8,
mainColor: '#ffffff',
fillIntensity: 0.35,
fillColor: '#ffffff',
ambientIntensity: 0.45,
exposure: 1.0,
envIntensity: 0.0,
}

Expand Down