Files
Papay-Shooter/docs/GRAPHICAL_FIDELITY.md
2026-08-02 02:20:02 -04:00

12 KiB
Raw Permalink Blame History

Graphical Fidelity Direction

Primary reference: Sakura Crossing (MIT) — a procedural Japanese suburb in three.js, rendered to look like a hand-painted 2D anime background. This is the target image for the project, with one deliberate departure: the line work is slightly harder and thicker.

Secondary reference: The Long Silence. The transferable lesson there is not its realistic-space rendering. It is that one authored visual law is reused everywhere, expensive detail is paid for only where it can be seen, and every art decision is checked in repeatable captures.

Character-specific decisions are governed by CHARACTER_ART_DIRECTION.md: 90% urban anime character read, 10% selectively deployed utility technology.

The cel law

Four layered techniques, ported from the reference. scenes/maps/sakura_crossing is the map built to demonstrate all four; scenes/maps/sakura_palette.gd is the palette port.

1. Quantised toon lighting with a hue-shifted shadowassets/shaders/sakura_cel.gdshader. Direct light is snapped to one of 25 flat values from a hand-authored ramp, sampled at dot(N,L) * 0.5 + 0.5. Then:

irradiance = band * mix(shadow_tint, vec3(1.0), band)

The band drives its own tint, so full light is untinted and the darkest band is almost pure cool violet. That hue shift in shadow is most of what separates "anime cel" from "low-poly 3D" — a wall whose shade is merely 40% of its lit colour reads as an unlit polygon. High-key ramps (RAMP_SOFT, RAMP_SOFT3) exist for pale masses like blossom that must stay light on the shadow side.

Faceted shading is on by default and is not a shortcut: a quantised ramp over smooth normals puts a curved band boundary across a curved surface, which reads as an airbrushed gradient no matter how few steps it has.

2. Screen-space ink from the SECOND difference of linear depthassets/shaders/ink_edge.gdshader. This replaced a conventional first-difference edge detect, and the distinction is the single most important one in the file. A first difference measures depth slope, which is large wherever a surface is oblique to the camera — so the road, every roof and every long wall carried ink, and the threshold had to be raised until it smeared. A second difference measures curvature, which is exactly zero across any planar surface however oblique. It therefore fires only on real silhouettes and real creases, and the threshold can be low enough to catch a distant railing.

Convex curvature inks strongly, concave faintly — the outside line of a shape is heavy, the contact line inside it is light, which is what an animator draws.

Two things to know when tuning it: ink_hardness also sets the noise floor, because a grazing ground plane quantises in the depth buffer into a staircase and a staircase has a large second difference at every step. Too soft and the road comes out finely hatched. And anything smaller than the line width — petals, distant wires — will be inked solid, so sub-pixel scatter belongs in the transparent pass where the ink cannot see it.

3. Inverted-hull outlines on hero props only — toon_outline.gdshader, applied via LevelMaterials.outline().

4. A four-light anime rigLevelEnvironment._build_light_rig. A warm quantised key (the only shadow caster), a strong cool bounce from the opposite quarter at roughly a third of the key, a weak violet up-light for undersides, and a violet-ground hemisphere carried by ambient. The bounce is the important one: it puts a distinct, cool, non-black value on every shadow plane, so a surface turning away from the sun turns into another colour rather than into darkness.

The colour grade is a split-tone — cool violet into the darks, warm paper white into the lights — and it lives at the end of the ink pass rather than in Environment.adjustment, so it can be a split rather than a single saturation number. Environment's adjustment stage is therefore disabled; enabling both grades the frame twice.

Lettering and trim

The reference is emphatic that "every sign, fascia, lantern and price strip is drawn at runtime — there is not one image asset in src/." That is not a technical footnote. A Japanese street is made of lettering, and the first build of Sakura Crossing had correct forms, correct colour and no type on it anywhere; it read as an architectural massing study.

scenes/maps/sakura_signage.gd is the answer. Godot's equivalent of the reference's Canvas2D is TextMesh — real geometry from a font — which suits this renderer better than a drawn texture would, because the whole map is untextured flat colour and, more importantly, geometry gets inked. Every glyph picks up the screen-space outline exactly as the buildings do, which is what seats the lettering in the picture rather than on top of it.

Settings that matter, both measured:

  • depth = 0. Extruded text is 4 500 verts for four kanji; flat is 2 100. The extrusion's side walls are most of the mesh and are never seen on a sign fixed to a wall. Flat glyphs sit a centimetre proud of their board instead.
  • curve_step = 8. The 0.5 default is 43 000 verts for three signs; 8 is 13 700 and looks identical at any distance a sign is read from.

The font is a system stack — the same one the reference uses (Yu Gothic, Meiryo, Hiragino Kaku Gothic ProN, MS Gothic). Ships no font file; if none resolve, signs go blank and nothing crashes.

Ink or no ink is an art decision, per object. Raised lettering on a sign board wants the outline — it is an object standing off a wall. Paint on the ground does not: road markings were inked at first and came out hollow, because a stroke of 止まれ at a grazing angle is a few pixels wide, the ink fired on both edges and the two outlines met in the middle and ate the letter. Pass inked: false and the glyphs go to the transparent pass where the ink cannot see them. Same mechanism as the petals — anything thinner than the line width gets inked solid.

Trim is the other half. A cel image is judged on its third tier of structure, and the first build had almost none: no sills, no downpipes, no meters, no manholes, no tactile dots. All of it is render-only and most of it is a box — none of it needs to be clever, it needs to be there.

One layout lesson worth keeping: unit width is the character of a street. Eight shop units across this map made each one 19.5 m wide, which is a department store, not a shop — one awning and one noren stretched across sixty feet of frontage. Six-to-ten-metre units give twenty awning colours, twenty names and twenty doorways per side, and that rhythm is most of what a 商店街 is.

Cost: ~5 300 mesh instances, 6.85 ms/frame at 1080p in the heaviest view.

Image contract

Every gameplay frame should have:

  • A readable dark, mid and light band with less than 2% practical clipping.
  • One dominant warm/cool lighting relationship, not white ambient everywhere.
  • Dark contact at feet, props and building bases.
  • Large, medium and small visual structure, in that order.
  • Ink weight that follows importance: character silhouette, rigid accessory, architecture crease, then tertiary texture.
  • Quiet traversal space around loud signs, characters and combat VFX.
  • Surface detail that disappears cleanly before it becomes sub-pixel noise.

The current automatic guardrail is:

python tools/levels.py <capture-directory> --inset 8 --gate

debug/fidelity_probe.gd is the deterministic look-development scene. debug/visual_capture.gd remains the real-game regression capture.

Lighting and post

  • Keep the sun as the form key and ambient as a low-energy colour bed.
  • Use tight SSAO only for contact. Broad grey AO fights flat cel bands.
  • Keep cast shadow and form shadow separate; cast shadows may be deeper and harder than a character or prop's terminator.
  • Let only authored emissives cross the glow threshold. White paint must never bloom.
  • Preserve a filmic shoulder and measure clipping instead of judging it from a single monitor.
  • The first-person weapon uses a camera-relative warm key and cool fill. Its graphic read must not change when the player turns in world space.

Texturing and materials

LevelMaterials.SURFACE_LAW is the shared construction language:

  • wall: storey-scale seams plus a restrained base grade.
  • ground: larger paving divisions with no vertical grade.
  • panel: tight machined divisions for metal and kiosks.
  • trim: grounding grade without a grid.

These are semantic materials, not colour presets. Do not apply one noise field to every asset. Foliage, vehicles and character silhouettes stay quiet unless their own authored texture calls for detail.

The next texture pass should add a small reusable decal sheet for:

  • one or two edge chips;
  • drain and service markings;
  • poster tears and tape corners;
  • sparse hazard chevrons;
  • directional pavement paint.

Place decals by composition and gameplay route. Random coverage is not detail; it is visual noise.

Modeling

Build and review assets in three reads:

  1. Silhouette at combat distance.
  2. Primary planes and construction breaks at traversal distance.
  3. Fasteners, bevels and decals only at inspection distance.

For the city kit:

  • Give rooflines and street corners deliberate silhouette interruptions.
  • Prefer modeled awnings, frames, vents and cable bundles over high-frequency material noise.
  • Reuse a small vocabulary of construction dimensions so adjacent modules feel manufactured together.
  • Merge static pieces by material only after their panel coordinates are stable; seams should continue across parts meant to read as one assembly.
  • Add LOD or visibility ranges before adding tertiary geometry across all 100 city blocks.

Character and weapon animation

Anime fidelity comes from pose design and timing more than interpolation count:

  • Let the body lead every movement verb. Jump jets, grapple hardware and dash thrusters deploy only long enough to explain the motion, then return visual focus to the character.
  • Gameplay anticipation must be short but unmistakable.
  • Key combat silhouettes need a readable held pose before recovery.
  • Accelerate through impact, hold the strongest pose, then use a clean recovery arc instead of uniformly smooth motion.
  • Keep weapon, support hand and head intent aligned; the existing hold-profile checks should remain mandatory.
  • Judge locomotion from the gameplay camera, not only in an animation viewer.
  • Use debug/anim_capture.gd, debug/fp_weapon_capture.gd and the hold/pose checks whenever animation assets change.

Secondary cloth and hair should lag the torso and settle quickly enough not to obscure aim or team recognition. Their job is to sell acceleration, not to run an independent simulation showcase.

VFX

  • Reserve the brightest values for hits, muzzle flashes and abilities.
  • Shape effects as graphic wedges, rings and speed lines before adding soft particles.
  • Use colour to communicate gameplay class consistently.
  • Fade detail by projected size. A distant effect should become one clean spark, not many sub-pixel particles.
  • Keep screen-space distortion local and brief; the HUD and target silhouette must remain readable.

Performance and review

Fidelity features should degrade by resolution or distance before being disabled outright. Panel seams and sky clouds are analytic and band-limited; future decals and tertiary geometry should use visibility ranges.

Review sets should include:

  • deterministic fidelity probe, key and grazing angles;
  • Neon Alley street, spawn and aerial views;
  • one bright and one dark character skin;
  • each first-person weapon family;
  • run, slide, dash, reload and one ability impact;
  • a low render-scale capture to expose shimmer and sub-pixel detail.

Compare the set as a group. A change is not an improvement if one hero frame is better while combat readability or another map regresses.