Skip to content

Commit c95e0ce

Browse files
TMHSDigitalclaude
andcommitted
fix(examples): redesign bake-normal-high-to-low as a map, source and LOD triptych
The still showed only the baked map card and the LOD wearing it, so the thumbnail never showed what the bake was transferred *from*: two flat squares. The render now stages the pipeline left to right. First the unlit baked map card, then the high-poly source plate the check bakes from (previously hidden), then the decimated LOD wearing the map. Floor captions carry live triangle counts read from the evaluated meshes ("HIGH 3200 tris", "LOW 900 tris + map"). Source and LOD share one cast-bronze material (metallic, noise-mottled roughness), so geometry versus map is the only difference. A raking key from low on the left makes both reliefs throw light and shade. The map card's emission is trimmed 1.0 -> 0.8. Render path only; the bake, the statistics and every check are unchanged. Evidence (live runs, local): - .scratch/blender-5.2.1-windows-x64/blender.exe (Blender 5.2.1 LTS): check exit 0, lod_tris 3200 -> 900, detail frac=0.7211 mad=0.09356, flat frac=0.0000 mad=0.00277. --flat-source exits 5 ("detail frac 0.0000 < 0.4"). --output: framing fill x=0.878, margins l=0.053 r=0.056 b=0.294 t=0.194. - .scratch/blender-4.5.11-windows-x64/blender.exe (Blender 4.5.11 LTS): check plus --output exit 0 with identical bake stats and framing. - Contact sheet: candidate luma 0.286 / stage 0.216 / warmth -0.100 / sat 0.178, against armature-bend 0.243/0.192/-0.144/0.237, damped-track-aim 0.237/0.218/+0.105/0.178, and bmesh-gear 0.236/0.177/+0.303/0.250. Luma runs high and warmth negative because of the emissive lilac map card, which is the subject. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 2efe86b commit c95e0ce

7 files changed

Lines changed: 91 additions & 28 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,7 @@ makes the density drop visible.
913913
</tr>
914914
<tr>
915915
<td width="46%" valign="middle">
916-
<a href="examples/bake-normal-high-to-low/"><img src="examples/bake-normal-high-to-low/preview.webp" alt="Bake normal high to low: an unlit tangent-space normal map card of a six-lobe riveted hatch beside the collapse-decimated bronze plate wearing that map, dark studio" /></a>
916+
<a href="examples/bake-normal-high-to-low/"><img src="examples/bake-normal-high-to-low/preview.webp" alt="Bake normal high to low: an unlit tangent-space normal map card, the 3200-tri ribbed bronze source plate, and the 900-tri collapse-decimated plate wearing that map, captioned on a dark studio floor" /></a>
917917
</td>
918918
<td valign="middle">
919919

13 KB
Loading
2.19 KB
Loading

‎examples/bake-normal-high-to-low/README.md‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,20 @@ cage target; collapse keeps UVs) and [`image-pixels-testcard`](../image-pixels-t
3737
(`save_render`, not `Image.save()`, if you persist the datablock). UV transfer
3838
and atlas packing are out of scope.
3939

40-
The still stages the baked map as an unlit card beside the LOD wearing it.
41-
If the bake were flat, the card would be uniform `(128, 128, 255)` periwinkle
42-
and the plate would shade like the undisplaced cage.
40+
The still reads left to right as the pipeline: the baked map as an unlit
41+
card, the high-poly source it was baked from, and the collapse-decimated
42+
LOD wearing it. Source and LOD share one cast-bronze material, so the only
43+
difference between them is real geometry versus the map. The floor
44+
captions carry live triangle counts from the evaluated meshes (3200 vs
45+
900). A raking key from low on the left makes both the real relief and the
46+
baked relief throw light and shade. If the bake were flat, the card would
47+
be uniform `(128, 128, 255)` periwinkle and the right plate would shade
48+
like the undisplaced cage while the middle one kept its ribs.
4349

4450
Staging is render-only. Each panel stands in a low display plinth, lifted
4551
so its lowest evaluated point sits 35 mm in the slot. Both panels used to
4652
balance on an edge, and the leaning plate pierced the floor. The
47-
solidify rim wears plain bronze instead of the baked map. The slight
53+
LOD's solidify rim wears plain bronze instead of the baked map. The slight
4854
waviness along the plate's border is the bake itself, normal-map shading
4955
inside the UV margin, and is left as the map produces it. Bake
5056
statistics are unchanged: frac 0.7211, MAD 0.09356 on all three binaries.

‎examples/bake-normal-high-to-low/bake_normal_high_to_low.py‎

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,29 @@ def paint_principled(mat, color, metallic, roughness):
222222
return bsdf
223223

224224

225+
BRONZE = (0.62, 0.36, 0.16, 1.0)
226+
227+
228+
def bronze(mat):
229+
"""Cast bronze: metallic, with a noise-mottled roughness so the relief
230+
catches broken highlights instead of one smeared sheen. Render only."""
231+
nt = mat.node_tree
232+
bsdf = paint_principled(mat, BRONZE, 1.0, 0.34)
233+
noise = nt.nodes.new("ShaderNodeTexNoise")
234+
noise.inputs["Scale"].default_value = 18.0
235+
noise.inputs["Detail"].default_value = 6.0
236+
rmap = nt.nodes.new("ShaderNodeMapRange")
237+
rmap.inputs["To Min"].default_value = 0.24
238+
rmap.inputs["To Max"].default_value = 0.48
239+
nt.links.new(noise.outputs["Fac"], rmap.inputs["Value"])
240+
nt.links.new(rmap.outputs["Result"], bsdf.inputs["Roughness"])
241+
return bsdf
242+
243+
225244
def wire_normal_map(mat, tex):
226245
nodes = mat.node_tree.nodes
227246
links = mat.node_tree.links
228-
bsdf = paint_principled(mat, (0.22, 0.13, 0.07, 1.0), 0.58, 0.44)
247+
bsdf = bronze(mat)
229248
nrm = nodes.new("ShaderNodeNormalMap")
230249
nrm.space = "TANGENT"
231250
links.new(tex.outputs["Color"], nrm.inputs["Color"])
@@ -368,7 +387,7 @@ def make_map_card(image, name="BakeCard"):
368387
tex = nodes.new("ShaderNodeTexImage")
369388
tex.image = image
370389
emit = nodes.new("ShaderNodeEmission")
371-
emit.inputs["Strength"].default_value = 1.0
390+
emit.inputs["Strength"].default_value = 0.8
372391
out = nodes.new("ShaderNodeOutputMaterial")
373392
links.new(tex.outputs["Color"], emit.inputs["Color"])
374393
links.new(emit.outputs["Emission"], out.inputs["Surface"])
@@ -427,30 +446,65 @@ def seat_on_plinth(ob, name):
427446
return plinth
428447

429448

430-
def render_still(low, mat, tex, path, engine):
449+
def make_label(text, loc, name):
450+
"""A small text caption lying on the floor in front of a plinth."""
451+
cu = bpy.data.curves.new(name, "FONT")
452+
cu.body = text
453+
cu.size = 0.22
454+
cu.align_x, cu.align_y = "CENTER", "CENTER"
455+
cu.extrude = 0.004
456+
lm = bpy.data.materials.new(name + "Mat")
457+
lm.use_nodes = True
458+
b = paint_principled(lm, (0.62, 0.60, 0.56, 1.0), 0.0, 0.5)
459+
b.inputs["Emission Color"].default_value = (0.9, 0.86, 0.78, 1.0)
460+
b.inputs["Emission Strength"].default_value = 0.35
461+
cu.materials.append(lm)
462+
ob = bpy.data.objects.new(name, cu)
463+
ob.location = loc
464+
ob.rotation_euler = (math.radians(62.0), 0.0, 0.0)
465+
bpy.context.scene.collection.objects.link(ob)
466+
return ob
467+
468+
469+
def render_still(high, low, mat, tex, path, engine):
470+
"""Left to right: the baked tangent map, the high-poly source it was baked
471+
from, and the decimated LOD wearing it. The source and the LOD share one
472+
bronze, so the only difference between them is geometry versus map."""
431473
scene = bpy.context.scene
432474
for ob in list(scene.objects):
433-
if ob.type == "MESH" and ob != low:
475+
if ob.type == "MESH" and ob not in (low, high):
434476
ob.hide_render = True
435477
ob.hide_viewport = True
478+
high_tris = evaluated_triangle_count(high)
479+
low_tris = evaluated_triangle_count(low)
436480

437481
wire_normal_map(mat, tex)
438482
# The solidify rim is the plate's edge, not baked surface: give it plain
439483
# bronze. Wearing the map, it sampled the UV border and rendered ragged.
440484
rim = bpy.data.materials.new("BronzeRim")
441485
rim.use_nodes = True
442-
paint_principled(rim, (0.22, 0.13, 0.07, 1.0), 0.58, 0.44)
486+
bronze(rim)
443487
low.data.materials.append(rim)
444-
solid = low.modifiers.new("SolidifyDisplay", "SOLIDIFY")
445-
solid.thickness = THICKNESS
446-
solid.offset = 1.0
447-
solid.material_offset_rim = 1
448-
low.rotation_euler.x = math.radians(72.0)
449-
low.location = (1.20, 0.0, 1.05)
488+
src = bpy.data.materials.new("BronzeSource")
489+
src.use_nodes = True
490+
bronze(src)
491+
high.data.materials.clear()
492+
high.data.materials.append(src)
493+
for ob, x in ((high, 0.35), (low, 3.05)):
494+
solid = ob.modifiers.new("SolidifyDisplay", "SOLIDIFY")
495+
solid.thickness = THICKNESS
496+
solid.offset = 1.0
497+
solid.material_offset_rim = 1 if ob is low else 0
498+
ob.rotation_euler.x = math.radians(72.0)
499+
ob.location = (x, 0.0, 1.05)
450500
card = make_map_card(tex.image)
451-
card.location = (-1.50, 0.0, 1.05)
501+
card.location = (-2.45, 0.0, 1.05)
452502
plinths = [seat_on_plinth(ob, name) for ob, name in
453-
((card, "CardPlinth"), (low, "PlatePlinth"))]
503+
((card, "CardPlinth"), (high, "SourcePlinth"), (low, "PlatePlinth"))]
504+
labels = [make_label(t, (x, -1.05, 0.13), f"Label{i}") for i, (t, x) in enumerate((
505+
("BAKED MAP", -2.45),
506+
(f"HIGH {high_tris} tris", 0.35),
507+
(f"LOW {low_tris} tris + map", 3.05)))]
454508

455509
floor_me = bpy.data.meshes.new("Floor")
456510
bm = bmesh.new()
@@ -492,18 +546,20 @@ def light(name, loc, energy, size, col, rot):
492546
ob.rotation_euler = tuple(math.radians(a) for a in rot)
493547
scene.collection.objects.link(ob)
494548

495-
light("Key", (-4.0, -5.0, 6.0), 600.0, 4.5, (1.0, 0.96, 0.9), (48, 0, -38))
496-
light("Fill", (5.0, -4.0, 3.0), 110.0, 9.0, (0.75, 0.85, 1.0), (62, 0, 50))
497-
light("Rim", (0.5, 4.5, 5.0), 350.0, 4.0, (0.6, 0.78, 1.0), (-55, 0, 175))
498-
light("Wedge", (2.5, 3.5, 4.2), 480.0, 6.0, (1.0, 0.76, 0.5), (-72, 0, 195))
549+
# A raking key from low on the left, so the relief on the source and the
550+
# baked relief on the LOD both throw light and shade across the ribs.
551+
light("Key", (-6.5, -3.5, 2.6), 900.0, 3.0, (1.0, 0.94, 0.86), (72, 0, -62))
552+
light("Fill", (5.0, -4.0, 3.0), 90.0, 9.0, (0.75, 0.85, 1.0), (62, 0, 50))
553+
light("Rim", (0.5, 4.5, 5.0), 300.0, 4.0, (0.6, 0.78, 1.0), (-55, 0, 175))
554+
light("Wedge", (2.5, 3.5, 4.2), 520.0, 6.0, (1.0, 0.72, 0.45), (-72, 0, 195))
499555

500556
cam_data = bpy.data.cameras.new("Cam")
501557
cam_data.lens = 50.0
502558
cam = bpy.data.objects.new("Cam", cam_data)
503-
cam.location = (0.0, -8.4, 3.15)
559+
cam.location = (0.42, -12.2, 3.4)
504560
scene.collection.objects.link(cam)
505561
aim = bpy.data.objects.new("Aim", None)
506-
aim.location = (0.0, 0.0, 1.05)
562+
aim.location = (0.42, 0.0, 0.78)
507563
scene.collection.objects.link(aim)
508564
con = cam.constraints.new("TRACK_TO")
509565
con.target = aim
@@ -529,8 +585,8 @@ def light(name, loc, energy, size, col, rot):
529585
fcode = gallery_framing.check_framing(
530586
scene,
531587
cam,
532-
hero=[card, low],
533-
elements=[card, low] + plinths,
588+
hero=[card, high, low],
589+
elements=[card, high, low] + plinths + labels,
534590
stage=[floor, wall],
535591
)
536592
if fcode:
@@ -567,7 +623,8 @@ def main():
567623
tex = next(
568624
n for n in mat.node_tree.nodes if n.type == "TEX_IMAGE" and n.image == img
569625
)
570-
rcode = render_still(low, mat, tex, os.path.abspath(args.output), args.engine)
626+
rcode = render_still(high, low, mat, tex, os.path.abspath(args.output),
627+
args.engine)
571628
if rcode:
572629
return rcode
573630
print(f"rendered still {args.output}")
11.9 KB
Loading

‎examples/gallery.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@
682682
"name": "bake-normal-high-to-low",
683683
"dir": "examples/bake-normal-high-to-low",
684684
"teaches": "A collapse-decimated hatch plate receiving a Cycles cage-baked tangent normal map from a ribbed high-poly source",
685-
"alt": "A baked tangent-space normal map shown as a lilac card beside a bronze plate wearing it, with a radial embossed pattern, both standing in low display plinths.",
685+
"alt": "Left to right: a lilac baked normal-map card, the 3200-tri ribbed bronze source plate, and the 900-tri decimated plate wearing the map, each captioned on the floor.",
686686
"witnessesFix": "Statistical gates not byte-identity: detail frac 0.7211 / MAD 0.09356 vs flat 0.0000 / 0.00277; --flat-source exits 5; type=NORMAL not bake_type; RNA identical on 4.5.11, 5.1.2, 5.2.1",
687687
"hero": "docs/gallery/assets/bake-normal-high-to-low-hero.webp",
688688
"preview": "examples/bake-normal-high-to-low/preview.webp",

0 commit comments

Comments
 (0)