Modélisation low-poly dans Blender
Le low poly est un style, pas seulement un faible nombre de triangles : de grandes formes lisibles, des facettes plates visibles, une poignée de couleurs unies, et des proportions un peu poussées au-delà de la réalité. Un modèle réaliste avec moins de polygones n'est pas low-poly ; c'est un modèle réaliste grossier.
La boucle de travail générale (regarder la scène, planifier en parties, une partie par appel, capture d'écran après chaque étape, ne jamais supprimer ce que vous n'avez pas créé) se trouve dans la compétence blender-modeling : suivez-la. Cette compétence indique ce qui est différent pour le low poly.
Votre connexion Blender
Vous travaillez via BlenderLab, le serveur officiel du Blender Lab ; ses outils sont
blenderlab_… :
- Ce qui est dans la scène :
get_objects_summary; un objet en détail :get_object_detail_summary(parname). Le fichier, sauvegardé ou non, et son chemin :get_blendfile_summary_path_info. - Exécuter Python :
execute_blender_code. Rien ne persiste entre les appels (transportez vos fonctions d'aide dans chaque script) ; ce que le script affiche est renvoyé, et un dictionnaire assigné àresultest renvoyé comme données. Son garde-fou refuse quelques opérateurs (quitter Blender, réinitialisations d'usine). - Voir la vue 3D :
get_screenshot_of_area_as_imageavecarea_ui_type: "VIEW_3D"(la fenêtre entière :get_screenshot_of_window_as_image) ; la pointer sur un objet :jump_to_view3d_object_by_name. - Consulter l'API Python :
get_python_api_docs(un identifiant tel quebpy.types.BevelModifier),search_api_docs; le manuel :search_manual_docs. - Rendu rapide :
render_thumbnail_to_path,render_viewport_to_path; l'image va dans le dossier temporaire de Blender et le résultat donne le vrai chemin. - Exporter : du code (
bpy.ops.export_scene.gltf,bpy.ops.export_scene.fbx). - Bibliothèques d'actifs et générateurs 3D : aucun. Construisez l'objet, ou demandez à l'utilisateur un fichier à importer.
Si ces outils ne sont pas disponibles, arrêtez-vous et dites-le : l'utilisateur doit installer la connexion BlenderLab (Outils → Connexions), la donner à cet agent, et avoir Blender ouvert avec le serveur du module complémentaire démarré. Ne décrivez pas le travail comme terminé quand vous n'avez pas pu le faire.
Le style, en règles
- Ombrage plat partout. Chaque face est d'une seule couleur plate ; pas d'ombrage lisse, pas d'auto-lissage. C'est ce qui fait apparaître les facettes.
- Peu de segments, délibérément. Cylindres à 6-8 côtés, cônes à 5-6, sphères en icosphères avec 1-2 subdivisions. Un cylindre à 32 côtés gâche le look plus que tout autre chose.
- Pas de biseau, pas de surface de subdivision. Ils arrondissent les facettes. Pour un bord plus doux utilisez au maximum un chanfrein à un seul segment.
- Couleur unie, pas de textures. Un matériau par couleur, Principled BSDF avec Base Color, Rugosité 0.8-1.0, Métallique 0, pas d'éclat spéculaire. Réutilisez les matériaux : une scène entière en nécessite généralement 6 à 12.
- Une palette réfléchie. Choisissez-la avant la modélisation : 1-2 couleurs dominantes, 2-3 de soutien, 1 accent. Des demi-teintes légèrement désaturées, des tons chauds et froids équilibrés, et deux nuances de chaque couleur principale (claire et foncée) pour la variété entre les parties voisines.
- Proportions massives et exagérées. Murs, poutres et troncs plus épais que dans la réalité ; toits, portes et fenêtres surdimensionnés ; formes fuselées (plus larges à la base, ou au sommet pour un effet cartoon). Les parties fines disparaissent à ce niveau de détail.
- Imperfection à la main, en petites doses. Rien de parfaitement aligné : inclinez une cheminée de 2-4°, variez les tuiles de quelques pourcents de leur taille et de 2-3° d'inclinaison, déplacez les sommets des rochers et du terrain aléatoirement. La structure doit rester lisible : les rangées de tuiles restent des rangées, aucune pièce tournée sur le côté ou soulevée de la surface. Avec une graine aléatoire fixe, pour qu'une ré-exécution donne le même résultat.
- Détail par petites pièces séparées, pas par subdivision. Une fenêtre est un cadre de quatre boîtes plus un carreau ; les tuiles sont des rangées de petites dalles ; les pierres sont quelques blocs irréguliers encastrés dans le mur. Chaque pièce est simple ; l'agencement donne la richesse.
- Les pièces peuvent s'intersecter. Les parties low-poly sont poussées les unes dans les autres au lieu d'être soudées : une poutre s'enfonce dans un mur, un rocher dans le sol. Ne consacrez pas d'efforts aux booléens ou aux jonctions propres.
Flux de travail
- Lisez la référence, s'il y en a une : listez les pièces, comptez ce qui se répète (rangées de tuiles, nombre de poutres, fenêtres par mur), notez les proportions entre les pièces sous forme de ratios (hauteur du toit par rapport à la hauteur du mur, largeur par rapport à la hauteur), et nommez les couleurs de la palette. Construisez à partir de cette liste. L'image décide : ancrez une taille et déduisez le reste des ratios, n'inventez pas de tailles en mètres que l'image contredit, et n'ajoutez pas ce qu'elle ne montre pas (un socle, des accessoires) sauf si demandé.
- Blocage des grandes masses d'abord avec des boîtes et des coins aux bonnes proportions. Prenez une capture d'écran et comparez la silhouette : si la silhouette est mauvaise, les détails ne la sauveront pas.
- Palette : créez les matériaux une fois, nommés par ce qu'ils colorent (
LP_Mur_Clair,LP_Toit_Rouge,LP_Bois_Foncé). - Structure, puis pièces secondaires (poutres, cadres, cheminée, marches), puis petits détails (tuiles, pierres, planches, fissures), puis environnement (dalle de sol, quelques rochers, touffes d'herbe).
- Rugosification : les décalages et inclinaisons aléatoires de la règle 7.
- Éclairage et cadrage (ci-dessous), puis placez la vue à l'angle de la référence, capturez-la et comparez pièce par pièce : silhouette, proportions, emplacement de chaque pièce, couleurs. Corrigez.
- Terminez par
final_check, le dernier appel de la tâche : la fonction est ci-dessous, dans "La vérification finale". Vous ne voyez pas vos rendus ; elle mesure le rendu pour vous (le modèle séparé du fond, par rapport à l'image de référence lorsque vous avez son nom de fichier) et imprime une lignePROBLEME:pour ce qui ne va pas. Corrigez, exécutez-la à nouveau (deux tours au maximum) et mettez sa sortie dans le rapport. Les mesures que vous faites vous-même ne la remplacent pas : une tâche qui construit ou modifie un modèle n'est pas terminée tant que cette fonction n'a pas été exécutée.
Attendez-vous à deux ou trois tours de correction par rapport à la référence. Dites ce qui diffère encore.
Vous avez un nombre limité de tours d'outils par tâche (voir "Votre budget d'étapes" dans
blender-modeling) : construisez deux ou trois pièces liées par appel, capturez aux
jalons (blocage, détails, final) et mettez les pièces les plus visibles en premier.
Le code pour chaque recette se trouve dans references/low-poly-recipes.md : palette, ombrage plat, boîtes fuselées, toits à pignon, rangées de tuiles, fenêtres et portes, structures en bois, rochers, arbres, terrain, dispersion et configuration d'éclairage. Ouvrez-le avant le premier script.
Plusieurs blocs dans un seul objet
Les rangées de pierres, créneaux, pavés, planches et briques sont de nombreuses petites boîtes qui appartiennent à
un seul objet. Construisez-les avec lp_blocks : elle termine chaque boîte dans son propre bmesh et seulement
ensuite l'ajoute à l'objet. Ne joignez pas les boîtes en ajoutant manuellement des listes de sommets et de faces,
et ne déplacez pas "les N derniers sommets" après une opération qui change leur nombre (un
chanfrein le fait) : les faces se retrouvent sur les mauvais sommets et le modèle sort sous forme de pointes
étirées. final_check nomme un objet cassé de cette façon ; reconstruisez-le, ne le signalez pas.
import bpy, bmesh
from mathutils import Matrix, Vector
def lp_blocks(name, col, mat, blocks, chamfer=0.03):
"""Plusieurs boîtes dans UN objet à ombrage plat. blocks est une liste de (centre, taille) ou
(centre, taille, rotation_z) : centre et taille sont (x, y, z) en mètres, la rotation en radians.
Chaque boîte est terminée dans son propre bmesh et seulement ensuite ajoutée à l'objet."""
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) # ajoute la boîte terminée au maillage de l'objet
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
# Une rangée de pierres de mur, alternant deux tailles, dans un seul objet.
col = bpy.data.collections["LP_Model"]
pierre = bpy.data.materials["LP_Pierre_Clair"]
lp_blocks("Rangée_Mur_01", col, pierre,
[((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)])
Formes et comment les obtenir
| Voulez | Construisez-le comme |
|---|---|
| Murs, poutres, planches, marches | Boîtes, mises à l'échelle ; fusselez le haut ou le bas pour le caractère |
| Pierres, créneaux, pavés, briques | lp_blocks (ci-dessus) : plusieurs boîtes chanfreinées dans un seul objet |
| Toit à pignon | Un prisme (profil triangulaire extrudé), débordant des murs de tous côtés |
| Tuiles | Rangées de fines dalles sur la pente du toit, chaque rangée chevauchant celle du dessous, avec une petite taille et inclinaison aléatoires |
| Cheminée, tour, pilier | Boîte fuselée ; dalle de capuchon légèrement plus large |
| Arbre | Tronc : cylindre fuselé à 5-6 côtés. Feuillage : 2-3 cônes empilés (pin) ou 1-3 icosphères déplacées (feuillu) |
| Rocher | Icosphere à 1 subdivision, sommets déplacés aléatoirement, écrasée en Z, enfoncée dans le sol |
| Terrain, île | Une grille avec peu de coupes, sommets relevés par du bruit, puis ombrage plat ; ou une dalle épaisse irrégulière pour un socle de diorama |
| Eau | Un plan plat ou une dalle, d'un bleu, légèrement brillant (Rugosité 0.2) |
| Herbe, buissons | Petits cônes ou icosphères écrasées en grappes |
| Objets ronds (tonneau, roue, puits) | Cylindres à 6-8 côtés |
Budgets
Comptages approximatifs de triangles, pour juger si quelque chose est surconstruit :
| Actif | Triangles |
|---|---|
| Petit accessoire (rocher, caisse, buisson) | 20-150 |
| Arbre | 60-300 |
| Maison ou bâtiment | 500-3 000 |
| Personnage | 300-1 500 |
| Diorama entier | 3 000-15 000 |
Imprimez le compte lorsque vous terminez. Bien au-dessus du budget signifie généralement trop de segments ou un modificateur qui subdivise.
Éclairage et présentation
Le style dépend de la lumière : les facettes ne se lisent que lorsque les faces voisines reçoivent une luminosité différente.
- Un soleil en angle (environ 45° vers le haut, 30-45° sur le côté), intensité 3-4, avec des ombres douces ; plus un monde d'une couleur de ciel claire à une intensité de 0.6-1.0 pour que les ombres ne soient pas noires. Pas de configuration studio à trois points. Ne poussez pas la lumière vers le haut pour obtenir un fond blanc, et ne teintez pas le monde pour le colorer : le matériau de l'arrière-plan donne la couleur.
- Un soleil légèrement chaud contre un monde légèrement froid donne le look classique.
- Réglez la transformation de vue sur
Standardpour que les couleurs de la palette restent telles que choisies (la transformation par défaut les grisonne). - Caméra : vue en trois-quarts depuis le dessus (30-35° vers le bas), objectif long (50-85 mm) ou orthographique pour un diorama.
- Un fond uni dans une couleur de la palette, ou une dalle de sol qui se termine par un bord net. Un plan d'arrière-plan doit être bien plus grand que le modèle (20 fois ou plus), ou le monde de la même couleur : aucun bord ou horizon ne doit apparaître dans le cadre.
- EEVEE est suffisant et rapide. Les ombres douces et l'occlusion ambiante aident beaucoup.
Les matériaux, le rendu dans un fichier et le dépannage se trouvent dans blender-materials-render.
Pour un moteur de jeu
Les actifs low-poly vont généralement dans un moteur. Conservez l'ombrage plat lors de l'exportation (les facettes
sont stockées sous forme de normales divisées, ce qui augmente le nombre de sommets mais pas le nombre de triangles),
regroupez les pièces d'un actif en un seul maillage avec quelques emplacements de matériaux, et placez
l'origine au centre inférieur. Suivez blender-game-export pour le reste.
La vérification finale (toujours votre dernier appel)
Les outils de rendu écrivent un fichier et vous donnent son chemin : vous ne voyez jamais l'image. Donc le dernier
appel execute_blender_code de la tâche est final_check, copié tel quel, et ce qu'il
imprime va dans le rapport tel quel. Il fait un petit rendu depuis la caméra de la scène et
nomme ce qui ne va pas : objets sans matériau, géométrie cassée, modèle hors du
cadre, rendu brûlé ou sombre, et, avec le nom du fichier de référence, les différences de
cadrage, luminosité, couleur et solidité de la silhouette.
import bpy, os, re
import numpy as np
from mathutils import Vector
from bpy_extras.object_utils import world_to_camera_view
MOTS_ARRIERE_PLAN = ("sol", "fond", "plancher", "ciel", "ombre")
def stats_sujet(chemin, taille=128):
"""Une image divisée en arrière-plan (les couleurs de ses quatre coins) et sujet (le reste) :
combien de l'image le sujet prend, sa boîte, sa luminosité, saturation et couleurs."""
img = bpy.data.images.load(chemin, check_existing=False)
w, h = img.size
sw, sh = (taille, max(8, round(taille * h / w))) if w >= h else (max(8, round(taille * w / h)), taille)
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] # la ligne 0 est le bas de l'image
cy, cx = max(2, sh // 8), max(2, sw // 8)
coins = 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)])
# Arrière-plan = proche d'une des couleurs des coins (un dégradé ou une ombre douce compte toujours).
q = np.unique(np.round(coins * 8).astype(int), axis=0)
plat = rgb.reshape(-1, 3)
dist = np.min(np.abs(plat[:, None, :] * 8 - q[None, :, :]).max(axis=2), axis=1)
sujet = (dist > 1.0).reshape(sh, sw)
part = float(sujet.mean())
tout = plat @ np.array([0.2126, 0.7152, 0.0722], dtype=np.float32)
sortie = {"size": (w, h), "image_brightness": round(float(tout.mean()), 3), "subject_pct": round(100 * part, 1)}
if part < 0.005:
return
…
[Version tronquée pour la traduction]