255 lines
12 KiB
Markdown
255 lines
12 KiB
Markdown
# Graphical Fidelity Direction
|
||
|
||
**Primary reference: [Sakura Crossing](https://github.com/Kenton-GMI/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](https://github.com/achimala/TheLongSilence).
|
||
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](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 shadow** —
|
||
`assets/shaders/sakura_cel.gdshader`. Direct light is snapped to one of 2–5 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 depth** —
|
||
`assets/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 rig** — `LevelEnvironment._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:
|
||
|
||
```powershell
|
||
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.
|