Tweaking settings

This commit is contained in:
Jude Southworth committed 2026-10-27 10:09:48 +00:00
1 parent e6a0750cb9
commit a7ed716983
6 files changed
+132 -6

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
python3 make_label.py assets/food_apple_01/food_apple_01_1k.blend \
--texture assets/face_crop.png \
--project \
--output test-2.png \
--azimuth 15 \
--elevation -5 \
--margin 0.9 \
--transparent \
--project-size 1.35 \
--project-offset-y 0.05 \
--project-offset-x -0.05 \
--blend-mode MIX \
--light-strength 1 \
--ambient 0.25 \
--blend-factor 0.75 \
--dew 45 \
--dew-size 0.025 \
--dew-flatness 0.4 \
--dew-seed 4
+8 -3
View File
@@ -1,7 +1,7 @@
python3 make_label.py assets/food_apple_01/food_apple_01_1k.blend \ python3 make_label.py assets/food_apple_01/food_apple_01_1k.blend \
--texture assets/face_crop.png \ --texture assets/face_crop.png \
--project \ --project \
--output test.png \ --output test-2.png \
--azimuth 15 \ --azimuth 15 \
--elevation -5 \ --elevation -5 \
--margin 0.9 \ --margin 0.9 \
@@ -10,5 +10,10 @@ python3 make_label.py assets/food_apple_01/food_apple_01_1k.blend \
--project-offset-y 0.05 \ --project-offset-y 0.05 \
--project-offset-x -0.05 \ --project-offset-x -0.05 \
--blend-mode VALUE \ --blend-mode VALUE \
--light-strength 1.5 \ --light-strength 1 \
--blend-factor 1.5 --ambient 0.25 \
--blend-factor 1 \
--dew 45 \
--dew-size 0.025 \
--dew-flatness 0.4 \
--dew-seed 4
+105 -3
View File
@@ -27,6 +27,7 @@ overrides, --light-strength, --light-left/--light-up, --ambient, --unlit,
import argparse import argparse
import math import math
import os import os
import random
import shutil import shutil
import sys import sys
@@ -94,6 +95,12 @@ def parse_args():
help="degrees the light is raised above the camera's view axis (negative = down)") help="degrees the light is raised above the camera's view axis (negative = down)")
p.add_argument("--ambient", type=float, default=0.1, p.add_argument("--ambient", type=float, default=0.1,
help="uniform fill light strength (0 = none)") help="uniform fill light strength (0 = none)")
p.add_argument("--dew", type=int, default=0, help="number of dew drops to scatter (0 = none)")
p.add_argument("--dew-size", type=float, default=0.03,
help="largest drop radius as a fraction of the model's size")
p.add_argument("--dew-flatness", type=float, default=0.55,
help="drop height relative to its radius (1 = round bead)")
p.add_argument("--dew-seed", type=int, default=0, help="random seed for drop placement")
p.add_argument("--unlit", action="store_true", p.add_argument("--unlit", action="store_true",
help="skip lighting/roughness/normal; emit the blended colour as-is") help="skip lighting/roughness/normal; emit the blended colour as-is")
p.add_argument("--resolution", type=int, default=1024, help="square output size in px") p.add_argument("--resolution", type=int, default=1024, help="square output size in px")
@@ -172,8 +179,8 @@ def uv_islands(bm, uv_layer):
return islands return islands
def mark_stem(meshes, max_width): def find_stem_faces(meshes, max_width):
"""Store a 'projection_keep' face-corner attribute (0 on the stem) and return non-stem vertices. """Polygon indices of the stem, per mesh name.
The stem is any UV island in the top half of the model whose horizontal The stem is any UV island in the top half of the model whose horizontal
extent is under max_width x the model's width. extent is under max_width x the model's width.
@@ -181,7 +188,7 @@ def mark_stem(meshes, max_width):
center, _, _ = bounds(meshes) center, _, _ = bounds(meshes)
corners = [o.matrix_world @ Vector(c) for o in meshes for c in o.bound_box] corners = [o.matrix_world @ Vector(c) for o in meshes for c in o.bound_box]
model_width = max(max(c[i] for c in corners) - min(c[i] for c in corners) for i in (0, 1)) model_width = max(max(c[i] for c in corners) - min(c[i] for c in corners) for i in (0, 1))
body_verts = [] stems = {}
for obj in meshes: for obj in meshes:
mesh, mw = obj.data, obj.matrix_world mesh, mw = obj.data, obj.matrix_world
stem_faces = set() stem_faces = set()
@@ -194,6 +201,17 @@ def mark_stem(meshes, max_width):
if spread < max_width * model_width and min(p.z for p in pts) > center.z: if spread < max_width * model_width and min(p.z for p in pts) > center.z:
stem_faces.update(f.index for f in island) stem_faces.update(f.index for f in island)
bm.free() bm.free()
stems[obj.name] = stem_faces
return stems
def mark_stem(meshes, max_width):
"""Store a 'projection_keep' face-corner attribute (0 on the stem) and return non-stem vertices."""
stems = find_stem_faces(meshes, max_width)
body_verts = []
for obj in meshes:
mesh, mw = obj.data, obj.matrix_world
stem_faces = stems[obj.name]
print(f"{obj.name}: {len(stem_faces)} stem faces kept as-is") print(f"{obj.name}: {len(stem_faces)} stem faces kept as-is")
keep = mesh.attributes.get("projection_keep") or \ keep = mesh.attributes.get("projection_keep") or \
@@ -380,6 +398,86 @@ def make_material(args, maps, texture):
return mat return mat
def add_dew(scene, meshes, args):
"""Scatter flattened water droplets over the skin (not the stem or the underside)."""
rng = random.Random(args.dew_seed)
_, _, extent = bounds(meshes)
stems = find_stem_faces(meshes, args.stem_max_width)
# Candidate triangles, weighted by area, so droplets are spread evenly over the surface.
tris, weights = [], []
for obj in meshes:
mesh, mw = obj.data, obj.matrix_world
normal_mw = mw.to_3x3().inverted().transposed()
mesh.calc_loop_triangles()
for tri in mesh.loop_triangles:
if tri.polygon_index in stems[obj.name]:
continue
normal = (normal_mw @ tri.normal).normalized()
if normal.z < -0.3: # underside
continue
pts = [mw @ mesh.vertices[v].co for v in tri.vertices]
tris.append((pts, normal))
weights.append((pts[1] - pts[0]).cross(pts[2] - pts[0]).length / 2)
if not tris:
return
# Clear water, darkened towards the edges so each drop's outline reads.
water = bpy.data.materials.new("water")
water.use_nodes = True
nodes, links = water.node_tree.nodes, water.node_tree.links
bsdf = nodes["Principled BSDF"]
bsdf.inputs["Base Color"].default_value = (1, 1, 1, 1)
bsdf.inputs["Roughness"].default_value = 0.02
bsdf.inputs["IOR"].default_value = 1.33
bsdf.inputs["Transmission Weight"].default_value = 1.0
rim = nodes.new("ShaderNodeBsdfGlossy")
rim.inputs["Color"].default_value = (0.05, 0.05, 0.05, 1)
rim.inputs["Roughness"].default_value = 0.02
edge = nodes.new("ShaderNodeLayerWeight")
edge.inputs["Blend"].default_value = 0.35
mix = nodes.new("ShaderNodeMixShader")
links.new(edge.outputs["Facing"], mix.inputs["Fac"])
links.new(bsdf.outputs["BSDF"], mix.inputs[1])
links.new(rim.outputs["BSDF"], mix.inputs[2])
links.new(mix.outputs["Shader"], nodes["Material Output"].inputs["Surface"])
# Light refracting through the drops (caustics) only adds fireflies here.
scene.cycles.caustics_refractive = False
scene.cycles.caustics_reflective = False
scene.cycles.blur_glossy = 1.0
bm = bmesh.new()
bmesh.ops.create_uvsphere(bm, u_segments=24, v_segments=12, radius=1.0)
for face in bm.faces:
face.smooth = True
drop_mesh = bpy.data.meshes.new("dew_drop")
bm.to_mesh(drop_mesh)
bm.free()
drop_mesh.materials.append(water)
collection = bpy.data.collections.new("dew")
scene.collection.children.link(collection)
for i, (pts, normal) in enumerate(rng.choices(tris, weights, k=args.dew)):
a, b = rng.random(), rng.random()
if a + b > 1:
a, b = 1 - a, 1 - b
point = pts[0] + (pts[1] - pts[0]) * a + (pts[2] - pts[0]) * b
# Log-uniform size between dew-size/2.5 and dew-size, as a fraction of the model.
r = extent * args.dew_size * math.exp(rng.uniform(math.log(0.4), 0))
height = r * args.dew_flatness
drop = bpy.data.objects.new(f"dew_{i}", drop_mesh)
drop.scale = (r, r, height)
drop.rotation_euler = normal.to_track_quat("Z", "Y").to_euler()
# Sink the drop so roughly its top 70% sits proud of the skin.
drop.location = point + normal * height * 0.4
# Light reaching the skin through a drop is a caustic that renders dark and
# noisy; letting it pass straight through keeps the skin under the drop lit.
drop.visible_shadow = False
collection.objects.link(drop)
print(f"Added {args.dew} dew drops")
def find_maps(args, meshes): def find_maps(args, meshes):
original = next((m for o in meshes for m in o.data.materials if m), None) original = next((m for o in meshes for m in o.data.materials if m), None)
maps = {} maps = {}
@@ -500,6 +598,10 @@ def main():
center, dist, radius = frame_camera(scene, meshes, args.azimuth, args.elevation, args.margin) center, dist, radius = frame_camera(scene, meshes, args.azimuth, args.elevation, args.margin)
if not args.unlit: if not args.unlit:
add_light_behind_camera(scene, center, dist, radius, args) add_light_behind_camera(scene, center, dist, radius, args)
if args.dew:
add_dew(scene, meshes, args)
elif args.dew:
print("--dew is ignored with --unlit")
setup_render(scene, args) setup_render(scene, args)
bpy.ops.render.render(write_still=True) bpy.ops.render.render(write_still=True)
Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB