Tweaking settings
This commit is contained in:
1 parent
e6a0750cb9
commit
a7ed716983
6 files changed
+132
-6
No files matched your search
+105
-3
@@ -27,6 +27,7 @@ overrides, --light-strength, --light-left/--light-up, --ambient, --unlit,
|
||||
import argparse
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
@@ -94,6 +95,12 @@ def parse_args():
|
||||
help="degrees the light is raised above the camera's view axis (negative = down)")
|
||||
p.add_argument("--ambient", type=float, default=0.1,
|
||||
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",
|
||||
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")
|
||||
@@ -172,8 +179,8 @@ def uv_islands(bm, uv_layer):
|
||||
return islands
|
||||
|
||||
|
||||
def mark_stem(meshes, max_width):
|
||||
"""Store a 'projection_keep' face-corner attribute (0 on the stem) and return non-stem vertices.
|
||||
def find_stem_faces(meshes, max_width):
|
||||
"""Polygon indices of the stem, per mesh name.
|
||||
|
||||
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.
|
||||
@@ -181,7 +188,7 @@ def mark_stem(meshes, max_width):
|
||||
center, _, _ = bounds(meshes)
|
||||
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))
|
||||
body_verts = []
|
||||
stems = {}
|
||||
for obj in meshes:
|
||||
mesh, mw = obj.data, obj.matrix_world
|
||||
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:
|
||||
stem_faces.update(f.index for f in island)
|
||||
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")
|
||||
|
||||
keep = mesh.attributes.get("projection_keep") or \
|
||||
@@ -380,6 +398,86 @@ def make_material(args, maps, texture):
|
||||
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):
|
||||
original = next((m for o in meshes for m in o.data.materials if m), None)
|
||||
maps = {}
|
||||
@@ -500,6 +598,10 @@ def main():
|
||||
center, dist, radius = frame_camera(scene, meshes, args.azimuth, args.elevation, args.margin)
|
||||
if not args.unlit:
|
||||
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)
|
||||
|
||||
bpy.ops.render.render(write_still=True)
|
||||
|
||||
Reference in new issue
Block a user