Het Lab · Notitieboek nr. 1

Blender aansturen met code.

Een nachtdecor op filmniveau, gebouwd en belicht door een AI-agent in headless Blender, daarna gebakken tot 6,6 MB voor de browser.

20 juli 2026 · 7 min leestijd · Python (bpy) · Cycles · three.js
3D-render van een nachtkantoor: brandende scharnierlamp, retrocomputer, lederen fauteuil, post-its aan de muur
Ruwe Cycles-render, onbewerkt. 1080p, 256 samples, op een MacBook M1. Niemand opende de interface van Blender.

Niemand heeft dit kantoor gemodelleerd. Geen muis, geen viewport, geen artiest: Python-scripts, geschreven door een AI-agent en uitgevoerd door Blender via de command line. Dezelfde code perste het hele decor daarna samen tot 6,6 MB, klein genoeg voor een webpagina. Hieronder het notitieboek, met de echte code.

Het probleem

Onze webexperiences vragen decors die naast een filmbeeld kunnen staan: zacht licht, geloofwaardige rommel, scherptediepte. Alleen moeten die decors op een telefoon draaien, aan 60 beelden per seconde. Cycles rekent minuten aan één beeld; de browser krijgt er 16 milliseconden voor. Die twee werelden praten niet met elkaar.

Ander gegeven: er is hier geen 3D-artiest. De studio, dat is een mens die beslist en een AI-agent die uitvoert. Blender zou dus niet geopend worden. Het zou aangestuurd worden.

Blender zonder interface

Blender bevat een volledige Python-interpreter, bpy, en draait probleemloos zonder interface. De hele scène past dus in een programma:

/Applications/Blender.app/Contents/MacOS/Blender \
  --background --python build_office.py -- --render

Tijdens het bouwen ziet de agent niets. Hij beschrijft, in meters. De kamer meet 4,2 m op 5,2 m: dozen, heel simpel, tot en met de rechterwand die in vier panelen werd gesneden om er een raam in te maken:

box('floor',  (4.2, 5.2, 0.05), (0, 0, -0.025), WOOD)
box('wall_back', (4.2, 0.06, 3.0), (0, -2.6, 1.5), WALL)
# Rechterwand DOORBOORD met een raam (opening y -1.35..0.15, z 0.85..2.5)
box('wall_right_a', (0.06, 5.2, 0.85), (2.1, 0, 0.425), WALL)  # onder het raam
box('wall_right_b', (0.06, 5.2, 0.5),  (2.1, 0, 2.75),  WALL)  # erboven

Het meubilair werd niet gemodelleerd. Alles komt van Poly Haven, een CC0-bibliotheek van gescande objecten met een API: het metalen bureau, de fauteuil, de scharnierlamp, de CRT-televisie, de laptop uit de jaren 90. Eén functie, place(), importeert elk object, meet het, schaalt het en zet het op de vloer. De redenering van een decorbouwer: dit bureau is 78 cm hoog, het staat daar, zo gedraaid.

desk  = place(import_asset('polyhaven/desk', 'desk'), target_h=0.78, loc=(-0.5, -1.85, 0))
chair = place(import_asset('polyhaven/office-chair', 'chair'), target_h=0.95,
              loc=(-1.05, -0.8, 0), rot_z=math.radians(210))
crt   = place(import_asset('polyhaven/crt-tv', 'crt'), target_h=0.42,
              loc=(0.15, -2.1, DESK_TOP), rot_z=math.radians(42))

De rommel is ook code

Een opgeruimd bureau vertelt niets; dit bureau had een verleden nodig. De module clutter.py importeert geen enkele asset. Primitieven en regels, meer niet. Stapels A4 die nooit helemaal recht liggen, ongelijke mappen, kabels die doorhangen in een kettinglijn, veertien post-its. Elke stapel krijgt zijn eigen random seed en zijn messiness-parameter: op 0 ligt de stapel netjes, op 1 spreekt de commentaarregel in het script van “chaos aan het einde van de nacht”:

clutter.paper_stack((-1.0, -1.55, DESK_TOP), height=0.16, seed=3, messiness=1.0)
clutter.folder_stack((1.1, -2.2, DESK_TOP), count=7, seed=5)
clutter.cable((0.15, -2.3, DESK_TOP), (0.9, -2.5, 0.05), sag=0.25, seed=11)
clutter.postits((-0.1, -2.555, 1.35), 'y', count=14, seed=9)  # tegen de muur geplakt

Licht maakt de film

Vier bronnen. De bureaulamp, warm, die de hele scène draagt. Stadslicht, koud, door de venetiaanse jaloezieën. De schermen. En een lade die op een kier bleef staan en gouden licht lekt. Wat erin zit, vertellen we hier niet.

key.data.energy = 180
key.data.color  = (1.0, 0.78, 0.5)          # de lamp: warm, de key
moon.data.color = (0.6, 0.71, 0.95)         # de stad: koud, door de jaloezieën
box('drawer_gold', ..., emit=(1.0, 0.72, 0.28), strength=90)   # de lade
scene.view_settings.view_transform = 'AgX'  # + look 'AgX - Punchy'

Alles gaat door de AgX-kleurtransformatie, die hoge lichten zacht laat afrollen in plaats van ze wit uit te bijten zoals 3D-renders dat tien jaar geleden deden.

De agent herleest zichzelf

De eerste render was niet goed.

Eerste versie van de render: het CRT-scherm is een overbelichte witte rechthoek recht in de camera
Versie 1. Het CRT-scherm: een uitgebeten witte rechthoek, recht in de camera.
Gecorrigeerde versie: de televisie is naar de fauteuil gedraaid, de gloed is blauwig en gekalibreerd
Versie 2. Het toestel 42° naar de fauteuil gedraaid, blauwige gloed, gekalibreerde emissie.

Niemand kwam tussen deze twee beelden. De agent renderde de scène, bekeek het resultaat, merkte het overbelichte scherm op dat in de camera staarde, en corrigeerde zijn eigen script: het toestel draait naar de fauteuil, de emissie zakt, de tint koelt af. De correctie die een director of photography op het oog zou maken, gebeurt hier door code te herlezen.

De cijfers, op een gewone MacBook M1

De brug naar de browser: het licht bakken

Geen enkele telefoon zal deze belichting live berekenen, en dat hoeft ook niet. De truc is zo oud als het videospel: bereken het licht één keer en schilder het daarna op de muren. Dat is de bake.

Het tweede script voegt de scène samen tot twee objecten, de schil (muren, vloer, plafond, jaloezieën) en de inhoud (meubels, rommel), vouwt elk open op een nieuwe UV-atlas en bakt de Cycles-belichting in twee texturen. Daarna vervangt het de materialen. Het geëxporteerde bestand bevat geen enkele lamp meer: het licht is textuur geworden. Alleen de schermen en het goud van de lade blijven emissief; die blijven in realtime leven:

groups = {'shell': muren_vloer_plafond, 'stuff': al_de_rest}
bpy.ops.uv.smart_project(island_margin=0.002)
bpy.ops.uv.pack_islands(margin=0.003)   # anders klitten de eilanden samen in een hoek
bpy.ops.object.bake(type='COMBINED', margin=8, use_clear=True)
bpy.ops.export_scene.gltf(filepath='office_baked.glb', export_format='GLB')

Drie valkuilen onderweg:

1. Het gebakken beeld moet manueel worden opgeslagen via script: het automatische opslaan van de bake is kapot in headless-modus.
2. Zonder pack_islands na het unwrappen klitten alle UV-eilanden samen in één hoek van de atlas.
3. Op M1 kan een GPU-bake artefacten opleveren: dezelfde bake opnieuw draaien op CPU lost het op.

Aan het einde van de keten: een GLB van 6,6 MB die three.js laadt als eender welk model. Alleen draagt dit model filmbelichting mee die niets meer kost om te berekenen.

Wat dit verandert, ook voor uw bedrijf

Dit notitieboek wil geen 3D verkopen. 3D is onze testbank, de zwaarste die we vonden: geometrie, licht en performancebudget moeten tegelijk kloppen. Waar het om gaat, is het mechanisme eronder: een AI-agent kan elke software aansturen die zich via code laat automatiseren. Hij klikt niet sneller dan een mens. Hij schrijft, voert uit, leest het resultaat, corrigeert zichzelf.

Zet uw ERP, uw planningstool of uw documentketen op de plaats van Blender: de lus verandert niet. Het is dezelfde die wij bij onze klanten installeren.

Praat met ons over uw bedrijf
© 2026 The Good Life Studio From Belgium · hello@thegoodlife.studio