Rock Generator 2.8.0: Cavity in Vertex-Farben (texturfreier Weg)
Idee kam vom User: die Steine ueber "smart materials" in UE loesen statt ueber gebackene Texturen. Prozedurales Noise/Voronoi im Shader waere zu teuer (laeuft pro Pixel), aber der Kern der Idee traegt - deshalb dieser Weg: Neue Option "Cavity in Vertex-Farben": schreibt Cavity/AO plus optionalen Hoehen-Verlauf in das Farb-Attribut "Cavity" (vertex_color_dirt + eigene Nachbearbeitung fuer Staerke und Verlauf). Das Material braucht damit KEINE Textur: Base Color = Farbton x VertexColor, dazu Kantenlicht und Schattentint. Gegenueber Triplanar sitzen die dunklen Fugen dadurch auf den ECHTEN Kanten des Meshes statt darueberzuschweben - genau das passiert in der Genshin-Referenz. Verifiziert: Attribut wird angelegt, Wertebereich 0.250..0.947 (echter Kontrast), und es ueberlebt den FBX-Roundtrip UNVERAENDERT (gleicher Bereich nach Import). Regler: Cavity-Staerke, Hoehen-Verlauf, Weichzeichnen. Neuer Test tests/test_vertex_cavity.py. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,15 @@ Abgestimmt auf `A:\eco\EcoGame` (UE 5.8). Kurzfassung:
|
|||||||
Platten), `Fugenbreite` (klein = harte Fuge, flache Platte), `Platten flach`
|
Platten), `Fugenbreite` (klein = harte Fuge, flache Platte), `Platten flach`
|
||||||
(1 = Flaeche nahezu einfarbig). Ein dichtes Zellmuster ueber die ganze
|
(1 = Flaeche nahezu einfarbig). Ein dichtes Zellmuster ueber die ganze
|
||||||
Oberflaeche erschlaegt die Form und passt NICHT zu diesem Look.
|
Oberflaeche erschlaegt die Form und passt NICHT zu diesem Look.
|
||||||
|
- **Texturfreie Alternative — Vertex-Cavity:** Statt zu baken laesst sich die
|
||||||
|
Cavity in die **Vertex-Farben** schreiben (Attribut `Cavity`, ueberlebt den
|
||||||
|
FBX-Roundtrip verlustfrei — gemessen 0.250..0.947 vor und nach dem Import).
|
||||||
|
Das Material braucht dann **keine Textur**:
|
||||||
|
`Base Color = Farbton x VertexColor`, dazu Kantenlicht (Fresnel) und
|
||||||
|
Schattentint. Vorteil gegenueber Triplanar: die dunklen Fugen sitzen auf den
|
||||||
|
ECHTEN Kanten des Meshes statt darueberzuschweben. Grenze: die Aufloesung
|
||||||
|
haengt an der Vertex-Dichte — fuer flaechige Platten reicht sie, fuer feine
|
||||||
|
Struktur nicht.
|
||||||
- 1024 px reicht fuer stylized Formen.
|
- 1024 px reicht fuer stylized Formen.
|
||||||
- Tileable (4D-Torus) passt zum triplanar mappenden Master-Material.
|
- Tileable (4D-Torus) passt zum triplanar mappenden Master-Material.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
schema_version = "1.0.0"
|
schema_version = "1.0.0"
|
||||||
|
|
||||||
id = "stylized_rock_generator"
|
id = "stylized_rock_generator"
|
||||||
version = "2.7.0"
|
version = "2.8.0"
|
||||||
name = "Stylized Rock Generator"
|
name = "Stylized Rock Generator"
|
||||||
tagline = "Batch-Generator fuer stylized Rocks mit UE5-Export"
|
tagline = "Batch-Generator fuer stylized Rocks mit UE5-Export"
|
||||||
maintainer = "D4rkst3r"
|
maintainer = "D4rkst3r"
|
||||||
|
|||||||
Vendored
+4
-4
@@ -7,7 +7,7 @@
|
|||||||
"id": "stylized_rock_generator",
|
"id": "stylized_rock_generator",
|
||||||
"name": "Stylized Rock Generator",
|
"name": "Stylized Rock Generator",
|
||||||
"tagline": "Batch-Generator fuer stylized Rocks mit UE5-Export",
|
"tagline": "Batch-Generator fuer stylized Rocks mit UE5-Export",
|
||||||
"version": "2.7.0",
|
"version": "2.8.0",
|
||||||
"type": "add-on",
|
"type": "add-on",
|
||||||
"maintainer": "D4rkst3r",
|
"maintainer": "D4rkst3r",
|
||||||
"license": [
|
"license": [
|
||||||
@@ -23,9 +23,9 @@
|
|||||||
"Mesh",
|
"Mesh",
|
||||||
"Modeling"
|
"Modeling"
|
||||||
],
|
],
|
||||||
"archive_url": "./stylized_rock_generator-2.7.0.zip",
|
"archive_url": "./stylized_rock_generator-2.8.0.zip",
|
||||||
"archive_size": 18091,
|
"archive_size": 19166,
|
||||||
"archive_hash": "sha256:2a3b20b6a915271e10b5246b77fc89361cab3dab2ead032aff4e4cd3526ccf33"
|
"archive_hash": "sha256:4c05d56bd4962caf7ce2bddd086ecbf0f6e34eb7fc045f35770f8e6d5267b7ad"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"schema_version": "1.0.0",
|
"schema_version": "1.0.0",
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
bl_info = {
|
bl_info = {
|
||||||
"name": "Stylized Rock Generator",
|
"name": "Stylized Rock Generator",
|
||||||
"author": "D4rkst3r",
|
"author": "D4rkst3r",
|
||||||
"version": (2, 7, 0),
|
"version": (2, 8, 0),
|
||||||
"blender": (4, 2, 0),
|
"blender": (4, 2, 0),
|
||||||
"location": "View3D > Sidebar > Rock Gen",
|
"location": "View3D > Sidebar > Rock Gen",
|
||||||
"description": (
|
"description": (
|
||||||
@@ -60,6 +60,7 @@ PRESET_KEYS = (
|
|||||||
"shading_mode", "autosmooth_angle",
|
"shading_mode", "autosmooth_angle",
|
||||||
"randomize_shape", "noise_scale_max", "displace_strength_max", "nonuniform_scale",
|
"randomize_shape", "noise_scale_max", "displace_strength_max", "nonuniform_scale",
|
||||||
"flatten_bottom", "flatten_ratio", "set_origin_bottom", "keep_size",
|
"flatten_bottom", "flatten_ratio", "set_origin_bottom", "keep_size",
|
||||||
|
"bake_vertex_cavity", "cavity_strength", "cavity_blur", "cavity_gradient",
|
||||||
"use_class_size", "base_size",
|
"use_class_size", "base_size",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -453,6 +454,65 @@ def _apply_random_transform(context, obj, settings, rng):
|
|||||||
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
|
bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _bake_vertex_cavity(context, obj, settings):
|
||||||
|
"""Cavity + Hoehen-Verlauf in die VERTEX-FARBEN backen.
|
||||||
|
|
||||||
|
Warum: das Material braucht damit KEINE Textur. Base Color = Farbton x
|
||||||
|
Vertex-Farbe, dazu Kantenlicht und Schattentint im Shader - genau der
|
||||||
|
Genshin-Aufbau. Ausserdem sitzen die dunklen Fugen auf den ECHTEN Kanten
|
||||||
|
des Meshes, statt per Triplanar darueberzuschweben.
|
||||||
|
|
||||||
|
Aufloesung haengt an der Vertex-Dichte; fuer flaechige Platten reicht das,
|
||||||
|
fuer feine Struktur nicht (dann Textur nehmen).
|
||||||
|
"""
|
||||||
|
me = obj.data
|
||||||
|
layer = me.color_attributes.get("Cavity")
|
||||||
|
if layer is None:
|
||||||
|
layer = me.color_attributes.new(name="Cavity", type='BYTE_COLOR', domain='CORNER')
|
||||||
|
me.color_attributes.active_color = layer
|
||||||
|
me.color_attributes.render_color_index = me.color_attributes.find("Cavity")
|
||||||
|
|
||||||
|
_deselect_all(context)
|
||||||
|
context.view_layer.objects.active = obj
|
||||||
|
obj.select_set(True)
|
||||||
|
try:
|
||||||
|
bpy.ops.paint.vertex_color_set(color=(1.0, 1.0, 1.0, 1.0))
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
bpy.ops.paint.vertex_color_dirt(
|
||||||
|
blur_strength=1.0,
|
||||||
|
blur_iterations=settings.cavity_blur,
|
||||||
|
clean_angle=3.14159,
|
||||||
|
dirt_angle=0.0,
|
||||||
|
dirt_only=False,
|
||||||
|
normalize=True,
|
||||||
|
)
|
||||||
|
except RuntimeError as exc:
|
||||||
|
raise RockGenError("Vertex-Cavity fehlgeschlagen: %s" % exc)
|
||||||
|
|
||||||
|
# Staerke regeln + optionaler Hoehen-Verlauf (unten dunkler, wie in der
|
||||||
|
# Referenz). Beides direkt auf den Farbwerten, das ist schnell genug.
|
||||||
|
zs = [v.co.z for v in me.vertices]
|
||||||
|
if not zs:
|
||||||
|
return
|
||||||
|
zmin, zmax = min(zs), max(zs)
|
||||||
|
span = max(zmax - zmin, 1e-6)
|
||||||
|
amt = settings.cavity_strength
|
||||||
|
grad = settings.cavity_gradient
|
||||||
|
data = layer.data
|
||||||
|
for poly in me.polygons:
|
||||||
|
for li in poly.loop_indices:
|
||||||
|
vi = me.loops[li].vertex_index
|
||||||
|
c = data[li].color
|
||||||
|
val = 1.0 - (1.0 - c[0]) * amt # Cavity abschwaechen
|
||||||
|
if grad > 0.0:
|
||||||
|
t = (me.vertices[vi].co.z - zmin) / span
|
||||||
|
val *= (1.0 - grad) + grad * (0.35 + 0.65 * t)
|
||||||
|
val = max(0.0, min(1.0, val))
|
||||||
|
data[li].color = (val, val, val, 1.0)
|
||||||
|
|
||||||
|
|
||||||
def _apply_shading(context, obj, settings):
|
def _apply_shading(context, obj, settings):
|
||||||
"""Setzt den Shading-Look: FLAT (facettiert), SMOOTH oder AUTO (nach Winkel)."""
|
"""Setzt den Shading-Look: FLAT (facettiert), SMOOTH oder AUTO (nach Winkel)."""
|
||||||
_deselect_all(context)
|
_deselect_all(context)
|
||||||
@@ -607,6 +667,8 @@ def make_one_rock(context, settings, index, batch_count=1):
|
|||||||
_apply_hardsurface(context, obj, settings)
|
_apply_hardsurface(context, obj, settings)
|
||||||
_apply_random_transform(context, obj, settings, rng)
|
_apply_random_transform(context, obj, settings, rng)
|
||||||
_apply_shading(context, obj, settings)
|
_apply_shading(context, obj, settings)
|
||||||
|
if settings.bake_vertex_cavity:
|
||||||
|
_bake_vertex_cavity(context, obj, settings)
|
||||||
if settings.set_origin_bottom:
|
if settings.set_origin_bottom:
|
||||||
_set_origin_bottom(context, obj)
|
_set_origin_bottom(context, obj)
|
||||||
if settings.assign_material:
|
if settings.assign_material:
|
||||||
@@ -730,6 +792,27 @@ class RockGenSettings(PropertyGroup):
|
|||||||
description="Setzt den Origin auf die Unterseiten-Mitte (sauberes Aufstellen in UE)",
|
description="Setzt den Origin auf die Unterseiten-Mitte (sauberes Aufstellen in UE)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# --- Vertex-Cavity (texturfreier Weg) ---
|
||||||
|
bake_vertex_cavity: BoolProperty(
|
||||||
|
name="Cavity in Vertex-Farben", default=False,
|
||||||
|
description=("Backt Cavity/AO in die Vertex-Farben (Attribut 'Cavity'). "
|
||||||
|
"Das Material braucht dann KEINE Textur: Base Color = Farbton "
|
||||||
|
"x Vertex-Farbe, Rest ueber Kantenlicht und Schattentint"),
|
||||||
|
)
|
||||||
|
cavity_strength: FloatProperty(
|
||||||
|
name="Cavity-Staerke", default=0.7, min=0.0, max=1.0,
|
||||||
|
description="0 = alles weiss, 1 = volle Abdunklung in den Vertiefungen",
|
||||||
|
)
|
||||||
|
cavity_blur: IntProperty(
|
||||||
|
name="Weichzeichnen", default=1, min=0, max=5,
|
||||||
|
description="Glaettet den Cavity-Verlauf ueber benachbarte Vertices",
|
||||||
|
)
|
||||||
|
cavity_gradient: FloatProperty(
|
||||||
|
name="Hoehen-Verlauf", default=0.35, min=0.0, max=1.0,
|
||||||
|
description=("Dunkelt den Stein nach unten hin ab (wie in der Referenz). "
|
||||||
|
"0 = aus"),
|
||||||
|
)
|
||||||
|
|
||||||
# --- Material ---
|
# --- Material ---
|
||||||
assign_material: BoolProperty(
|
assign_material: BoolProperty(
|
||||||
name="Material zuweisen", default=False,
|
name="Material zuweisen", default=False,
|
||||||
@@ -1567,6 +1650,15 @@ class VIEW3D_PT_rock_generator(Panel):
|
|||||||
sub.prop(settings, "lod_count")
|
sub.prop(settings, "lod_count")
|
||||||
sub.prop(settings, "lod_ratio_step")
|
sub.prop(settings, "lod_ratio_step")
|
||||||
|
|
||||||
|
box = layout.box()
|
||||||
|
box.label(text="Vertex-Cavity (ohne Textur)")
|
||||||
|
box.prop(settings, "bake_vertex_cavity")
|
||||||
|
sub = box.column(align=True)
|
||||||
|
sub.enabled = settings.bake_vertex_cavity
|
||||||
|
sub.prop(settings, "cavity_strength")
|
||||||
|
sub.prop(settings, "cavity_gradient")
|
||||||
|
sub.prop(settings, "cavity_blur")
|
||||||
|
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
box.label(text="Material")
|
box.label(text="Material")
|
||||||
box.prop(settings, "assign_material")
|
box.prop(settings, "assign_material")
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import bpy, sys, importlib.util, os, tempfile
|
||||||
|
spec=importlib.util.spec_from_file_location("srg", r"A:\eco\stylized_rock_generator\stylized_rock_generator.py")
|
||||||
|
r=importlib.util.module_from_spec(spec); sys.modules["srg"]=r; spec.loader.exec_module(r)
|
||||||
|
try: r.unregister()
|
||||||
|
except Exception: pass
|
||||||
|
r.register()
|
||||||
|
fails=[]
|
||||||
|
for o in list(bpy.data.objects): bpy.data.objects.remove(o,do_unlink=True)
|
||||||
|
s=bpy.context.scene.rock_gen_settings
|
||||||
|
s.batch_count=1; s.subsurf_levels=4; s.use_modal=False
|
||||||
|
s.shading_mode='FLAT'; s.bake_vertex_cavity=True
|
||||||
|
s.name_prefix="Fels"; s.name_form="A"; s.name_class='M'
|
||||||
|
res=bpy.ops.object.generate_rocks_batch()
|
||||||
|
obs=[o for o in bpy.context.scene.objects if o.type=='MESH']
|
||||||
|
ob=obs[0]
|
||||||
|
me=ob.data
|
||||||
|
names=[a.name for a in me.color_attributes]
|
||||||
|
print("FARB-ATTRIBUTE:", names)
|
||||||
|
if "Cavity" not in names: fails.append("kein_attribut")
|
||||||
|
else:
|
||||||
|
vals=[d.color[0] for d in me.color_attributes["Cavity"].data]
|
||||||
|
vals.sort()
|
||||||
|
print("CAVITY min=%.3f p10=%.3f median=%.3f max=%.3f (n=%d)"
|
||||||
|
% (vals[0], vals[len(vals)//10], vals[len(vals)//2], vals[-1], len(vals)))
|
||||||
|
if vals[-1]-vals[0] < 0.05: fails.append("kein_kontrast")
|
||||||
|
# Faceted?
|
||||||
|
sm=sum(1 for p in me.polygons if p.use_smooth)
|
||||||
|
print("Shading: smooth=%d / flat=%d" % (sm, len(me.polygons)-sm))
|
||||||
|
# FBX-Roundtrip: ueberleben die Vertex-Farben?
|
||||||
|
tmp=tempfile.mkdtemp(); fp=os.path.join(tmp,"cav.fbx")
|
||||||
|
for o in bpy.context.selected_objects: o.select_set(False)
|
||||||
|
ob.select_set(True); bpy.context.view_layer.objects.active=ob
|
||||||
|
bpy.ops.export_scene.fbx(filepath=fp, use_selection=True, colors_type='SRGB',
|
||||||
|
mesh_smooth_type='FACE', use_triangles=True)
|
||||||
|
for o in list(bpy.data.objects): bpy.data.objects.remove(o,do_unlink=True)
|
||||||
|
bpy.ops.import_scene.fbx(filepath=fp)
|
||||||
|
imp=[o for o in bpy.data.objects if o.type=='MESH'][0]
|
||||||
|
inames=[a.name for a in imp.data.color_attributes]
|
||||||
|
print("NACH FBX-ROUNDTRIP:", inames, "Tris=%d" % sum(len(p.vertices)-2 for p in imp.data.polygons))
|
||||||
|
if not inames: fails.append("fbx_verliert_farben")
|
||||||
|
else:
|
||||||
|
iv=[d.color[0] for d in imp.data.color_attributes[0].data]
|
||||||
|
print(" Wertebereich nach Import: %.3f .. %.3f" % (min(iv), max(iv)))
|
||||||
|
r.unregister()
|
||||||
|
print("ERGEBNIS:", "ALLE CHECKS OK" if not fails else "FEHLER: "+", ".join(fails))
|
||||||
Reference in New Issue
Block a user