Low-Poly-Modellierung in Blender
Low Poly ist ein Stil, nicht nur eine niedrige Dreiecksanzahl: große, lesbare Formen, sichtbare flache Facetten, eine Handvoll einfarbiger Farben und Proportionen, die ein wenig über die Realität hinausgehen. Ein realistisches Modell mit weniger Polygonen ist kein Low Poly; es ist ein grobes realistisches Modell.
Der allgemeine Arbeitsablauf (Szene betrachten, in Teilen planen, ein Teil pro Aufruf, Screenshot nach jedem Schritt, niemals löschen, was du nicht erstellt hast) ist im blender-modeling-Skill beschrieben: Folge ihm. Dieses Skill beschreibt, was für Low Poly anders ist.
Deine Blender-Verbindung
Du arbeitest über BlenderLab, den offiziellen Blender-Lab-Server; seine Werkzeuge heißen blenderlab_…:
- Was in der Szene ist:
get_objects_summary; ein Objekt im Detail:get_object_detail_summary(nachname). Die Datei, gespeichert oder nicht, und ihr Pfad:get_blendfile_summary_path_info. - Python ausführen:
execute_blender_code. Nichts bleibt zwischen Aufrufen erhalten (trage deine Helfer in jedes Skript); was das Skript ausgibt, kommt zurück, und ein Dict, dasresultzugewiesen wird, kommt als Daten zurück. Seine Schutzmechanismen verweigern einige Operatoren (Blender beenden, Werkseinstellungen). - Das 3D-Viewport sehen:
get_screenshot_of_area_as_imagemitarea_ui_type: "VIEW_3D"(das ganze Fenster:get_screenshot_of_window_as_image); auf ein Objekt ausrichten:jump_to_view3d_object_by_name. - Die Python-API überprüfen:
get_python_api_docs(ein Bezeichner wiebpy.types.BevelModifier),search_api_docs; das Handbuch:search_manual_docs. - Schnelles Rendern:
render_thumbnail_to_path,render_viewport_to_path; das Bild geht in Blenders temporären Ordner und das Ergebnis gibt den echten Pfad zurück. - Exportieren: Code (
bpy.ops.export_scene.gltf,bpy.ops.export_scene.fbx). - Asset-Bibliotheken und 3D-Generatoren: keine. Baue das Objekt oder frage den Benutzer nach einer zu importierenden Datei.
Falls diese Werkzeuge nicht verfügbar sind, stoppe und sage das: Der Benutzer muss die BlenderLab-Verbindung installieren (Extras → Verbindungen), sie diesem Agenten geben und Blender mit gestartetem Add-On-Server geöffnet haben. Beschreibe keine Arbeit als erledigt, die du nicht ausführen konntest.
Der Stil, als Regeln
- Flache Schattierung überall. Jede Fläche hat eine einzige flache Farbe; keine glatte Schattierung, kein Auto Smooth. Das macht die Facetten sichtbar.
- Wenige Segmente, bewusst. Zylinder 6–8 Seiten, Kegel 5–6, Kugeln als Ikosphären mit 1–2 Unterteilungen. Ein 32-seitiger Zylinder ruiniert den Look mehr als alles andere.
- Kein Bevel, kein Subdivision Surface. Sie runden die Facetten ab. Für eine weichere Kante höchstens eine einsegmentige Fase.
- Einfarbig, keine Texturen. Ein Material pro Farbe, Principled BSDF mit Basis-Farbe, Rauheit 0,8–1,0, Metallisch 0, kein spiegelnder Glanz. Materialien wiederverwenden: Eine ganze Szene braucht normalerweise 6–12.
- Eine bewusste Palette. Wähle sie vor dem Modellieren: 1–2 dominante Farben, 2–3 unterstützende, 1 Akzent. Leicht entsättigte Mitteltöne, warm und kühl ausbalanciert, und zwei Töne jeder Hauptfarbe (hell und dunkel) für Abwechslung zwischen benachbarten Teilen.
- Klobige, übertriebene Proportionen. Dickere Wände, Balken und Stämme als im echten Leben; übergroße Dächer, Türen und Fenster; sich verjüngende Formen (breiter an der Basis oder oben für ein Cartoon-Gefühl). Dünne Teile verschwinden bei diesem Detailgrad.
- Unvollkommenheit von Hand, in kleinen Dosen. Nichts perfekt ausgerichtet: Neige einen Schornstein um 2–4°, variiere Dachziegel um ein paar Prozent ihrer Größe und 2–3° ihrer Neigung, verschiebe Eckpunkte von Felsen und Gelände zufällig. Die Struktur muss noch lesbar sein: Reihen von Ziegeln bleiben Reihen, kein Teil wird seitlich gedreht oder von der Oberfläche gehoben. Mit einem festen Zufallssamen, sodass eine erneute Ausführung das gleiche Ergebnis liefert.
- Detail durch separate kleine Teile, nicht durch Unterteilen. Ein Fenster ist ein Rahmen aus vier Quadern plus einer Scheibe; Dachziegel sind Reihen kleiner Platten; Steine sind ein paar unregelmäßige Blöcke, die in die Mauer gesetzt sind. Jedes Teil ist einfach; die Anordnung ergibt den Reichtum.
- Teile dürfen sich durchdringen. Low-Poly-Teile werden ineinander geschoben, statt verschweißt: Ein Balken sinkt in eine Wand, ein Fels in den Boden. Gib dich nicht mit Booleschen Operationen oder sauberen Verbindungen ab.
Arbeitsablauf
- Lies die Referenz, falls vorhanden: Liste die Teile auf, zähle, was sich wiederholt (Reihen von Ziegeln, Anzahl der Balken, Fenster pro Wand), notiere die Proportionen zwischen Teilen als Verhältnisse (Dachhöhe zu Wandhöhe, Breite zu Höhe) und benenne die Palettenfarben. Baue von dieser Liste aus. Das Bild entscheidet: Verankere eine Größe und leite den Rest aus den Verhältnissen ab, erfinde keine Größen in Metern, die das Bild widerlegt, und füge nichts hinzu, was es nicht zeigt (eine Basis, Requisiten), es sei denn, es wird danach gefragt.
- Blockout zuerst die großen Massen mit Boxen und Keilen in den richtigen Proportionen. Mache einen Screenshot und vergleiche die Silhouette: Wenn die Silhouette falsch ist, werden Details sie nicht retten.
- Palette: Erstelle die Materialien einmal, benannt nach dem, was sie färben (
LP_Wand_Hell,LP_Dach_Rot,LP_Holz_Dunkel). - Struktur, dann sekundäre Teile (Balken, Rahmen, Schornstein, Stufen), dann kleine Details (Ziegel, Steine, Bretter, Risse), dann Umgebung (Bodenplatte, ein paar Felsen, Grasbüschel).
- Vergröbern: Die zufälligen Versätze und Neigungen aus Regel 7.
- Licht und Rahmen (unten), dann das Viewport auf den Winkel der Referenz ausrichten, erfassen und Teil für Teil vergleichen: Silhouette, Proportionen, wo jedes Teil sitzt, Farben. Korrigieren.
- Beende mit
final_check, dem letzten Aufruf der Aufgabe: Die Funktion ist unten unter „Der abschließende Check“. Du siehst deine Render nicht; sie misst das Rendering für dich (das Modell getrennt vom Hintergrund, gegen das Referenzbild, wenn du dessen Dateinamen hast) und gibt einePROBLEM:-Zeile für das aus, was falsch ist. Korrigiere, führe es erneut aus (höchstens zwei Durchläufe) und füge seine Ausgabe in den Bericht ein. Messungen, die du selbst vornimmst, ersetzen es nicht: Eine Aufgabe, die ein Modell erstellt oder ändert, ist erst beendet, wenn diese Funktion gelaufen ist.
Erwarte zwei oder drei Korrekturrunden gegen die Referenz. Sage, was noch abweicht.
Du hast eine begrenzte Anzahl von Werkzeugrunden pro Aufgabe (siehe „Dein Schrittbudget“ in blender-modeling): Baue zwei oder drei verwandte Teile pro Aufruf, erfasse bei den Meilensteinen (Blockout, Details, Finale) und setze die sichtbarsten Teile zuerst.
Code für jedes Rezept ist in references/low-poly-recipes.md: Palette, flache Schattierung, sich verjüngende Boxen, Satteldächer, Reihen von Ziegeln, Fenster und Türen, Fachwerk, Felsen, Bäume, Gelände, Streuen und die Beleuchtungseinrichtung. Öffne es vor dem ersten Skript.
Viele Blöcke in einem Objekt
Steinreihen, Zinnen, Pflaster, Bretter und Ziegel sind viele kleine Boxen, die in ein Objekt gehören. Baue sie mit lp_blocks: Es beendet jede Box in ihrem eigenen Bmesh und fügt sie erst dann dem Objekt hinzu. Verbinde keine Boxen, indem du Scheitel- und Flächenlisten von Hand anfügst, und verschiebe nicht „die letzten N Scheitelpunkte“ nach einer Operation, die ihre Anzahl ändert (das tut eine Fase): Die Flächen landen auf den falschen Scheitelpunkten und das Modell kommt als gestreckte Spitzen heraus. final_check benennt ein so kaputtes Objekt; baue es neu, melde es nicht.
import bpy, bmesh
from mathutils import Matrix, Vector
def lp_blocks(name, col, mat, blocks, chamfer=0.03):
"""Viele Boxen in EINEM flach schattierten Objekt. blocks ist eine Liste von (Mittelpunkt, Größe) oder
(Mittelpunkt, Größe, z_Drehung): Mittelpunkt und Größe sind (x, y, z) in Metern, die Drehung in Bogenmaß.
Jede Box wird in ihrem eigenen Bmesh beendet und erst dann dem Objekt hinzugefügt."""
whole = bmesh.new()
for center, size, *rot in blocks:
bm = bmesh.new()
bmesh.ops.create_cube(bm, size=1.0)
bmesh.ops.scale(bm, vec=Vector(size), verts=bm.verts)
if chamfer > 0:
bmesh.ops.bevel(bm, geom=bm.edges[:], offset=min(chamfer, min(size) * 0.25), segments=1, affect='EDGES')
bmesh.ops.transform(bm, matrix=Matrix.Translation(Vector(center)) @ Matrix.Rotation(rot[0] if rot else 0.0, 4, 'Z'), verts=bm.verts)
piece = bpy.data.meshes.new("_piece")
bm.to_mesh(piece)
bm.free()
whole.from_mesh(piece) # fügt die fertige Box dem Objekt-Mesh hinzu
bpy.data.meshes.remove(piece)
mesh = bpy.data.meshes.new(name)
whole.to_mesh(mesh)
whole.free()
for poly in mesh.polygons:
poly.use_smooth = False
obj = bpy.data.objects.get(name)
if obj is None:
obj = bpy.data.objects.new(name, mesh)
col.objects.link(obj)
else:
obj.data = mesh
obj.data.materials.append(mat)
return obj
# Eine Reihe von Wandsteinen, abwechselnd zwei Größen, in einem einzigen Objekt.
col = bpy.data.collections["LP_Model"]
stone = bpy.data.materials["LP_Stone_Light"]
lp_blocks("Wall_Course_01", col, stone,
[((x * 0.42 - 1.26, -1.0, 0.35), (0.40 if x % 2 else 0.36, 0.20, 0.30)) for x in range(7)])
Formen und wie man sie bekommt
| Gewünscht | Baue es als |
|---|---|
| Wände, Balken, Bretter, Stufen | Boxen, skaliert; oben oder unten verjüngt für Charakter |
| Steinblöcke, Zinnen, Pflaster, Ziegel | lp_blocks (oben): viele gefaste Boxen in einem Objekt |
| Satteldach | Ein Prisma (Dreiecksprofil extrudiert), das auf allen Seiten über die Wände überhängt |
| Dachziegel | Reihen dünner Platten auf der Dachschräge, jede Reihe überlappt die darunter, mit kleinen zufälligen Größen und Neigungen |
| Schornstein, Turm, Pfeiler | Verjüngte Box; Abschlussplatte etwas breiter |
| Baum | Stamm: 5–6-seitiger verjüngter Zylinder. Laub: 2–3 gestapelte Kegel (Kiefer) oder 1–3 verschobene Ikosphären (Laubbaum) |
| Fels | Ikosphäre mit 1 Unterteilung, Scheitelpunkte zufällig verschoben, in Z gestaucht, in den Boden versenkt |
| Gelände, Insel | Ein Gitter mit wenigen Schnitten, Scheitelpunkte durch Rauschen angehoben, dann flach schattiert; oder eine dicke unregelmäßige Platte als Dioramenbasis |
| Wasser | Eine flache Ebene oder Platte, einfarbig blau, leicht glänzend (Rauheit 0,2) |
| Gras, Büsche | Kleine Kegel oder gestauchte Ikosphären in Gruppen |
| Runde Dinge (Fass, Rad, Brunnen) | 6–8-seitige Zylinder |
Budgets
Ungefähre Dreiecksanzahlen, um zu beurteilen, ob etwas überbaut ist:
| Asset | Dreiecke |
|---|---|
| Kleine Requisite (Fels, Kiste, Busch) | 20–150 |
| Baum | 60–300 |
| Haus oder Gebäude | 500–3,000 |
| Figur | 300–1,500 |
| Ganzes Diorama | 3,000–15,000 |
Gib die Anzahl aus, wenn du fertig bist. Weit über dem Budget bedeutet normalerweise zu viele Segmente oder einen Modifikator, der unterteilt.
Licht und Präsentation
Der Stil hängt vom Licht ab: Facetten werden nur sichtbar, wenn benachbarte Flächen unterschiedliche Helligkeit erhalten.
- Eine Sonne in einem Winkel (etwa 45° nach oben, 30–45° zur Seite), Stärke 3–4, mit weichen Schatten; plus eine Welt in einer hellen Himmelsfarbe bei Stärke 0,6–1,0, damit Schatten nicht schwarz sind. Kein Drei-Punkt-Studio-Setup. Schiebe das Licht nicht nach oben, um einen weißen Hintergrund zu bekommen, und töne die Welt nicht farbig: Das eigene Material des Hintergrunds gibt die Farbe.
- Eine leicht warme Sonne gegen eine leicht kühle Welt ergibt den klassischen Look.
- Setze die Ansichtstransformation auf
Standard, damit die Palettenfarben so bleiben, wie gewählt (die Standardtransformation macht sie grau). - Kamera: Dreiviertelansicht von oben (30–35° nach unten), langes Objektiv (50–85 mm) oder orthografisch für ein Diorama.
- Ein einfarbiger Hintergrund in einer Palettenfarbe oder eine Bodenplatte, die in einer sauberen Kante endet. Eine Hintergrundebene muss viel größer sein als das Modell (20-mal oder mehr), oder die Welt dieselbe Farbe haben: Es darf keine Kante oder Horizont im Bild sichtbar sein.
- EEVEE ist ausreichend und schnell. Weiche Schatten und Umgebungsverdeckung helfen sehr.
Materialien, Rendern in eine Datei und Fehlerbehebung sind in blender-materials-render.
Für eine Game-Engine
Low-Poly-Assets gehen normalerweise in eine Engine. Behalte die flache Schattierung beim Exportieren bei (die Facetten werden als Split-Normals gespeichert, was die Scheitelpunktanzahl erhöht, aber nicht die Dreiecksanzahl), führe die Teile eines Assets in einem Mesh mit wenigen Material-Slots zusammen und setze den Ursprung auf die untere Mitte. Folge für den Rest blender-game-export.
Der abschließende Check (immer dein letzter Aufruf)
Die Render-Werkzeuge schreiben eine Datei und geben dir ihren Pfad: Du siehst das Bild nie. Daher ist der letzte execute_blender_code-Aufruf der Aufgabe final_check, genau so kopiert, und was er ausgibt, kommt so in den Bericht. Es erstellt ein kleines Rendering aus der Kamera der Szene und benennt, was falsch ist: Objekte ohne Material, kaputte Geometrie, ein Modell außerhalb des Rahmens, ein verbranntes oder dunkles Rendering, und, mit dem Dateinamen der Referenz, die Unterschiede in Bildausschnitt, Helligkeit, Farbe und wie solide die Silhouette ist.
import bpy, os, re
import numpy as np
from mathutils import Vector
from bpy_extras.object_utils import world_to_camera_view
BACKDROP_WORDS = ("ground", "backdrop", "floor", "sky", "shadow")
def subject_stats(path, size=128):
"""Ein Bild aufgeteilt in Hintergrund (die Farben seiner vier Ecken) und Motiv (der Rest):
Wie viel des Bildes das Motiv einnimmt, seine Box, seine Helligkeit, Sättigung und Farben."""
img = bpy.data.images.load(path, check_existing=False)
w, h = img.size
sw, sh = (size, max(8, round(size * h / w))) if w >= h else (max(8, round(size * w / h)), size)
img.scale(sw, sh)
px = np.empty(len(img.pixels), dtype=np.float32)
img.pixels.foreach_get(px)
bpy.data.images.remove(img)
rgb = px.reshape(sh, sw, 4)[:, :, :3] # Zeile 0 ist der untere Bildrand
cy, cx = max(2, sh // 8), max(2, sw // 8)
corners = np.concatenate([rgb[:cy, :cx].reshape(-1, 3), rgb[:cy, -cx:].reshape(-1, 3),
rgb[-cy:, :cx].reshape(-1, 3), rgb[-cy:, -cx:].reshape(-1, 3)])
# Hintergrund = nahe einer der Eckfarben (ein Farbverlauf oder ein weicher Schatten zählt noch).
q = np.unique(np.round(corners * 8).astype(int), axis=0)
flat = rgb.reshape(-1, 3)
dist = np.min(np.abs(flat[:, None, :] * 8 - q[None, :, :]).max(axis=2), axis=1)
subject = (dist > 1.0).reshape(sh, sw)
share = float(subject.mean())
whole = flat @ np.array([0.2126, 0.7152, 0.0722], dtype=np.float32)
out = {"size": (w, h), "image_brightness": round(float(whole.mean()), 3), "subject_pct": round(100 * share, 1)}
if share < 0.005:
return
…