--- name: flutter_scene-looks version: 4 description: Give a flutter_scene render a deliberate, polished look. Use this whenever a scene looks flat, dull, or washed out, or whenever the ask is to make it look good, because a good look is lighting plus post-processing, not geometry. Ships copy-paste EnvironmentSettings presets that configure the whole stack coherently. --- # Making flutter_scene look good The single most common reason a flutter_scene render looks amateur is that the post-processing stack was left at defaults. flutter_scene has a deep lit-and-post pipeline (image-based lighting, tone mapping, bloom, lens flares, ambient occlusion, screen-space reflections, fog, god rays, depth of field, color grading, vignette, film grain). Out of the box almost all of it is off, so a bare scene is technically correct and visually flat. **The insight: a polished look is lighting and post-processing, not geometry.** Better meshes will not fix a flat render. Do not spend effort on modeling detail when the scene reads dull; spend it on the look. And a look is a *coherent* set of choices, not twelve knobs turned independently. Turning bloom, AO, SSR, fog, grain, and grading up one at a time, each by feel, lands in muddy incoherent territory. Pick one deliberate preset and paste it whole. ## Apply a look in one line Every scene-wide look field lives on one aggregate value, `EnvironmentSettings`, assigned through a single setter: ```dart scene.environmentSettings = EnvironmentSettings(/* fields below */); ``` That one assignment configures tone mapping, exposure, image-based-lighting intensity, and the entire post stack, **including** fog, god rays, depth of field, and auto exposure. You do not need to touch `scene.fog`, `scene.godRays`, `scene.depthOfField`, or `scene.autoExposure` separately; those are the live per-effect objects, but `EnvironmentSettings` carries all of their fields and applies them for you. Every effect is off by default, so a preset only names the fields it turns on. `EnvironmentSettings` is also a blendable snapshot. Read the current look with `scene.environmentSettings`, and cross-fade two looks with `EnvironmentSettings.lerp(a, b, t)` driven from an animation. That is how you transition day to night or ramp an effect in. ## Lights are separate, and still matter `EnvironmentSettings` covers image-based lighting (the `environment` map) and the whole post stack, but **direct lights are not part of it.** Shadows, god rays, and any strong key light come from the scene's lights, set separately: ```dart scene.directionalLight = DirectionalLight( direction: vm.Vector3(-0.4, -1.0, -0.3), intensity: 4.0, castsShadow: true, // shadows are off until you ask ); ``` An unset `scene.environment` still resolves to a default studio IBL, so a `PhysicallyBasedMaterial` is always lit. But a scene with no direct light is soft and shadowless. God rays require a shadow-casting `DirectionalLight`; shadows require `castsShadow: true` on the light. ## The four looks Paste one whole. Each is a real `EnvironmentSettings` literal; import `package:vector_math/vector_math.dart as vm` for the `Vector3` color fields. AO, SSR, god rays, and depth of field require a `PerspectiveCamera` (the only built-in camera). ### showcase Clean, bright product-viz beauty. Punchy tone mapping, soft bloom on highlights, grounded contact occlusion, and real reflections. The default reach-for-it look. ```dart scene.environmentSettings = EnvironmentSettings( toneMapping: ToneMappingMode.aces, exposure: 1.0, bloomEnabled: true, bloomThreshold: 1.1, bloomIntensity: 0.2, bloomScatter: 0.7, ambientOcclusionEnabled: true, ambientOcclusionMethod: AmbientOcclusionMethod.groundTruth, ambientOcclusionBentNormals: true, ambientOcclusionSpecularMode: SpecularAmbientOcclusionMode.bentCone, ambientOcclusionIntensity: 1.0, screenSpaceReflectionsEnabled: true, screenSpaceReflectionsIntensity: 1.0, vignetteEnabled: true, vignetteIntensity: 0.25, ); ``` ### stylized Vivid and graphic. Saturated, slightly warm, glowing, flatter shading (no heavy occlusion or reflections). For playful or illustrative scenes. ```dart scene.environmentSettings = EnvironmentSettings( toneMapping: ToneMappingMode.aces, colorGradingEnabled: true, saturation: 1.25, contrast: 1.1, brightness: 1.05, temperature: 0.1, bloomEnabled: true, bloomThreshold: 0.9, bloomIntensity: 0.28, bloomScatter: 0.8, vignetteEnabled: true, vignetteIntensity: 0.2, ); ``` ### moody Dark, cinematic, atmospheric. Lower exposure, cool graded, foggy, heavy vignette, subtle grain and aberration, deep occlusion. God rays if the scene has a shadow-casting sun. Use a cool horizon-colored fog. ```dart scene.environmentSettings = EnvironmentSettings( toneMapping: ToneMappingMode.aces, exposure: 0.8, colorGradingEnabled: true, contrast: 1.15, saturation: 0.9, temperature: -0.1, fogEnabled: true, fogMode: FogMode.exponential, fogColor: vm.Vector3(0.05, 0.06, 0.09), fogDensity: 0.03, ambientOcclusionEnabled: true, ambientOcclusionMethod: AmbientOcclusionMethod.groundTruth, ambientOcclusionIntensity: 1.2, ambientOcclusionPower: 1.8, vignetteEnabled: true, vignetteIntensity: 0.6, vignetteRadius: 0.6, filmGrainEnabled: true, filmGrainIntensity: 0.25, chromaticAberrationEnabled: true, chromaticAberrationIntensity: 0.15, godRaysEnabled: true, // needs scene.directionalLight with castsShadow: true godRaysIntensity: 1.0, godRaysDensity: 0.6, godRaysColor: vm.Vector3(1.0, 0.95, 0.85), ); ``` ### clean Neutral and honest. Minimal post, no grading, no bloom, no vignette, just correct tone mapping and gentle grounding occlusion. The right look for an editor, an inspector, a UI-embedded viewer, or anywhere you want an accurate read of the actual material. ```dart scene.environmentSettings = EnvironmentSettings( toneMapping: ToneMappingMode.pbrNeutral, // the engine default exposure: 1.0, ambientOcclusionEnabled: true, ambientOcclusionIntensity: 0.8, ambientOcclusionHalfResolution: true, ); ``` ## Tuning from a preset Start from the nearest look, then move one field at a time. - **Too dim or too bright overall.** Change `exposure` (default 1.0), not per-light intensity. Or turn on `autoExposureEnabled: true` to let the scene meter itself. - **Highlights not glowing.** Lower `bloomThreshold` toward 1.0 or below, or raise `bloomIntensity`. `bloomScatter` widens the glow. - **Want a lens flare off a bright source.** Turn on `lensFlareEnabled` (needs `bloomEnabled`). Keep `lensFlareIntensity` modest and drop `lensFlareHaloIntensity` first if the flare washes the frame; the halo is the broad wash, the ghosts are the crisp chain. - **Reads flat and ungrounded.** `ambientOcclusionEnabled: true`. Use `AmbientOcclusionMethod.groundTruth` for quality, keep `ambientOcclusionHalfResolution: true` for cost. - **Colors feel wrong.** Turn on `colorGradingEnabled` and reach for `saturation`, `contrast`, `temperature`, `tint` before anything else. - **Wrong tone-map feel.** `ToneMappingMode.aces` is contrasty and filmic, `pbrNeutral` (default) preserves hue and saturation, `agx` is the most neutral highlight rolloff. Do not reintroduce an `exposure: 2.0` hack; that was an artifact of an older renderer. ## Micro-surface metrics and anti-waxiness tuning When tuning procedural materials or custom shaders, use surface metrics and exposure discipline to eliminate waxiness, plastic reads, or harsh aliasing: - **Surface reads like smooth plastic.** Increase high-frequency micro-scale variation. Low 1-pixel luminance gradients `(|dL/dx| + |dL/dy|) / 2` indicate untextured or overly smooth surfaces. - **Blotchy macro clouds.** Balance high-frequency and low-frequency energy ratios (`hf/lf`). High variance with low fine detail indicates large-scale noise patches without adequate surface grain. - **Harsh normal-map glitter or torn noise.** Check terminator crossing behavior under low grazing light angles (sun low on the horizon). Over-amplified normal maps flip adjacent pixels between full light and full shadow. - **Colors look washed out in bright areas.** Tone mapping curves compress channel differences near the shoulder. A surface reading high brightness with low saturation is often over-exposed rather than under-pigmented; reduce exposure to restore natural material saturation. ## Look tools that are not on EnvironmentSettings Anti-aliasing, reflection probes, and planar reflectors affect the look but are set outside `EnvironmentSettings`, so a preset does not turn them on. - **Anti-aliasing** is a `Scene` field. The default `AntiAliasingMode.auto` already picks `msaa` where the backend supports it and `fxaa` otherwise, so edges are handled. Reach for `scene.antiAliasingMode = AntiAliasingMode.smaa` when `fxaa` looks mushy (it blurs texture detail) and `msaa` is unavailable; SMAA keeps edges clean without the blur, at ~3x fxaa cost. - **Reflection probes** capture true local reflections that SSR cannot, because SSR only reflects what is on screen. Attach a `ReflectionProbeComponent` to a node placed at the reflective spot (a room, a mirror ball); it captures the surroundings into a parallax-corrected box and blends with the environment. The capture renders the scene six times, so it happens once on activate (or on an explicit `requestCapture()`), never per frame. - **Planar reflectors** render a true per-frame mirror for one flat surface (a mirror, a glossy floor), which neither SSR nor a probe can produce. Attach a `PlanarReflectorComponent` to the mirror node and give the surface a `.fmat` material declaring `engine_inputs: [ planar_reflection ]`; the component renders the scene once more per frame from the reflected camera and the material samples it with `GetPlanarReflection()`. That extra scene render is the cost, so bound it with `resolutionScale` (default 0.5) and `layerMask`. See `references/looks.md` for all three. ## Cost The post stack is not free. AO, SSR, depth of field, and god rays each add screen-space passes, and mobile and web are the budget. Keep `ambientOcclusionHalfResolution: true`, prefer `DepthOfFieldQuality.low` on mobile, and do not stack SSR plus god rays plus depth of field on a low-end target without profiling. The `clean` look is nearly free; `moody` is the heaviest. See `references/looks.md` for the full per-effect knob reference, defaults, and cost notes.