Tree Generator v1.9.0: Wurzelbretter am Stammfuss

Gelappter Wurzelanlauf statt glattem Trichter (Root Lobes / Root Lobe Depth /
Root Height). Als Mesh-Offset umgesetzt -> 0 zusaetzliche Tris. Stamm-Sides auf
12 fuer saubere Lappen (+104 Tris, gemessen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-29 11:23:32 +02:00
co-authored by Claude Opus 4.8
parent 55beb2bb2f
commit e3ad1727b6
5 changed files with 73 additions and 8 deletions
+68 -3
View File
@@ -1,7 +1,7 @@
bl_info = {
"name": "Stylized Tree Generator",
"author": "D4rkst3r",
"version": (1, 8, 0),
"version": (1, 9, 0),
"blender": (4, 2, 0),
"location": "View3D > Sidebar > Tree Gen",
"description": "Parametrischer Baum-/Palmen-/Busch-Generator (Geometry Nodes) mit Wachstums-Stufen",
@@ -43,12 +43,18 @@ DEFAULTS = {
"Sub Count": 2, # Sub-Aeste je Hauptast (0 = aus)
"Sub Length": 0.55,
"Sub Up": 1.4,
"Sides": 8, # Stamm rund (Referenz TreeIt: ~9)
"Sides": 12, # Stamm rund genug fuer die Wurzelbretter
# (+104 Tris ggue. 8 - gemessen, lohnt sich)
"Branch Sides": 4, # Aeste sparsamer
"Sub Sides": 3, # Zweige nur Dreiecke - sieht man nicht
"UV Scale": 1.0, # Rinden-Dichte; UVs sind world-space (m)
"Tip Blunt": 0.18, # >0 verhindert Nadelspitzen (0 = spitz, 0.45 = Kaktus)
"Root Flare": 1.1, # Wurzelanlauf: Stamm verbreitert sich am Fuss
"Root Lobes": 6.0, # Wurzelbretter; sollte in "Sides" aufgehen,
# sonst sampelt das Profil die Lappen weg
"Root Lobe Depth": 1.3, # wie weit die Lappen rausstehen
"Root Height": 1.1, # Hoehe (m) des Anlaufs; zu klein -> zu wenige
# Ringe liegen drin und man sieht nichts
"Detail": 0.55, # skaliert alle Resample-Counts = Tri-Budget-Regler
"Branch Collar": 0.4, # Ansatz-Verdickung: Uebergang Ast->Stamm (0 = aus)
"Merge": 0.0, # 1 = Aeste per Voxel-Remesh mit dem Stamm verschmelzen
@@ -174,6 +180,9 @@ def build_group():
add_in("UV Scale", 'NodeSocketFloat', DEFAULTS["UV Scale"], 0.01, 20.0)
add_in("Detail", 'NodeSocketFloat', DEFAULTS["Detail"], 0.2, 2.0)
add_in("Root Flare", 'NodeSocketFloat', DEFAULTS["Root Flare"], 0.0, 4.0)
add_in("Root Lobes", 'NodeSocketFloat', DEFAULTS["Root Lobes"], 0.0, 12.0)
add_in("Root Lobe Depth", 'NodeSocketFloat', DEFAULTS["Root Lobe Depth"], 0.0, 3.0)
add_in("Root Height", 'NodeSocketFloat', DEFAULTS["Root Height"], 0.05, 3.0)
add_in("Branch Collar", 'NodeSocketFloat', DEFAULTS["Branch Collar"], 0.0, 3.0)
add_in("Merge", 'NodeSocketFloat', DEFAULTS["Merge"], 0.0, 1.0)
add_in("Merge Voxel", 'NodeSocketFloat', DEFAULTS["Merge Voxel"], 0.005, 0.2)
@@ -664,7 +673,63 @@ def build_group():
L(uv.outputs[0], _sock(sm, "Value"))
return sm
mesh_trunk = _to_mesh(setrad, "Curve", V["Sides"], 3400, 900)
mesh_trunk0 = _to_mesh(setrad, "Curve", V["Sides"], 3400, 900)
# ---------- Wurzelanlauf ausformen (Wurzelbretter) ----------
# Der reine Radius-Flare ergibt einen glatten Trichter. Echte Wurzelanlaeufe
# sind GELAPPT. Das laesst sich nicht ueber den Kurvenradius machen (der ist
# rundum gleich), sondern erst am fertigen Mesh: Vertices nahe dem Boden
# radial nach aussen schieben, moduliert ueber den Winkel. Kostet 0 Tris.
fpos = N("GeometryNodeInputPosition"); fpos.location = (4750, 1150)
fsep = N("ShaderNodeSeparateXYZ"); fsep.location = (4900, 1150)
L(fpos.outputs[0], fsep.inputs[0])
# Hoehen-Abfall: nur das unterste Stueck ist betroffen
fh = N("ShaderNodeMath"); fh.location = (5050, 1300); fh.operation = 'DIVIDE'
L(fsep.outputs["Z"], fh.inputs[0]); L(V["Root Height"], fh.inputs[1])
fc = N("ShaderNodeMath"); fc.location = (5200, 1300); fc.operation = 'MINIMUM'
fc.inputs[1].default_value = 1.0
L(fh.outputs[0], fc.inputs[0])
fi = N("ShaderNodeMath"); fi.location = (5350, 1300); fi.operation = 'SUBTRACT'
fi.inputs[0].default_value = 1.0
L(fc.outputs[0], fi.inputs[1])
fp = N("ShaderNodeMath"); fp.location = (5500, 1300); fp.operation = 'POWER'
fp.inputs[1].default_value = 2.5
L(fi.outputs[0], fp.inputs[0])
fpc = N("ShaderNodeMath"); fpc.location = (5650, 1300); fpc.operation = 'MAXIMUM'
fpc.inputs[1].default_value = 0.0
L(fp.outputs[0], fpc.inputs[0])
# Lappen ueber den Winkel: cos(Lobes * atan2(y,x)), auf 0..1 gehoben
fat = N("ShaderNodeMath"); fat.location = (5050, 1000); fat.operation = 'ARCTAN2'
L(fsep.outputs["Y"], fat.inputs[0]); L(fsep.outputs["X"], fat.inputs[1])
fml = N("ShaderNodeMath"); fml.location = (5200, 1000); fml.operation = 'MULTIPLY'
L(fat.outputs[0], fml.inputs[0]); L(V["Root Lobes"], fml.inputs[1])
fco = N("ShaderNodeMath"); fco.location = (5350, 1000); fco.operation = 'COSINE'
L(fml.outputs[0], fco.inputs[0])
fno = N("ShaderNodeMath"); fno.location = (5500, 1000); fno.operation = 'MULTIPLY_ADD'
fno.inputs[1].default_value = 0.5; fno.inputs[2].default_value = 0.5
L(fco.outputs[0], fno.inputs[0])
# Betrag = RootLobeDepth * Hoehenabfall * Lappen * Stammradius
fa1 = N("ShaderNodeMath"); fa1.location = (5800, 1150); fa1.operation = 'MULTIPLY'
L(fpc.outputs[0], fa1.inputs[0]); L(fno.outputs[0], fa1.inputs[1])
fa2 = N("ShaderNodeMath"); fa2.location = (5950, 1150); fa2.operation = 'MULTIPLY'
L(fa1.outputs[0], fa2.inputs[0]); L(V["Root Lobe Depth"], fa2.inputs[1])
fa3 = N("ShaderNodeMath"); fa3.location = (6100, 1150); fa3.operation = 'MULTIPLY'
L(fa2.outputs[0], fa3.inputs[0]); L(V["Trunk Radius"], fa3.inputs[1])
# Richtung: radial nach aussen (XY), Z bleibt unangetastet
fxy = N("ShaderNodeCombineXYZ"); fxy.location = (5200, 850)
L(fsep.outputs["X"], fxy.inputs["X"]); L(fsep.outputs["Y"], fxy.inputs["Y"])
fnrm = N("ShaderNodeVectorMath"); fnrm.location = (5350, 850); fnrm.operation = 'NORMALIZE'
L(fxy.outputs[0], fnrm.inputs[0])
foff = N("ShaderNodeVectorMath"); foff.location = (6250, 1000); foff.operation = 'SCALE'
L(fnrm.outputs[0], foff.inputs[0]); L(fa3.outputs[0], _sock(foff, "Scale"))
mesh_trunk = N("GeometryNodeSetPosition"); mesh_trunk.location = (6400, 900)
L(_out(mesh_trunk0, "Geometry"), _sock(mesh_trunk, "Geometry"))
L(foff.outputs[0], _sock(mesh_trunk, "Offset"))
mesh_branch = _to_mesh(rsetp, "Geometry", V["Branch Sides"], 3400, 200)
mesh_sub = _to_mesh(sbset, "Geometry", V["Sub Sides"], 3400, -500)