AI-driven head rework: judge picks the decimated MakeHuman head over the displacement shell
This commit is contained in:
1 parent
30f240b591
commit
632ec16aaa
3 files changed
+63
-43
No files matched your search
@@ -80,16 +80,18 @@ the rest of §C remains the broad review checklist.
|
|||||||
|
|
||||||
### 3. Head and face prototype
|
### 3. Head and face prototype
|
||||||
|
|
||||||
|
### 3. Head and face prototype
|
||||||
|
|
||||||
- [x] Build one `npc_casual` head/face prototype as a connected head shell plus intentional feature
|
- [x] Build one `npc_casual` head/face prototype as a connected head shell plus intentional feature
|
||||||
geometry — not a sphere with floating facial balls. Use loop-based eye sockets/lids, a
|
geometry — not a sphere with floating facial balls. Use loop-based eye sockets/lids, a
|
||||||
readable nose bridge/tip/ala, a mouth with upper/lower lip volumes, ears with concha depth,
|
readable nose bridge/tip/ala, a mouth with upper/lower lip volumes, ears with concha depth,
|
||||||
and brow/cheek/jaw planes that survive the 256² AO bake.
|
and brow/cheek/jaw planes that survive the 256² AO bake.
|
||||||
`tools/npc_parts.py build --part head_proto` displaces a seg-48 shell into eye sockets with
|
`tools/npc_parts.py build --part head_proto` built the displacement shell (sockets, nose,
|
||||||
an orbital rim, a nose bridge/tip/ala, upper/lower lips, a chin bump, a brow step, a sloped
|
lips, chin, brow, ears) but the AI-judged rework (concept + judge, see
|
||||||
forehead and extruded ears (helix + concha + lobe); the eyeballs stay separate. Objective
|
`docs/3d-model-review.md` §AI-driven head rework) shows it oscillates between "egg" and
|
||||||
centre-profile evidence: nose tip −0.192, brow −0.150, chin −0.149, jaw step −0.11, forehead
|
"spiky" at ~3/5. The judge's decisive result: **ship the decimated MakeHuman head**
|
||||||
−0.126→−0.078. Vision-judged ~3/5; remaining polish (forehead/ear/jawline read) is folded
|
(1,573 tris, nose/chin/jaw landmarks) plus the project's separate eye/mouth/brow pieces. The
|
||||||
into §3.2 comparison.
|
displacement `_head_proto` is a rejected variant; the MakeHuman head is the new base.
|
||||||
- [x] Compare two or three subdivision/loop variants at 1.85 m front/three-quarter/profile and a
|
- [x] Compare two or three subdivision/loop variants at 1.85 m front/three-quarter/profile and a
|
||||||
1.5 m portrait. Pick the variant that reads best before changing all 12 characters; keep the
|
1.5 m portrait. Pick the variant that reads best before changing all 12 characters; keep the
|
||||||
rejected variants local.
|
rejected variants local.
|
||||||
|
|||||||
@@ -232,6 +232,24 @@ brow −0.150, chin −0.149, jaw step −0.11, forehead −0.126→−0.078. Th
|
|||||||
forehead, a defined ear helix/lobe, a jawline that steps to the neck) is folded into the §3.2
|
forehead, a defined ear helix/lobe, a jawline that steps to the neck) is folded into the §3.2
|
||||||
variant comparison; the neck is part of `_skin_body`, so the standalone head always lacks it.
|
variant comparison; the neck is part of `_skin_body`, so the standalone head always lacks it.
|
||||||
|
|
||||||
|
### AI-driven head rework (2026-11-04)
|
||||||
|
|
||||||
|
The head was reworked against an AI-generated target concept (front + profile, saved under
|
||||||
|
`tmp/npc-parts/head_concept_*.png`) and judged with `describe_image.py` across several iterations.
|
||||||
|
The displacement-on-sphere approach oscillates between "decorated egg" (too subtle) and
|
||||||
|
"spiky wedges" (too aggressive) and settles at ~3/5 — the coarse s48 sphere cannot resolve the
|
||||||
|
nose/lip/chin relief cleanly.
|
||||||
|
|
||||||
|
The judge then compared the displacement head against the **decimated MakeHuman head**
|
||||||
|
(extracted from `makehuman_retargeted.glb`, 2,009 verts / 1,573 tris, saved as
|
||||||
|
`tmp/npc-parts/makehuman_head.glb`) and returned a decisive result: **ship the MakeHuman head** —
|
||||||
|
it keeps the nose/chin/jaw landmarks at a fraction of the cost, while the procedural head's
|
||||||
|
"clean ellipse outline instantly screams procedural sphere".
|
||||||
|
|
||||||
|
**Conclusion:** the next head asset is the MakeHuman head shape (1,573 tris) plus the project's
|
||||||
|
separate eye/mouth/brow pieces for readability, not the displacement shell. The displacement
|
||||||
|
`_head_proto` stays local as a rejected variant.
|
||||||
|
|
||||||
### Head subdivision comparison (2026-11-04)
|
### Head subdivision comparison (2026-11-04)
|
||||||
|
|
||||||
Three `head_proto` variants at UV-sphere segments 24/48/64 were rendered at 1.85 m and 1.5 m in
|
Three `head_proto` variants at UV-sphere segments 24/48/64 were rendered at 1.85 m and 1.5 m in
|
||||||
|
|||||||
+37
-37
@@ -869,7 +869,7 @@ def _head_proto(mats, spec, subdiv=48):
|
|||||||
white = material("eye_white", textured_solid=True)
|
white = material("eye_white", textured_solid=True)
|
||||||
rings = max(14, subdiv // 2 + 2)
|
rings = max(14, subdiv // 2 + 2)
|
||||||
head = sphere("HeadShell", 0.155, (0, 0, 1.635), skin, seg=subdiv, rings=rings,
|
head = sphere("HeadShell", 0.155, (0, 0, 1.635), skin, seg=subdiv, rings=rings,
|
||||||
scale=(0.90, 0.96, 1.08))
|
scale=(0.88, 0.95, 1.18))
|
||||||
_shape_head_mesh(head) # brow ridge + cheekbone + jaw/chin planes
|
_shape_head_mesh(head) # brow ridge + cheekbone + jaw/chin planes
|
||||||
bm = bmesh.new()
|
bm = bmesh.new()
|
||||||
bm.from_mesh(head.data)
|
bm.from_mesh(head.data)
|
||||||
@@ -877,10 +877,10 @@ def _head_proto(mats, spec, subdiv=48):
|
|||||||
# Pass 1: skull shape — flatten the face plane and taper the jaw/chin.
|
# Pass 1: skull shape — flatten the face plane and taper the jaw/chin.
|
||||||
for v in bm.verts:
|
for v in bm.verts:
|
||||||
x, y, z = v.co
|
x, y, z = v.co
|
||||||
if y < -0.03 and 1.44 < z < 1.76:
|
if y < -0.03 and 1.42 < z < 1.80:
|
||||||
v.co.y = -0.13 + (y + 0.13) * 0.10 # flatten the forward bulge
|
v.co.y = -0.13 + (y + 0.13) * 0.05 # flatten the forward bulge
|
||||||
if y < -0.03 and z > 1.70:
|
if y < -0.03 and z > 1.70:
|
||||||
v.co.y += (z - 1.70) * 0.35 # forehead slopes back to the crown
|
v.co.y += (z - 1.70) * 0.45 # forehead slopes back to the crown
|
||||||
if z < 1.58:
|
if z < 1.58:
|
||||||
taper = max(0.0, 1.0 - (1.58 - z) / 0.12)
|
taper = max(0.0, 1.0 - (1.58 - z) / 0.12)
|
||||||
v.co.x *= 1.0 - 0.16 * taper # narrow the lower face
|
v.co.x *= 1.0 - 0.16 * taper # narrow the lower face
|
||||||
@@ -889,11 +889,11 @@ def _head_proto(mats, spec, subdiv=48):
|
|||||||
for v in bm.verts:
|
for v in bm.verts:
|
||||||
x, y, z = v.co
|
x, y, z = v.co
|
||||||
ax = abs(x)
|
ax = abs(x)
|
||||||
# --- brow step ---
|
# --- brow ledge: a clear ridge where the forehead meets the nose ---
|
||||||
brow = (max(0.0, 1.0 - abs(z - 1.695) / 0.022)
|
brow = (max(0.0, 1.0 - abs(z - 1.695) / 0.024)
|
||||||
* max(0.0, 1.0 - abs(ax - 0.050) / 0.045))
|
* max(0.0, 1.0 - abs(ax - 0.050) / 0.045))
|
||||||
if brow > 0:
|
if brow > 0:
|
||||||
v.co.y -= 0.016 * brow
|
v.co.y -= 0.028 * brow
|
||||||
# --- eye sockets: recess the floor, leave a raised orbital rim ---
|
# --- eye sockets: recess the floor, leave a raised orbital rim ---
|
||||||
for s in (-1.0, 1.0):
|
for s in (-1.0, 1.0):
|
||||||
r = math.hypot(x - 0.052 * s, z - 1.664)
|
r = math.hypot(x - 0.052 * s, z - 1.664)
|
||||||
@@ -902,46 +902,46 @@ def _head_proto(mats, spec, subdiv=48):
|
|||||||
elif r < 0.046:
|
elif r < 0.046:
|
||||||
v.co.y -= 0.010 * (1.0 - (r - 0.036) / 0.010)
|
v.co.y -= 0.010 * (1.0 - (r - 0.036) / 0.010)
|
||||||
# --- nose: a wide bridge/tip with ala wings ---
|
# --- nose: a wide bridge/tip with ala wings ---
|
||||||
if y < 0 and ax < 0.075:
|
if y < 0 and ax < 0.080:
|
||||||
band = max(0.0, 1.0 - abs(z - 1.632) / 0.055)
|
band = max(0.0, 1.0 - abs(z - 1.632) / 0.055)
|
||||||
if band > 0:
|
if band > 0:
|
||||||
tip = max(0.0, 1.0 - abs(z - 1.615) / 0.032)
|
tip = max(0.0, 1.0 - abs(z - 1.615) / 0.034)
|
||||||
v.co.y -= (0.060 * tip + 0.018 * max(0.0, band - tip)) \
|
v.co.y -= (0.062 * tip + 0.020 * max(0.0, band - tip)) \
|
||||||
* (1.0 - ax / 0.075)
|
* (1.0 - ax / 0.080)
|
||||||
for s in (-1.0, 1.0):
|
for s in (-1.0, 1.0):
|
||||||
ar = math.hypot((x - 0.024 * s) / 0.014, (z - 1.604) / 0.013)
|
ar = math.hypot((x - 0.026 * s) / 0.015, (z - 1.604) / 0.014)
|
||||||
if ar < 1.0:
|
if ar < 1.0:
|
||||||
v.co.y -= 0.022 * (1.0 - ar)
|
v.co.y -= 0.024 * (1.0 - ar)
|
||||||
# --- chin bump + jaw step ---
|
# --- chin wedge + jaw step ---
|
||||||
if y < 0 and ax < 0.040:
|
if y < 0 and ax < 0.044:
|
||||||
chin = max(0.0, 1.0 - abs(z - 1.548) / 0.022)
|
chin = max(0.0, 1.0 - abs(z - 1.548) / 0.024)
|
||||||
if chin > 0:
|
if chin > 0:
|
||||||
v.co.y -= 0.030 * chin
|
v.co.y -= 0.034 * chin
|
||||||
step = max(0.0, 1.0 - abs(z - 1.516) / 0.016)
|
step = max(0.0, 1.0 - abs(z - 1.516) / 0.016)
|
||||||
if step > 0:
|
if step > 0:
|
||||||
v.co.y += 0.024 * step
|
v.co.y += 0.026 * step
|
||||||
# --- mouth: recessed line + upper/lower lip volumes ---
|
# --- mouth: recessed line + smooth protruding upper/lower lip block ---
|
||||||
if y < 0 and ax < 0.030:
|
if y < 0 and ax < 0.036:
|
||||||
if abs(z - 1.583) < 0.012:
|
if abs(z - 1.583) < 0.014:
|
||||||
v.co.y += 0.008
|
v.co.y += 0.006
|
||||||
if abs(z - 1.590) < 0.008:
|
if abs(z - 1.590) < 0.012:
|
||||||
v.co.y -= 0.014
|
v.co.y -= 0.016
|
||||||
if abs(z - 1.577) < 0.010:
|
if abs(z - 1.577) < 0.014:
|
||||||
v.co.y -= 0.018
|
v.co.y -= 0.020
|
||||||
# --- ears: helix rim + concha recess + a lobe ---
|
# --- ears: raised C-shape helix + concha recess + a lobe ---
|
||||||
for v in bm.verts:
|
for v in bm.verts:
|
||||||
x, y, z = v.co
|
x, y, z = v.co
|
||||||
r = math.hypot((z - 1.60) / 0.055, (y - 0.002) / 0.045)
|
r = math.hypot((z - 1.62) / 0.062, (y - 0.002) / 0.048)
|
||||||
near = abs(x) - 0.138
|
near = abs(x) - 0.138
|
||||||
sign = 1.0 if x >= 0 else -1.0
|
sign = 1.0 if x >= 0 else -1.0
|
||||||
if -0.02 < near < 0.06 and r < 1.0:
|
if -0.02 < near < 0.07 and r < 1.0:
|
||||||
if r < 0.30:
|
if r < 0.36:
|
||||||
v.co.x -= 0.018 * (1.0 - r / 0.30) * sign
|
v.co.x -= 0.016 * (1.0 - r / 0.36) * sign
|
||||||
else:
|
else:
|
||||||
v.co.x += 0.045 * (1.0 - r) * sign
|
v.co.x += 0.048 * (1.0 - r) * sign
|
||||||
lr = math.hypot((z - 1.575) / 0.016, (y + 0.012) / 0.016)
|
lr = math.hypot((z - 1.585) / 0.018, (y + 0.012) / 0.017)
|
||||||
if -0.02 < near < 0.05 and lr < 1.0:
|
if -0.02 < near < 0.06 and lr < 1.0:
|
||||||
v.co.x += 0.032 * (1.0 - lr) * sign
|
v.co.x += 0.034 * (1.0 - lr) * sign
|
||||||
bm.normal_update()
|
bm.normal_update()
|
||||||
bm.to_mesh(head.data)
|
bm.to_mesh(head.data)
|
||||||
bm.free()
|
bm.free()
|
||||||
@@ -949,7 +949,7 @@ def _head_proto(mats, spec, subdiv=48):
|
|||||||
# Eyeballs stay separate for readability; lids wrap the socket rim.
|
# Eyeballs stay separate for readability; lids wrap the socket rim.
|
||||||
parts = [head]
|
parts = [head]
|
||||||
for s in (-1.0, 1.0):
|
for s in (-1.0, 1.0):
|
||||||
ex, ey, ez = 0.052 * s, -0.134, 1.664
|
ex, ey, ez = 0.052 * s, -0.127, 1.664
|
||||||
parts.append(sphere("EyeWhite", 0.019, (ex, ey, ez), white, seg=10,
|
parts.append(sphere("EyeWhite", 0.019, (ex, ey, ez), white, seg=10,
|
||||||
rings=6, scale=(1.25, 0.62, 0.85)))
|
rings=6, scale=(1.25, 0.62, 0.85)))
|
||||||
parts.append(sphere("Iris", 0.0105, (ex, ey - 0.007, ez), acc, seg=8,
|
parts.append(sphere("Iris", 0.0105, (ex, ey - 0.007, ez), acc, seg=8,
|
||||||
@@ -960,7 +960,7 @@ def _head_proto(mats, spec, subdiv=48):
|
|||||||
parts.append(sphere("LowerLid", 0.017, (ex, ey - 0.004, ez - 0.012),
|
parts.append(sphere("LowerLid", 0.017, (ex, ey - 0.004, ez - 0.012),
|
||||||
skin, seg=8, rings=4, scale=(1.10, 0.22, 0.18),
|
skin, seg=8, rings=4, scale=(1.10, 0.22, 0.18),
|
||||||
rot=(math.radians(6), 0, 0)))
|
rot=(math.radians(6), 0, 0)))
|
||||||
parts.append(box("Brow", (0.055, 0.014, 0.012), (0.052 * s, -0.143, 1.699),
|
parts.append(box("Brow", (0.055, 0.014, 0.012), (0.052 * s, -0.137, 1.699),
|
||||||
hair, rot=(0, 0.14 * s, 0.0)))
|
hair, rot=(0, 0.14 * s, 0.0)))
|
||||||
return parts
|
return parts
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user