Rebuild the foliage with leaf cards: leafier silhouette at two thirds the triangles
This commit is contained in:
1 parent
d3a87acf2d
commit
045ed2a58f
13 files changed
+114
-28
No files matched your search
+66
-11
@@ -73,7 +73,7 @@ PROP_MAX_TRIS = 4000
|
||||
try:
|
||||
import bpy # type: ignore
|
||||
import bmesh # type: ignore
|
||||
from mathutils import Matrix, Vector # type: ignore
|
||||
from mathutils import Euler, Matrix, Vector # type: ignore
|
||||
except ImportError: # pragma: no cover - audit/sheet/list run outside Blender
|
||||
bpy = None
|
||||
|
||||
@@ -2621,20 +2621,73 @@ def build_gear(name):
|
||||
# Props (grounded, front = Blender -Y)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _shrub(name, center, radii, mat, lumps=7, seed=1, lump=0.42):
|
||||
"""Dense foliage cluster: many small flat-shaded clumps in two greens.
|
||||
def _leaf_card(name, size, centre, normal, mat, rng, spread=0.5):
|
||||
"""One flat leaf card: a 2-triangle quad, angled off the canopy normal.
|
||||
|
||||
Alternating light/dark clumps break the single-colour read that made the
|
||||
old shrub look like a rock; the facets still catch the light at game
|
||||
distance (the realism-pass foliage note).
|
||||
Cards are what makes foliage read as leaves instead of a rock: the silhouette
|
||||
becomes ragged and the light breaks across many small faces. They carry no
|
||||
alpha texture, so the material stays in the flat-colour library and the
|
||||
Compatibility renderer needs no transparency pass.
|
||||
"""
|
||||
bm = bmesh.new()
|
||||
corners = [(-size, -size * 0.7), (size, -size * 0.7),
|
||||
(size, size * 0.7), (-size, size * 0.7)]
|
||||
verts = [bm.verts.new((x, y, 0.0)) for x, y in corners]
|
||||
bm.faces.new(verts)
|
||||
mesh = bpy.data.meshes.new(name)
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
# Face the card along the canopy normal, then tilt it so the cluster is not a
|
||||
# shell of coplanar quads.
|
||||
direction = Vector(normal)
|
||||
if direction.length < 1e-6:
|
||||
direction = Vector((0.0, 0.0, 1.0))
|
||||
direction.normalize()
|
||||
quat = Vector((0.0, 0.0, 1.0)).rotation_difference(direction)
|
||||
tilt = Euler((rng.uniform(-spread, spread), rng.uniform(-spread, spread),
|
||||
rng.uniform(0.0, math.tau)), "XYZ").to_quaternion()
|
||||
obj.rotation_mode = "QUATERNION"
|
||||
obj.rotation_quaternion = quat @ tilt
|
||||
obj.location = centre
|
||||
return _finish(obj, mat, smooth=False)
|
||||
|
||||
|
||||
def _shrub(name, center, radii, mat, lumps=7, seed=1, lump=0.42):
|
||||
"""Foliage cluster: a dark inner mass wrapped in leaf cards.
|
||||
|
||||
The old version was a stack of spheres, which read as a rock at distance. The
|
||||
inner mass keeps the canopy opaque and the cards give it a leaf silhouette and
|
||||
a broken light response; it also costs fewer triangles than the sphere stack.
|
||||
"""
|
||||
parts = []
|
||||
import random
|
||||
rng = random.Random(seed)
|
||||
mat_light = material("foliage")
|
||||
mat_dark = material("foliage_dark")
|
||||
parts.append(sphere(name, 1.0, center, mat, seg=10, rings=7, scale=radii,
|
||||
# Inner mass: one low-poly sphere at 72 % so the cards always have something
|
||||
# opaque behind them.
|
||||
parts.append(sphere(name, 1.0, center, mat, seg=8, rings=6,
|
||||
scale=(radii[0] * 0.72, radii[1] * 0.72, radii[2] * 0.72),
|
||||
smooth=False))
|
||||
card_count = 18 + lumps * 7
|
||||
card_size = max(radii) * 0.17
|
||||
for i in range(card_count):
|
||||
# Fibonacci-ish distribution over the ellipsoid, jittered.
|
||||
t = (i + 0.5) / card_count
|
||||
z = 1.0 - 2.0 * t
|
||||
r = math.sqrt(max(0.0, 1.0 - z * z))
|
||||
a = math.tau * i * 0.61803398875 + rng.uniform(-0.25, 0.25)
|
||||
off = Vector((center[0] + math.cos(a) * radii[0] * r * rng.uniform(0.86, 1.02),
|
||||
center[1] + math.sin(a) * radii[1] * r * rng.uniform(0.86, 1.02),
|
||||
center[2] + z * radii[2] * rng.uniform(0.86, 1.02)))
|
||||
normal = (off - Vector(center))
|
||||
normal.x /= max(radii[0], 1e-4)
|
||||
normal.y /= max(radii[1], 1e-4)
|
||||
normal.z /= max(radii[2], 1e-4)
|
||||
parts.append(_leaf_card(f"{name}Leaf", card_size * rng.uniform(0.75, 1.3), off,
|
||||
normal, mat_light if i % 3 else mat_dark, rng))
|
||||
for i in range(lumps * 2): # inner clumps fill the volume
|
||||
a = math.tau * i / (lumps * 2) + rng.uniform(-0.25, 0.25)
|
||||
ring = rng.uniform(0.25, 0.75)
|
||||
@@ -2643,8 +2696,9 @@ def _shrub(name, center, radii, mat, lumps=7, seed=1, lump=0.42):
|
||||
center[1] + math.sin(a) * radii[1] * ring,
|
||||
center[2] + rng.uniform(-0.2, 0.55) * radii[2])
|
||||
parts.append(sphere(name, 1.0, off, mat_light if i % 2 == 0 else mat_dark,
|
||||
seg=8, rings=6,
|
||||
scale=(radii[0] * r, radii[1] * r, radii[2] * r * 0.9),
|
||||
seg=6, rings=4,
|
||||
scale=(radii[0] * r * 0.8, radii[1] * r * 0.8,
|
||||
radii[2] * r * 0.75),
|
||||
smooth=False))
|
||||
for i in range(max(3, lumps // 2)): # outer clumps break the silhouette
|
||||
a = math.tau * i / max(3, lumps // 2) + rng.uniform(-0.3, 0.3)
|
||||
@@ -2653,8 +2707,9 @@ def _shrub(name, center, radii, mat, lumps=7, seed=1, lump=0.42):
|
||||
center[1] + math.sin(a) * radii[1] * rng.uniform(0.85, 1.0),
|
||||
center[2] + rng.uniform(-0.05, 0.4) * radii[2])
|
||||
parts.append(sphere(name, 1.0, off, mat_dark if i % 2 == 0 else mat_light,
|
||||
seg=8, rings=6,
|
||||
scale=(radii[0] * r, radii[1] * r, radii[2] * r * 0.8),
|
||||
seg=6, rings=4,
|
||||
scale=(radii[0] * r * 0.8, radii[1] * r * 0.8,
|
||||
radii[2] * r * 0.7),
|
||||
rot=(rng.uniform(0, 1), rng.uniform(0, 1), rng.uniform(0, 3)),
|
||||
smooth=False))
|
||||
return parts
|
||||
|
||||
Reference in new issue
Block a user