From a8ca7a6453710f82ae6dfa551d43dc8a825e4457 Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Wed, 29 Jul 2026 07:11:35 +0200 Subject: [PATCH] v2.2.0: Textur-Bake-Button (tileable, fuer Triplanar in UE) + UE-Material-Anleitung - Bake BaseColor/Normal/Roughness/AO als PNG - Nahtlos tileable via 4D-Torus-Projektion der UV (cos/sin u,v) - Cycles-Bake mit Zustands-Restore, temporaere Bake-Plane + Cleanup - docs/UE_Material_Triplanar.md: WorldAlignedTexture/Normal-Verdrahtung - _deselect_all gegen transiente None-Refs nach Objekt-Removal gehaertet - Live gegen Blender 5.1.2 getestet (4 Maps, Normal jetzt mit Struktur), Headless gruen Co-Authored-By: Claude Opus 4.8 --- README.md | 9 + blender_manifest.toml | 2 +- dist/index.json | 8 +- dist/stylized_rock_generator-2.1.0.zip | Bin 11477 -> 0 bytes dist/stylized_rock_generator-2.2.0.zip | Bin 0 -> 14107 bytes docs/UE_Material_Triplanar.md | 101 ++++++++++ stylized_rock_generator.py | 261 ++++++++++++++++++++++++- 7 files changed, 373 insertions(+), 8 deletions(-) delete mode 100644 dist/stylized_rock_generator-2.1.0.zip create mode 100644 dist/stylized_rock_generator-2.2.0.zip create mode 100644 docs/UE_Material_Triplanar.md diff --git a/README.md b/README.md index 1e51f1d..8a0de5a 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,15 @@ aussen (signed volume > 0), UV0 + UV1 vorhanden, und gibt eine Zusammenfassung --- +## Changelog v2.2.0 + +- **Textur-Bake-Button:** backt ein nahtlos **tileables** Stylized-Rock-Set + (BaseColor / Normal / Roughness / AO) als PNG. Nahtlos durch **4D-Torus- + Projektion** der UV (`cos/sin` von U und V) → tilet in beide Richtungen ohne + Naht, ideal fuer **Triplanar** in Unreal. +- Anleitung zur UE-Material-Verdrahtung: siehe + [docs/UE_Material_Triplanar.md](docs/UE_Material_Triplanar.md). + ## Changelog v2.1.0 - **FBX-Export-Button (UE-ready):** exportiert jeden Rock / jede LOD-Gruppe als diff --git a/blender_manifest.toml b/blender_manifest.toml index 2e5414b..fe08bf4 100644 --- a/blender_manifest.toml +++ b/blender_manifest.toml @@ -1,7 +1,7 @@ schema_version = "1.0.0" id = "stylized_rock_generator" -version = "2.1.0" +version = "2.2.0" name = "Stylized Rock Generator" tagline = "Batch-Generator fuer stylized Rocks mit UE5-Export" maintainer = "D4rkst3r" diff --git a/dist/index.json b/dist/index.json index 8853a70..966b395 100644 --- a/dist/index.json +++ b/dist/index.json @@ -7,7 +7,7 @@ "id": "stylized_rock_generator", "name": "Stylized Rock Generator", "tagline": "Batch-Generator fuer stylized Rocks mit UE5-Export", - "version": "2.1.0", + "version": "2.2.0", "type": "add-on", "maintainer": "D4rkst3r", "license": [ @@ -23,9 +23,9 @@ "Mesh", "Modeling" ], - "archive_url": "./stylized_rock_generator-2.1.0.zip", - "archive_size": 11477, - "archive_hash": "sha256:76911905d9e283ce2cbe85ab341194e3fcfcb431eb7cf3816b96e6d2358214ae" + "archive_url": "./stylized_rock_generator-2.2.0.zip", + "archive_size": 14107, + "archive_hash": "sha256:4a4a83e9f6955f0deb0fe56dce59d8e540424df21982639e457db1b3533d9cf5" } ] } \ No newline at end of file diff --git a/dist/stylized_rock_generator-2.1.0.zip b/dist/stylized_rock_generator-2.1.0.zip deleted file mode 100644 index 67625a9039339615593ee30cfedc3126cea7b6ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11477 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 ymZ1M}|3{?we{b=>Dca diff --git a/dist/stylized_rock_generator-2.2.0.zip b/dist/stylized_rock_generator-2.2.0.zip new file mode 100644 index 0000000000000000000000000000000000000000..e21f1274c846db52ca35f15e0975200c4127bf06 GIT binary patch literal 14107 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 literal 0 HcmV?d00001 diff --git a/docs/UE_Material_Triplanar.md b/docs/UE_Material_Triplanar.md new file mode 100644 index 0000000..550a98f --- /dev/null +++ b/docs/UE_Material_Triplanar.md @@ -0,0 +1,101 @@ +# Unreal-Material: Stylized Rock (Triplanar / World-Aligned) + +Ziel: **ein** Material fuer **alle** Rocks. Dank Triplanar (world-aligned) wird die +Textur nach Weltposition projiziert – unabhaengig von den UVs jedes einzelnen +Steins. Du musst also nichts pro Rock texturieren. + +Die vier Maps kommen aus dem Addon (Panel *Rock Gen → Textur-Bake*): + +| Datei | Inhalt | UE sRGB | Compression | +|------------------|-------------|---------|--------------------| +| `*_BC.png` | BaseColor | **an** | Default (BC7/DXT1) | +| `*_N.png` | Normal | aus | **Normalmap** | +| `*_R.png` | Roughness | aus | Default/Masks | +| `*_AO.png` | AO / Cavity | aus | Default/Masks | + +> Die Normal Map ist OpenGL (+Y). In UE beim Import **„Flip Green Channel"** +> aktivieren (oder gruen einmalig invertieren), sonst wirkt das Relief eingedellt. + +--- + +## 1. Texturen importieren + +1. Die vier PNGs in den Content Browser ziehen. +2. Fuer `_N`: Compression = **Normalmap**, sRGB aus, **Flip Green Channel** an. +3. Fuer `_R` und `_AO`: sRGB **aus** (lineare Daten), Compression „Masks" ist ok. +4. `_BC`: sRGB **an** (Standard). + +*(Optional, spart Samples: `_AO`, `_R` und ggf. Metallic in eine „ORM"-Textur +packen – R=AO, G=Roughness, B=Metallic. Dann unten die einzelnen Sampler durch +einen ORM-Sampler + Component-Mask ersetzen.)* + +--- + +## 2. Material anlegen + +Neues Material `M_Rock_Stylized`, *Two Sided* aus, *Shading Model* Default Lit. + +### Parameter (fuer Instanzen) +- **ScalarParameter** `WorldSize` (Default `256`) → Weltgroesse einer Kachel in + cm. Kleiner = Textur wirkt groesser/detaillierter. +- (optional) **ScalarParameter** `NormalIntensity` (Default `1.0`). + +### BaseColor / Roughness / AO (World Aligned Texture) +Fuer jede der drei Maps einen **`WorldAlignedTexture`**-Node: + +``` +TextureObject(T_..._BC) ─┐ +WorldSize (Scalar) ──────► WorldAlignedTexture ─► XYZTexture ─► BaseColor +``` + +- `WorldAlignedTexture` → Eingang **TextureObject** = die jeweilige Textur + (als *TextureObject*-Node, nicht *TextureSample*). +- Eingang **WorldSize** = `WorldSize`-Parameter. +- Ausgang **XYZTexture** verbinden: + - `T_..._BC` → **Base Color** + - `T_..._R` → **Roughness** + - `T_..._AO` → **Ambient Occlusion** + +### Normal (World Aligned Normal) +Normalen brauchen den speziellen Node **`WorldAlignedNormal`**: + +``` +TextureObject(T_..._N) ─► WorldAlignedNormal ─► XYZNormal ─► (FlattenNormal / Normal) +WorldSize ─────────────► +``` + +- Ausgang **XYZTexture (Normal)** → **Normal**. +- Intensitaet optional ueber `FlattenNormal` (Flatness = `1 - NormalIntensity`) + oder einen Multiply auf den XY-Anteil. + +> `WorldAlignedTexture`/`WorldAlignedNormal` haben einen Haken +> **„Use World Space Normal to Blend"** bzw. eine `WorldSpaceNormal`-Eingabe – +> Default passt fuer Rocks. Der Node blendet automatisch die drei Projektions- +> achsen (Triplanar). + +--- + +## 3. Feinschliff (optional) + +- **Farbvariation pro Instanz:** BaseColor mit `PerInstanceRandom` * einer leichten + Tint-Range multiplizieren → jeder Rock leicht anders, gleiche Textur. +- **Nahtlos in der Hoehe:** Da die Bake-Textur bereits nahtlos tilet, entstehen bei + Triplanar keine sichtbaren Kanten. +- **LODs:** Beim FBX-Import „Import LODs" an – das Material gilt automatisch fuer alle + LOD-Stufen. +- **Lightmaps:** Der UV1-Kanal (Lightmap) aus dem Addon wird von UE fuer statische + Beleuchtung genutzt; das Triplanar-Material nutzt ihn nicht, das ist korrekt so. + +--- + +## Kurzfassung der Verdrahtung + +``` +WorldSize(Scalar) + ├─► WorldAlignedTexture(BC) ─► XYZTexture ─► BaseColor + ├─► WorldAlignedTexture(R) ─► XYZTexture ─► Roughness + ├─► WorldAlignedTexture(AO) ─► XYZTexture ─► Ambient Occlusion + └─► WorldAlignedNormal(N) ─► XYZNormal ─► Normal +``` + +Ein Material, liegt auf jedem Rock. Fertig. diff --git a/stylized_rock_generator.py b/stylized_rock_generator.py index f2e1a4a..fced9f6 100644 --- a/stylized_rock_generator.py +++ b/stylized_rock_generator.py @@ -1,7 +1,7 @@ bl_info = { "name": "Stylized Rock Generator", "author": "D4rkst3r", - "version": (2, 1, 0), + "version": (2, 2, 0), "blender": (4, 2, 0), "location": "View3D > Sidebar > Rock Gen", "description": ( @@ -107,9 +107,12 @@ def _face_count(obj): def _deselect_all(context): - for o in context.view_layer.objects: - if o.select_get(): + for o in list(context.view_layer.objects): + try: o.select_set(False) + except (ReferenceError, AttributeError, RuntimeError): + # Kann nach dem Entfernen eines Objekts kurzzeitig ungueltig sein + pass def _ensure_object_mode(context): @@ -728,6 +731,25 @@ class RockGenSettings(PropertyGroup): description="Ab dieser Rock-Anzahl wird der modale (nicht-blockierende) Modus genutzt", ) + # --- Textur-Bake (tileable, fuer Triplanar in UE) --- + bake_dir: StringProperty( + name="Bake-Ordner", default="", subtype='DIR_PATH', + description="Zielordner fuer die gebackenen Textur-PNGs", + ) + bake_prefix: StringProperty( + name="Textur-Prefix", default="T_Rock_Stylized", + description="Dateiname-Prefix (UE-Konvention T_)", + ) + bake_resolution: EnumProperty( + name="Aufloesung", + items=[('512', "512", ""), ('1024', "1024", ""), ('2048', "2048", "")], + default='1024', + ) + bake_color: BoolProperty(name="BaseColor", default=True) + bake_normal: BoolProperty(name="Normal", default=True) + bake_roughness: BoolProperty(name="Roughness", default=True) + bake_ao: BoolProperty(name="AO / Cavity", default=True) + # --- FBX-Export --- export_dir: StringProperty( name="Export-Ordner", default="", subtype='DIR_PATH', @@ -911,6 +933,226 @@ class OBJECT_OT_export_rocks_fbx(Operator): return {'FINISHED'} +# --------------------------------------------------------------------------- +# Textur-Bake (nahtlos tileable via 4D-Torus-Projektion, fuer Triplanar in UE) +# --------------------------------------------------------------------------- + +def _build_bake_material(): + """Baut ein prozedurales, nahtlos tileables Stylized-Rock-Material. + + Trick: Die 2D-UV wird als (cos u, sin u, cos v, sin v) auf einen Torus + projiziert und als 4D-Noise gesampelt -> tilet in U und V ohne Naht. + Liefert (mat, sockets) mit den fertigen Ausgangs-Sockets fuer die Bake-Passes. + """ + mat = bpy.data.materials.new("__RockBakeMat") + mat.use_nodes = True + nt = mat.node_tree + nodes, links = nt.nodes, nt.links + nodes.clear() + + out = nodes.new("ShaderNodeOutputMaterial") + bsdf = nodes.new("ShaderNodeBsdfPrincipled") + emit = nodes.new("ShaderNodeEmission") + texco = nodes.new("ShaderNodeTexCoord") + sep = nodes.new("ShaderNodeSeparateXYZ") + links.new(texco.outputs["UV"], sep.inputs[0]) + + def _circle(coord_socket): + mul = nodes.new("ShaderNodeMath"); mul.operation = 'MULTIPLY' + mul.inputs[1].default_value = TWO_PI + links.new(coord_socket, mul.inputs[0]) + cos = nodes.new("ShaderNodeMath"); cos.operation = 'COSINE' + links.new(mul.outputs[0], cos.inputs[0]) + sin = nodes.new("ShaderNodeMath"); sin.operation = 'SINE' + links.new(mul.outputs[0], sin.inputs[0]) + return cos.outputs[0], sin.outputs[0] + + cu, su = _circle(sep.outputs["X"]) + cv, sv = _circle(sep.outputs["Y"]) + comb = nodes.new("ShaderNodeCombineXYZ") + links.new(cu, comb.inputs[0]) + links.new(su, comb.inputs[1]) + links.new(cv, comb.inputs[2]) + + def _torus_noise(scale, detail): + n = nodes.new("ShaderNodeTexNoise") + n.noise_dimensions = '4D' + n.inputs["Scale"].default_value = scale + n.inputs["Detail"].default_value = detail + links.new(comb.outputs[0], n.inputs["Vector"]) + links.new(sv, n.inputs["W"]) + return n + + noise_base = _torus_noise(2.5, 6.0) # grobe Formvariation + noise_cavity = _torus_noise(5.0, 6.0) # feinere Risse/Cavity + noise_detail = _torus_noise(9.0, 8.0) # Mikro-Detail fuer die Normal Map + + # Cavity/AO: Kontrast per MapRange + ao = nodes.new("ShaderNodeMapRange") + links.new(noise_cavity.outputs["Fac"], ao.inputs["Value"]) + ao.inputs["From Min"].default_value = 0.3 + ao.inputs["From Max"].default_value = 0.7 + ao.inputs["To Min"].default_value = 0.35 + ao.inputs["To Max"].default_value = 1.0 + ao.clamp = True + + # BaseColor: kombinierter Fac (Form * Cavity) durch ColorRamp + comb_fac = nodes.new("ShaderNodeMath"); comb_fac.operation = 'MULTIPLY' + links.new(noise_base.outputs["Fac"], comb_fac.inputs[0]) + links.new(ao.outputs[0], comb_fac.inputs[1]) + ramp = nodes.new("ShaderNodeValToRGB") + ramp.color_ramp.elements[0].position = 0.25 + ramp.color_ramp.elements[0].color = (0.09, 0.09, 0.10, 1.0) + ramp.color_ramp.elements[1].position = 0.85 + ramp.color_ramp.elements[1].color = (0.46, 0.42, 0.37, 1.0) + links.new(comb_fac.outputs[0], ramp.inputs["Fac"]) + + # Roughness: rauer in dunklen Bereichen + rough = nodes.new("ShaderNodeMapRange") + links.new(noise_base.outputs["Fac"], rough.inputs["Value"]) + rough.inputs["To Min"].default_value = 0.9 + rough.inputs["To Max"].default_value = 0.6 + rough.clamp = True + + # Bump -> Normal (fuer den Normal-Bake). Height = grobe Form + Mikro-Detail, + # damit die Normal Map sichtbare Struktur bekommt (nicht flach). + height_mix = nodes.new("ShaderNodeMath"); height_mix.operation = 'ADD' + height_mix.inputs[1].default_value = 0.0 + hb = nodes.new("ShaderNodeMath"); hb.operation = 'MULTIPLY' + hb.inputs[1].default_value = 0.6 + links.new(noise_base.outputs["Fac"], hb.inputs[0]) + hd = nodes.new("ShaderNodeMath"); hd.operation = 'MULTIPLY' + hd.inputs[1].default_value = 0.4 + links.new(noise_detail.outputs["Fac"], hd.inputs[0]) + links.new(hb.outputs[0], height_mix.inputs[0]) + links.new(hd.outputs[0], height_mix.inputs[1]) + bump = nodes.new("ShaderNodeBump") + bump.inputs["Strength"].default_value = 0.9 + bump.inputs["Distance"].default_value = 0.5 + links.new(height_mix.outputs[0], bump.inputs["Height"]) + links.new(ramp.outputs["Color"], bsdf.inputs["Base Color"]) + links.new(rough.outputs[0], bsdf.inputs["Roughness"]) + links.new(bump.outputs["Normal"], bsdf.inputs["Normal"]) + links.new(bsdf.outputs[0], out.inputs["Surface"]) + + sockets = { + "out": out, "bsdf": bsdf, "emit": emit, + "color": ramp.outputs["Color"], + "roughness": rough.outputs[0], + "ao": ao.outputs[0], + } + return mat, sockets + + +def _bake_one(context, mat, img_node, name, folder, res, bake_type, non_color): + img = bpy.data.images.new(name, res, res, alpha=False, float_buffer=False) + if non_color: + img.colorspace_settings.name = 'Non-Color' + img_node.image = img + mat.node_tree.nodes.active = img_node + if bake_type == 'NORMAL': + bpy.ops.object.bake(type='NORMAL', normal_space='TANGENT', use_clear=True) + else: + bpy.ops.object.bake(type='EMIT', use_clear=True) + path = os.path.join(folder, name + ".png") + img.filepath_raw = path + img.file_format = 'PNG' + img.save() + bpy.data.images.remove(img) + return path + + +class OBJECT_OT_bake_rock_textures(Operator): + bl_idname = "object.bake_rock_textures" + bl_label = "Textur backen" + bl_description = "Backt ein nahtlos tileables Stylized-Rock-Textur-Set (fuer Triplanar in UE)" + + def execute(self, context): + settings = context.scene.rock_gen_settings + folder = bpy.path.abspath(settings.bake_dir) if settings.bake_dir else "" + if not folder or not os.path.isdir(folder): + self.report({'ERROR'}, "Bitte einen gueltigen Bake-Ordner waehlen.") + return {'CANCELLED'} + + res = int(settings.bake_resolution) + prefix = settings.bake_prefix.strip() or "T_Rock_Stylized" + scene = context.scene + + # Zustand sichern + prev_engine = scene.render.engine + prev_active = context.view_layer.objects.active + prev_selected = [o for o in context.selected_objects] + prev_samples = getattr(scene.cycles, "samples", None) if hasattr(scene, "cycles") else None + + _ensure_object_mode(context) + saved = [] + plane = None + mat = None + try: + bpy.ops.mesh.primitive_plane_add(size=2.0) + plane = context.active_object + plane.name = "__RockBakePlane" + mat, sk = _build_bake_material() + plane.data.materials.clear() + plane.data.materials.append(mat) + + scene.render.engine = 'CYCLES' + if hasattr(scene, "cycles"): + scene.cycles.samples = 1 # Emit/Normal sind deterministisch + scene.render.bake.margin = max(2, res // 128) + + nodes = mat.node_tree.nodes + links = mat.node_tree.links + img_node = nodes.new("ShaderNodeTexImage") + + _deselect_all(context) + plane.select_set(True) + context.view_layer.objects.active = plane + + if settings.bake_normal: + links.new(sk["bsdf"].outputs[0], sk["out"].inputs["Surface"]) + saved.append(_bake_one(context, mat, img_node, prefix + "_N", + folder, res, 'NORMAL', non_color=True)) + + def _emit_pass(source_socket, suffix, non_color): + links.new(source_socket, sk["emit"].inputs["Color"]) + links.new(sk["emit"].outputs[0], sk["out"].inputs["Surface"]) + saved.append(_bake_one(context, mat, img_node, prefix + suffix, + folder, res, 'EMIT', non_color)) + + if settings.bake_color: + _emit_pass(sk["color"], "_BC", non_color=False) + if settings.bake_roughness: + _emit_pass(sk["roughness"], "_R", non_color=True) + if settings.bake_ao: + _emit_pass(sk["ao"], "_AO", non_color=True) + + except Exception as exc: # noqa: BLE001 + print("[RockGen] Bake fehlgeschlagen: %s" % exc) + traceback.print_exc() + self.report({'ERROR'}, "Bake fehlgeschlagen: %s (Details in der Konsole)" % exc) + finally: + if plane is not None: + bpy.data.objects.remove(plane, do_unlink=True) + if mat is not None and mat.users == 0: + bpy.data.materials.remove(mat) + scene.render.engine = prev_engine + if prev_samples is not None: + scene.cycles.samples = prev_samples + _deselect_all(context) + for o in prev_selected: + try: + o.select_set(True) + except ReferenceError: + pass + context.view_layer.objects.active = prev_active + + if saved: + self.report({'INFO'}, "%d Textur(en) gebacken nach %s" % (len(saved), folder)) + return {'FINISHED'} + return {'CANCELLED'} + + # --------------------------------------------------------------------------- # Operatoren: Presets # --------------------------------------------------------------------------- @@ -1099,6 +1341,18 @@ class VIEW3D_PT_rock_generator(Panel): box.prop(settings, "export_selected_only") box.operator("object.export_rocks_fbx", icon='EXPORT') + box = layout.box() + box.label(text="Textur-Bake (tileable / Triplanar)") + box.prop(settings, "bake_dir") + box.prop(settings, "bake_prefix") + box.prop(settings, "bake_resolution") + row = box.row(align=True) + row.prop(settings, "bake_color", toggle=True) + row.prop(settings, "bake_normal", toggle=True) + row.prop(settings, "bake_roughness", toggle=True) + row.prop(settings, "bake_ao", toggle=True) + box.operator("object.bake_rock_textures", icon='TEXTURE') + # --------------------------------------------------------------------------- # Registration @@ -1108,6 +1362,7 @@ classes = ( RockGenSettings, OBJECT_OT_generate_rocks, OBJECT_OT_export_rocks_fbx, + OBJECT_OT_bake_rock_textures, ROCKGEN_OT_preset_save, ROCKGEN_OT_preset_load, ROCKGEN_OT_preset_delete,