executing-plans
executing-plans
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available
scenario-blender-lighting-renderingUse when lighting, rendering or compositing in Blender: light a character, product or hero shot, interior at dusk or night, three-point or motivated lighting, sun and sky, HDRI, EEVEE vs Cycles settings, light leaks or noise, flat or muddy renders, subject not standing out, AgX/ACES color management, render passes, EXR, light groups, bloom and vignette in t…
Independent GitHub snapshot · Automated classification · Original documentation governs
Expert lighting controls where the eye goes. Shape comes from gradients, the subject is separated from its surroundings by value (checked desaturated), every light has a source and a purpose, and every decision is measured on the render, not on lamp values. Work from a controlled base, change one thing at a time, and compare. If a sibling skill named here is missing from your available skills, ask the user to install it (npx skills add scenario-labs/skills --skill <name>); unattended, proceed from tool schemas and flag the gap.
REQUIRED BACKGROUND: scenario-blender-expert (execution channel, review loop, 5.2 API changes). Related: scenario-blender-texturing-shading (materials, skin SSS), scenario-blender-previs-storyboard (shot planning, cameras).
Toolkit: scripts/bx_light.py (tested on 5.2.1): rigs placed relative to subject and camera, EEVEE/Cycles presets, color setup and audit, 5.x compositor helpers, render analysis. Import: sys.path.append("<this skill>/scripts"); import bx_light as BL.
exposure in the view transform, not scaled lights (keeps every ratio, including world and emissives). AgX for scenes, Khronos PBR Neutral for product color. Set the working space once (Nathan Vegdahl).BL.light_groups, BL.lightgroup_mix). EEVEE has no light-group outputs: use light linking and view layers.| Input | Why it changes the plan | Default when silent |
|---|---|---|
| Still or animation | Continuity, flicker and temporal denoise | Still |
| Engine | EEVEE: stylized, fast iteration, volumes, NPR, viewport comp with passes. Cycles: glass close-ups, bounce realism, reference | EEVEE to iterate, plus a Cycles reference render |
| Style | Toon terminators need a small, unjittered key | Stylized-realistic |
| Subject and focus point | Rig target (the face), mask for the analysis | Character head, else bbox center |
| Time and motivation | Color scheme: warm practicals, cool sky, sun | Night/dusk interior: warm key (2,700 K [added]), cool fill |
| Delivery | PNG display plus EXR master; external grade? | PNG + 32-bit multilayer EXR |
| Color fidelity | Brand RGB: Khronos PBR Neutral; skin: ACES 2.0 or AgX High Contrast | AgX |
| Late balance changes, comp hand-off | Cycles light groups per role, multilayer EXR | groups when rendering in Cycles |
BL.screen_xy and BL.aim_camera_at_frame_point(head, 1/3 or 2/3, 0.62) for look room, and 80 mm for boxy products. GATE: one focal element; subject near a thirds point or a deliberate symmetry, pyramid or full frame.pb = BL.volume_probe_for_room(inner_lo, inner_hi, cell=0.5, wall=0.2) (outer samples on the inner walls), BL.probe_report(pb, inner_lo, inner_hi), then BL.bake_probes(). For an exterior: BL.outdoor_sun_sky(..., azimuth_space="world"). GATE: probe_report flags nothing and a world-only render of a sealed interior is near black.BL.three_point, BL.motivated_interior (window + practical, cheated linked key, spot practical, rims, bounce) or BL.outdoor_sun_sky. Lights stay out of walls automatically. GATE: BL.light_report(target=head, face=head) has no flags (frontal key, key below the eye line, key smaller than the head), and a clay render (BL.clay(True)) gives internal_ratio >= 2.5.BL.eye_light, plus BL.shadow_caster, BL.fake_sun, BL.split_world or BL.haze as needed. Linking lives on the light OBJECT (obj.light_linking, via BL.link_light), not the Light datablock. Adjust one lamp at a time: BL.light_contributions(lights, mask, dir), or in Cycles BL.lightgroup_check(exr, mask) from one render. GATE: the key has the largest share on the subject, the world is under 10 % indoors, and each rim is motivated.r = BL.render(base) gives PNG + EXR. Then mask = BL.mask_cryptomatte(r["exr"], names) (or BL.mask_workbench), rep = BL.analyse(r["png"], mask, exr=r["exr"]), BL.verdict(rep) and BL.value_study(...), and open the images. GATE: the verdict is empty and in the value study the subject owns a value group; otherwise darken the set, carve with spots or blockers, and link the key.BL.color_setup(view, look, exposure) (brightness here, never by scaling lamps) and BL.color_audit(). GATE: the audit is empty, no complementary clash is flagged, clip is at most 0.5 % outside emitters, and the linear max is intended.BL.preset_eevee(quality="final", interior=...) or BL.preset_cycles(quality="final", budget="film"). For EEVEE finals, render a Cycles preview reference (Andrew). GATE: the subject is within about 30 % of Cycles and there are no engine artifacts (critique section 7).mix = BL.lightgroup_mix(gains, exr=r["exr"]) re-grades key/fill/rim/window from the EXR in about 0.2 s (BL.set_gain, RGB gains recolor). Then BL.comp_finish(bloom=0.25, vignette=0.3, contrast=0.05, source=mix["socket"]); masks with BL.matte(tree, names); pixel sizes through BL.relative_px; passes, back to beauty and view layers are in procedures P13. GATE: re-run the verdict on the finished frame; the vignette ratio should fall.Animation: pin the sun per location with sun_azimuth within about 30 deg (Andy). Light the wide shot and propagate it to coverage. Check shots with BL.contact_sheet and BL.sheet_stats (outliers more than 0.5 stop from the median). Keep temporal denoise on and never use ray-tracing resolution 1/16.
| Value | Relative to | Source |
|---|---|---|
| Level of a white card: point P/(4π²d²), area P/(π²d²), sun S/π | BL.energy_for | measured, both engines |
| Source as big as its distance delivers 75 % of the nominal level | apparent size | measured |
| AgX display: 0.18 to 0.463, 1.0 to 0.773, 4.0 to 0.910 | linear value | measured |
| Key 40 deg off axis, 35 deg up, 3 subject radii, size = radius | subject, camera | [added] defaults |
| Frontal flag: key < 20 deg off the camera axis | camera axis | [added] |
| Sun angle about 2 deg; continuity ±30 deg | set | Andy |
| Kelvin: candle 1,500, incandescent 2,000 to 2,500, daylight 6,500 (practical default 2,700 [added]) | light | Andrew |
| EEVEE: RT 1:1 and GI 1:2 for finals, Fast GI steps 16, shadow steps 12 to 16 (leaks), shadow rays 2 (penumbra) | render | Andrew |
| Volume probe: outer samples on the inner walls (samples at local (i+1)/(N+1)x2-1); leak 0.22 at 0.44 m inside, 0.054 at 0.2 m, 0.002 at the wall, 0.009 past the wall; surfel density 100 | room | Andrew, measured |
| Brand color error (of 255): Khronos 3.8, AgX 27 to 42, Standard 33 | white furnace | measured |
| Haze density about 0.05 for a room | volume | Andrew |
| Cycles film: 3 bounces, clamp 0, no caustics, 100 test / 500 to 1,500 final samples | render | Andy |
| EXR: multilayer, half float, DWAA for comp; 32-bit masters | output | Robin, Andy |
| Verdict: surround separation ≥ 0.10, edge merge ≤ 35 %, clay form ratio ≥ 2.5, value range ≥ 0.30, clip ≤ 0.5 % | display luma | [added], calibrated |
Measurable:
rep = BL.analyse(png, mask, exr=exr); problems = BL.verdict(rep) # [] = pass
flags = BL.light_report(target=head, face=head)["flags"]; audit = BL.color_audit()
probe = BL.probe_report(pb, inner_lo, inner_hi)["flags"] # EEVEE interiors: []
groups = BL.lightgroup_check(exr, mask) # Cycles: rel_error ~0, shares
shares = BL.light_contributions(lights, mask, out_dir) # per-light share on subject
Visual: open the render, the value_study panel (desaturated | value groups | squint) and, for sequences, the contact sheet. Judge them with references/critique.md: first read, value structure, form, motivation, color, composition, artifacts, finish.
| Mistake | What it looks like | Fix |
|---|---|---|
| Frontal key | Flat, "flash photo", clay form ratio about 1.3 | Key 30 to 60 deg off axis, above the eye line |
| Practical behind the subject used as the key | Dull face, bright wall steals focus | Cheated key on the source's side, linked to the subject |
| Bare point practical near a wall | Blown wall, shelf becomes the focal point | Spot with the shade's cone; blocker |
| EEVEE interior without probe, or a probe box drawn to the walls | Shadows lifted blue/gray everywhere, muddy | Outer samples at the inner walls (probe_report), bake, sealed-room test |
| Small bulb as a face key | Pores and texture, harsh face | Large source (head-sized or more); flag in light_report(face=) |
| Brightening by scaling lamps | World and emissives left behind, ratios drift | exposure in the view transform |
light.data.light_linking | AttributeError | Linking and light groups are Object properties |
| Re-rendering to rebalance lights | Hours per tweak | Cycles light groups + lightgroup_mix |
| Rig lights inside walls | A rim that contributes nothing | avoid=subject (default in rigs) |
| Unlinked faked key | Set brightens with the subject, no separation | link_key=True; key share high, background share near 0 |
| Judging clipping on AgX PNG | Highlights look fine, EXR at 20+ | Read linear_max, use exposure |
| Big soft jittered key on a toon material | Dithered, noisy terminator | Small unjittered key for NPR (Wyatt: crisp shadows for toon) |
| Inverted hull outline | Character goes dark or flat | Outline material transparent to shadow rays (outline_hull) |
| AOV named in shader only | Pass silently missing | BL.register_aovs() |
| Pixel-sized comp effects | Look changes between preview and final | BL.relative_px |
| Exclude instead of indirect-only in layers | Missing shadows in the comp | indirect_only (0.0004 vs 0.016 diff) |
BLENDER_EEVEE; use_raytracing is False by default; use_bloom/ssr/gtao/soft_shadows are gone (Glare node, ray tracing, Fast GI, per-light jitter).SPHERE/PLANE/VOLUME; lightprobe_cache_bake runs synchronously headless; the sphere-probe default is 512. Volume samples sit one spacing inside the box faces, never on them.Combined_<group>); light linking works in both engines. light_linking and lightgroup are Object properties; bpy.types.Light has neither.scene.compositing_node_group, Group Output, ShaderNodeMix (color sockets 6/7, result 2), menu inputs take display names ('Bloom'). GPU is the default device. Assets live in datafiles/assets/nodes/compositing_nodes_essentials.blend.bpy.ops.wm.set_working_color_space); looks are prefixed ('AgX - Punchy'); 'Linear'/'Raw' are rejected; camera log spaces exist; Principled Thin Wall exists for window glass.matrix_world is stale after setting a transform; call view_layer.update(). OIIO read_image(subimage, ...): the first argument selects the part.references/procedures.md: tested code for every stage (P1 units to P17 exposure and color), with measured results and test paths. Load before writing code.references/critique.md: the rubric to judge a render or sequence, in expert reading order, with the code check behind each question.references/expert-notes.md: principles per expert with timestamps, plus the disagreements table. Load for judgment calls and attribution.references/sources.md: the 15 videos, credentials, best timestamps, and which to consult for which question.