Skill: Blender Lighting & Camera
Sei un esperto di fotografia 3D e compositing in Blender. Il tuo ruolo è configurare luci, camera, world e color management per ottenere render professionali — con particolare attenzione a:
- oggetti chiari/bianchi che si sovraespongono facilmente
- contrasto soggetto/sfondo per product shots
- fedeltà cromatica per materiali organici, porcellana, metallo
CONNESSIONE — MCP (predefinito)
mcp__Blender__execute_blender_code(code="""
import bpy
# ... codice di setup luci/camera ...
result = {"ok": True}
""")
DIAGNOSI RAPIDA — Problemi Comuni
| Sintomo | Causa | Fix |
|---|---|---|
| Oggetto bianco "bruciato", nessun dettaglio | exposure troppo alto, look sbagliato | exposure -0.4/-0.8, look "AgX - High Contrast" |
| Sfondo troppo simile al soggetto | world troppo chiaro | world strength 0.05-0.15, color (0.02,0.02,0.03) |
| Ombre troppo dure/nere | fill troppo debole | fill 12-25% dell'energia key, size grande (0.8-1.5m) |
| Ombre troppo morbide/flat | fill troppo forte o size troppo grande | fill < 15% key, ridurre size fill |
| SSS / trasparenze spente | Eevee non le renderizza | Cycles obbligatorio per SSS/glass realistici |
| Riflessi troppo intensi | rim/key a energia eccessiva | dimezzare energy, aumentare size |
| Toni piatti, "CG look" | AgX/Filmic spento o look "None" | usare AgX + Punchy/High Contrast |
| Colori saturi che diventano grigi | Filmic "Notorious Six" | passare ad AgX (default da Blender 4.0+) |
| Area light non illumina nulla | rotazione con metodo matrix manuale | usare to_track_quat('-Z','Y') — UNICO metodo affidabile |
| Bordi alias / pixelati | samples bassi | EEVEE: samples 64+, Cycles: samples 256+ |
COLOR MANAGEMENT — AgX Pipeline (default Blender 4.0+)
AgX ha sostituito Filmic come default da Blender 4.0. Filmic è deprecato. Differenze chiave:
- AgX: highlights → bianco naturale (come una camera reale)
- Filmic: colori saturi collassano in 6 tonalità ("Notorious Six") — artefatto visibile
- AgX: migliore shadow detail su oggetti scuri
- AgX: supporta Display P3 e BT.1886 oltre sRGB
Per file esistenti che usano Filmic:
scene.view_settings.view_transform = "AgX"
def setup_color_management(scene,
exposure=0.0,
look="AgX - Punchy",
view_transform="AgX"):
"""
Configura AgX color management (standard da Blender 4.0+).
exposure:
0.0 → neutro (default per oggetti a toni medi)
-0.4 → oggetti bianchi/chiari (porcellana, carta)
+0.1 → oggetti molto scuri (plastica nera, pelle scura)
+0.3 → scena sottoesposta da schiarire
look AgX:
"None" → flat/raw, solo per compositing esterno
"AgX - Base" → neutro, fedele ai colori, uso tecnico
"AgX - Punchy" → contrasto naturale, product shot standard ← PREFERITO
"AgX - High Contrast" → drammatico, nero profondo, electronics/tech
"AgX - Very High Contrast" → pubblicità, estremo
look Filmic (legacy — evitare):
"Medium High Contrast", "High Contrast" — per file vecchi pre-4.0
REGOLA AgX:
oggetti bianchi/chiari → exposure -0.4 + "AgX - Punchy"
oggetti medi → exposure 0.0 + "AgX - Punchy"
oggetti scuri (TV, metallo) → exposure +0.1 + "AgX - High Contrast"
organico/pelle → exposure 0.0 + "AgX - Punchy"
"""
scene.view_settings.view_transform = view_transform
scene.view_settings.look = look
scene.view_settings.exposure = exposure
scene.view_settings.gamma = 1.0
# Preset rapidi AgX:
def cm_product_white(scene):
"""Porcellana bianca, carta, tessuti chiari."""
setup_color_management(scene, exposure=-0.4, look="AgX - Punchy")
def cm_product_neutral(scene):
"""Oggetti a colori medi, legno, plastica colorata."""
setup_color_management(scene, exposure=0.0, look="AgX - Punchy")
def cm_product_dark(scene):
"""Metallo scuro, plastica nera, electronics (TV, smartphone)."""
setup_color_management(scene, exposure=0.1, look="AgX - High Contrast")
def cm_organic(scene):
"""Frutta, pelle, materiali organici (SSS richiede Cycles)."""
setup_color_management(scene, exposure=0.0, look="AgX - Punchy")
def cm_architectural(scene):
"""Interni/esterni, luce naturale."""
setup_color_management(scene, exposure=0.0, look="AgX - Punchy")
WORLD / BACKGROUND
def setup_world(scene,
color=(0.03, 0.03, 0.04),
strength=0.15,
hdri_path=None):
"""
Configura il world background.
PRODUCT SHOT (piccoli oggetti, sfondo scuro):
color=(0.02,0.02,0.03), strength=0.10-0.20
→ quasi nero, soggetto risalta, ombre non troppo lunghe
STUDIO NEUTRO:
color=(0.05,0.05,0.06), strength=0.20
→ grigio scuro neutro, non distrae
AMBIENTE CALDO:
color=(0.04,0.03,0.02), strength=0.15
→ leggero tono ambra, buono per legno e pelle
HDRI (Cycles only — environment realistico):
hdri_path = "/path/to/hdri.hdr"
strength = 0.5-2.0
ATTENZIONE: world strength alto + oggetto bianco = overexposure
Regola: se exposure < -0.3, tieni strength ≤ 0.20
"""
world = scene.world
if world is None:
world = bpy.data.worlds.new("World")
scene.world = world
world.use_nodes = True
nodes = world.node_tree.nodes
links = world.node_tree.links
nodes.clear()
if hdri_path:
# HDRI environment (Cycles)
env = nodes.new("ShaderNodeTexEnvironment")
bg = nodes.new("ShaderNodeBackground")
out = nodes.new("ShaderNodeOutputWorld")
env.image = bpy.data.images.load(hdri_path)
bg.inputs["Strength"].default_value = strength
links.new(env.outputs["Color"], bg.inputs["Color"])
links.new(bg.outputs["Background"], out.inputs["Surface"])
else:
# Colore solido
bg = nodes.new("ShaderNodeBackground")
out = nodes.new("ShaderNodeOutputWorld")
bg.inputs["Color"].default_value = (*color, 1.0)
bg.inputs["Strength"].default_value = strength
links.new(bg.outputs["Background"], out.inputs["Surface"])
return world
def setup_ground_plane(scene, collection,
color=(0.08, 0.05, 0.03),
roughness=0.55,
size=2.0,
z=0.0,
receive_shadow=True):
"""
Piano del tavolo/pavimento per product shot.
TAVOLO LEGNO SCURO (tazza espresso): color=(0.08,0.05,0.03) rough=0.55
TAVOLO MARMO: color=(0.85,0.82,0.78) rough=0.15 (Cycles SSR)
TAVOLO METALLO: color=(0.20,0.20,0.22) rough=0.30 metallic=0.8
CARTA BIANCA: color=(0.92,0.92,0.90) rough=0.90
SFONDO INFINITO (cyc): usare geometry shader o curve modifier
"""
bpy.ops.mesh.primitive_plane_add(size=size, location=(0, 0, z))
plane = bpy.context.active_object
plane.name = "Ground"
mat = bpy.data.materials.new("Ground_Mat")
mat.use_nodes = True
bsdf = mat.node_tree.nodes["Principled BSDF"]
bsdf.inputs["Base Color"].default_value = (*color, 1.0)
bsdf.inputs["Roughness"].default_value = roughness
plane.data.materials.clear()
plane.data.materials.append(mat)
if collection:
collection.objects.link(plane)
bpy.context.scene.collection.objects.unlink(plane)
return plane
LUCI — Tre Punti Base
Tipo di luce e dimensione — regole fondamentali
| Tipo | Ombre | Riflessi | Uso ideale |
|---|---|---|---|
| AREA small (0.05–0.20m) | Dure, definite | Highlight puntuale | Rim, accent |
| AREA medium (0.25–0.50m) | Medie | Highlight pulito | Key product shot |
| AREA large (0.8–2.0m) | Morbide, diffuse | Ampia striscia | Fill, softbox |
| SUN | Parallele (no falloff) | — | Esterni, architettura |
| POINT | Omnidirezionali | — | Fill secondario, GI bounce |
Ratio key/fill/rim consigliati:
| Setup | Key | Fill | Rim | Risultato |
|---|---|---|---|---|
| Product drammatico | 100% | 12–15% | 55–65% | Contrasto alto, tech/electronics |
| Product standard | 100% | 25–30% | 50% | Bilanciato, uso generale |
| Cibo/lifestyle | 100% | 33% | 40% | Morbido, luminoso |
| Ritratto | 100% | 50% | 30% | Viso, pelle |
from mathutils import Vector
import bpy
def add_area(name, energy, location, target, size, color=(1,1,1), collection=None):
"""
Crea AREA light orientata verso target con to_track_quat('-Z','Y').
✅ VERIFICATO Blender 5.x: to_track_quat('-Z','Y') funziona per AREA, SUN, SPOT.
❌ NON usare: rotazione via matrice manuale (causa Area light nera in Blender 5.x).
size: dimensione lato (SQUARE) in BU
0.05–0.20 → rim/accent (ombra dura)
0.25–0.50 → key (ombra media)
0.80–2.00 → fill/softbox (ombra morbida)
"""
ld = bpy.data.lights.new(name=name, type='AREA')
ld.energy = energy
ld.color = color
ld.size = size
ld.shape = 'SQUARE'
lo = bpy.data.objects.new(name, ld)
lo.location = location
# Orienta la luce verso il target — UNICO metodo affidabile in Blender 5.x
travel = (Vector(target) - Vector(location)).normalized()
lo.rotation_euler = travel.to_track_quat('-Z', 'Y').to_euler()
coll = collection or bpy.context.collection
coll.objects.link(lo)
return lo
def add_point(name, energy, location, color=(1,1,1), size=0.5, collection=None):
"""POINT light — omnidirezionale, nessuna rotazione necessaria."""
ld = bpy.data.lights.new(name=name, type='POINT')
ld.energy = energy
ld.color = color
ld.shadow_soft_size = size
lo = bpy.data.objects.new(name, ld)
lo.location = location
coll = collection or bpy.context.collection
coll.objects.link(lo)
return lo
def add_sun(name, energy, location, target, color=(1,1,1), collection=None):
"""SUN light orientato con to_track_quat — per esterni/architettura."""
ld = bpy.data.lights.new(name=name, type='SUN')
ld.energy = energy
ld.color = color
lo = bpy.data.objects.new(name, ld)
lo.location = location
travel = (Vector(target) - Vector(location)).normalized()
lo.rotation_euler = travel.to_track_quat('-Z', 'Y').to_euler()
coll = collection or bpy.context.collection
coll.objects.link(lo)
return lo
PRESET LUCI PER TIPO DI OGGETTO
1. Small Object — Product Shot (default)
Usare per: tazze, frutti, telefoni, gioielli, piccoli oggetti da tavolo (Ø < 30cm).
def light_rig_small_object(obj_center=(0,0,0.04)):
"""
Tre punti AREA per oggetto piccolo.
Ratio: key=100%, fill=25%, rim=55%
Color management: cm_product_neutral() — AgX Punchy
"""
cx, cy, cz = obj_center
t = (cx, cy, cz) # target
key = add_area("Key", energy=120, size=0.30,
location=(cx-0.30, cy-0.15, cz+0.36),
target=t, color=(1.00, 0.97, 0.90))
fill = add_area("Fill", energy=30, size=0.80,
location=(cx+0.32, cy+0.10, cz+0.18),
target=t, color=(0.85, 0.90, 1.00))
rim = add_area("Rim", energy=65, size=0.12,
location=(cx+0.00, cy+0.32, cz+0.28),
target=t, color=(1.00, 0.96, 0.88))
return key, fill, rim
def light_rig_small_object_white(obj_center=(0,0,0.04)):
"""
Variante per OGGETTI BIANCHI/CHIARI (porcellana, carta, ceramica).
Key più grande → ombra morbida. Fill ridotto → forma visibile.
Rim dimezzato → evita bordi bruciati su bianco.
Color management: cm_product_white() — AgX Punchy, exposure -0.4
"""
cx, cy, cz = obj_center
t = (cx, cy, cz)
key = add_area("Key", energy=100, size=0.45,
location=(cx-0.28, cy-0.14, cz+0.38),
target=t, color=(1.00, 0.97, 0.92))
fill = add_area("Fill", energy=20, size=1.00,
location=(cx+0.32, cy+0.12, cz+0.18),
target=t, color=(0.88, 0.93, 1.00))
rim = add_area("Rim", energy=40, size=0.12,
location=(cx+0.00, cy+0.30, cz+0.26),
target=t, color=(1.00, 0.97, 0.90))
return key, fill, rim
def light_rig_dark_product(obj_center=(0,0,0.04)):
"""
Per OGGETTI SCURI (TV, smartphone, plastica nera, metallo scuro).
Fill molto ridotto (12%) → contrasto alto, evidenzia la forma.
Rim potente → separa dal fondo scuro.
Color management: cm_product_dark() — AgX High Contrast, exposure +0.1
"""
cx, cy, cz = obj_center
t = (cx, cy, cz)
key = add_area("Key", energy=400, size=0.45,
location=(cx-1.2, cy-0.6, cz+1.1),
target=t, color=(1.00, 0.97, 0.92))
fill = add_area("Fill", energy=50, size=1.20,
location=(cx+1.2, cy-0.5, cz+0.3),
target=t, color=(0.88, 0.93, 1.00))
rim = add_area("Rim", energy=250, size=0.15,
location=(cx+0.3, cy+2.0, cz+0.7),
target=t, color=(1.00, 0.96, 0.88))
return key, fill, rim
2. Food & Organic — Macro/Close-up
Usare per: cibo, frutta, vegetali, oggetti con texture ricca.
def light_rig_food(obj_center=(0,0,0.05)):
"""
Setup morbido per cibo — key grande, fill generoso, backlight per traslucenza.
Ratio: key=100%, fill=33%, back=58%
NOTA: per SSS/traslucenza usare Cycles, non Eevee.
Color management: cm_organic() — AgX Punchy
"""
cx, cy, cz = obj_center
t = (cx, cy, cz)
key = add_area("Key_Food", energy=80, size=0.80,
location=(cx-0.25, cy-0.20, cz+0.40),
target=t, color=(1.00, 0.98, 0.94))
fill = add_area("Fill_Food", energy=26, size=1.40,
location=(cx+0.40, cy+0.10, cz+0.10),
target=t, color=(0.90, 0.95, 1.00))
back = add_area("Back_Food", energy=46, size=0.50,
location=(cx+0.05, cy+0.40, cz+0.10),
target=t, color=(1.00, 0.95, 0.85))
return key, fill, back
3. Furniture & Interior
Usare per: sedie, tavoli, lampade, oggetti di design (Ø 30cm–2m).
def light_rig_furniture(scene, collection, obj_center=(0,0,0.5)):
"""
Tre AREA per mobili/design. key=100%, fill=20%, rim=57%.
Energie scale su scala 1:10 rispetto a small_object.
Abbinare con: cm_product_neutral() → AgX Punchy, exposure=-0.1
"""
cx, cy, cz = obj_center
# KEY — sinistra e in alto, calda
key = add_area("Key_Furn", energy=350, size=1.00,
location=(cx-1.50, cy-0.80, cz+2.00),
target=obj_center,
color=(1.00, 0.97, 0.90),
collection=collection)
# FILL — destra, grande, fredda
fill = add_area("Fill_Furn", energy=70, size=2.50,
location=(cx+1.50, cy+0.50, cz+1.00),
target=obj_center,
color=(0.87, 0.92, 1.00),
collection=collection)
# RIM — dietro, stretto, contorno
rim = add_area("Rim_Furn", energy=200, size=0.40,
location=(cx+0.00, cy+2.00, cz+1.50),
target=obj_center,
color=(1.00, 0.96, 0.88),
collection=collection)
return key, fill, rim
4. Architectural — Exterior
Usare per: edifici, facciate, scene urbane.
def light_rig_architectural(scene, collection,
sun_location=(5, -5, 8),
sun_target=(0, 0, 1.5)):
"""
SUN (luce solare direzionale) + AREA sky fill.
Cycles consigliato per GI corretta.
Abbinare con: cm_architectural() → AgX Base, exposure=0.0
"""
# Sole — SUN usa to_track_quat come gli AREA
sun = add_sun("Sun",
energy=4.5,
location=sun_location,
target=sun_target,
color=(1.00, 0.96, 0.85),
collection=collection)
# Sky fill — AREA molto grande sopra la scena (simula cielo)
fill = add_area("Sky_Fill",
energy=800, size=8.0,
location=(0, 0, 12),
target=(0, 0, 0),
color=(0.55, 0.75, 1.00),
collection=collection)
return sun, fill
TECNICHE CINEMATOGRAFICHE — Named Lighting Patterns
Queste tecniche vengono da fotografia, cinema e animazione professionale. Ogni tecnica ha un posizionamento preciso, un ratio key:fill e un effetto emotivo definito. Scegliere in base al contenuto narrativo, non solo all'estetica.
Mappa ratio emotivi (Key:Fill)
| Ratio | Tipo scena | Effetto emotivo | Esempi |
|---|---|---|---|
| 1:1 | High-key | Allegro, commedia, pubblicità fresca | Spot yogurt, cartoon |
| 2:1 | Lifestyle | Naturale, documentaristico | Food editorial, moda casual |
| 3:1 | Drammatico | Carattere, tensione lieve | Ritratto, product premium |
| 4:1 | Dark-dramatic | Misterioso, intrigante | Thriller, parfum, automotive |
| 8:1+ | Low-key / Noir | Angoscia, suspense, potere | Villain, horror, dark art |
Regola pratica: il ratio segue l'arco emotivo — scene di gioia salgono verso 1:1, scene di conflitto scendono verso 8:1+.
Temperatura colore (standard professionale)
| Luce | Temperatura | Uso |
|---|---|---|
| Key | 3200–4000 K (warm amber) | Sorgente principale — dona calore naturale |
| Fill | 5500–6500 K (daylight) | Riempimento neutro — non contrasta col key |
| Rim | 6500–8000 K (cool blue) | Separazione dal fondo — effetto "halo" |
# Conversione K → RGB lineare (approssimazione pratica)
TEMP_COLORS = {
"tungsten": (1.00, 0.70, 0.42), # 3200K — very warm
"warm": (1.00, 0.82, 0.63), # 4000K — warm
"daylight": (1.00, 0.97, 0.90), # 5500K — neutral warm
"neutral": (1.00, 1.00, 1.00), # 6500K — D65 neutral
"cool": (0.90, 0.95, 1.00), # 7000K — slightly cool (fill)
"sky": (0.75, 0.87, 1.00), # 9000K — cool blue (rim/sky)
}
# Uso professionale:
# key_color = TEMP_COLORS["warm"] # (1.00, 0.82, 0.63)
# fill_color = TEMP_COLORS["cool"] # (0.90, 0.95, 1.00)
# rim_color = TEMP_COLORS["sky"] # (0.75, 0.87, 1.00)
Tecniche Named — Posizionamento e Setup
def light_rig_loop(obj_center=(0,0,0.04), subject_height=0.08):
"""
LOOP LIGHTING — tecnica standard ritratto/product.
Key: 45° orizzontale dal soggetto, 30-45° in alto.
Fill: lato opposto, 60-90° orizzontale, basso.
Rim: 120-135° dal key, altezza media.
Ratio key:fill = 2:1 → naturale, bilanciato.
Loop = piccola ombra a forma di loop sotto il naso (ritratto),
o ombra morbida laterale (oggetti).
Usa per: product shot standard, ritratto lifestyle, cibo casual.
"""
cx, cy, cz = obj_center
h = subject_height
t = (cx, cy, cz + h * 0.5) # target = centro soggetto
key = add_area("Loop_Key", energy=120, size=0.35,
location=(cx - h*3.5, cy - h*3.5, cz + h*3.5), # 45° hor, 45° vert
target=t, color=(1.00, 0.82, 0.63)) # warm 4000K
fill = add_area("Loop_Fill", energy=60, size=1.00,
location=(cx + h*4.0, cy - h*1.0, cz + h*1.5), # lato opposto
target=t, color=(0.90, 0.95, 1.00)) # cool fill
rim = add_area("Loop_Rim", energy=65, size=0.15,
location=(cx + h*1.0, cy + h*4.5, cz + h*2.5), # dietro
target=t, color=(0.75, 0.87, 1.00)) # sky cool
return key, fill, rim
def light_rig_rembrandt(obj_center=(0,0,0.04), subject_height=0.08):
"""
REMBRANDT LIGHTING — drammatico, carattere forte.
Key: 45° laterale, 45° in alto (come loop ma più laterale).
Fill: molto debole o assente (ratio 3:1 → 4:1).
Rim: stesso lato del fill, evidenzia profilo.
Effetto: triangolo di luce illuminato sulla guancia in ombra (ritratto).
Ratio key:fill = 3:1-4:1 → drammatico.
Usa per: ritratto carattere, villain, product premium, noir leggero.
"""
cx, cy, cz = obj_center
h = subject_height
t = (cx, cy, cz + h * 0.5)
key = add_area("Remb_Key", energy=150, size=0.30,
location=(cx - h*4.5, cy - h*2.0, cz + h*4.0), # più laterale
target=t, color=(1.00, 0.80, 0.58)) # tungsten warm
fill = add_area("Remb_Fill", energy=40, size=1.20,
location=(cx + h*3.0, cy - h*0.5, cz + h*0.8), # molto debole
target=t, color=(0.90, 0.95, 1.00))
rim = add_area("Remb_Rim", energy=80, size=0.15,
location=(cx + h*2.0, cy + h*4.0, cz + h*2.0),
target=t, color=(0.75, 0.87, 1.00))
return key, fill, rim
def light_rig_butterfly(obj_center=(0,0,0.04), subject_height=0.08):
"""
BUTTERFLY / PARAMOUNT LIGHTING — glamour, bellezza.
Key: direttamente davanti, in alto (non laterale).
Fill: sotto il soggetto o assente.
Rim: simmetrico, nessun lato dominante.
Effetto: ombra a farfalla sotto il naso (ritratto).
Ratio key:fill = 2:1-2.5:1 → luminoso, flattering.
Usa per: beauty, gioielli, prodotti cosmetici, still life simmetrico.
"""
cx, cy, cz = obj_center
h = subject_height
t = (cx, cy, cz + h * 0.5)
key = add_area("Btfly_Key", energy=130, size=0.45,
location=(cx, cy - h*2.5, cz + h*4.5), # frontale alto
target=t, color=(1.00, 0.97, 0.90)) # quasi neutro
fill = add_area("Btfly_Fill", energy=52, size=1.20,
location=(cx, cy - h*1.5, cz - h*0.5), # sotto, luce di rimbalzo
target=t, color=(0.90, 0.95, 1.00))
rim_l = add_area("Btfly_Rim_L", energy=55, size=0.15,
location=(cx - h*3.5, cy + h*3.0, cz + h*2.0),
target=t, color=(0.75, 0.87, 1.00))
rim_r = add_area("Btfly_Rim_R", energy=55, size=0.15,
location=(cx + h*3.5, cy + h*3.0, cz + h*2.0),
target=t, color=(0.75, 0.87, 1.00))
return key, fill, rim_l, rim_r
def light_rig_split(obj_center=(0,0,0.04), subject_height=0.08):
"""
SPLIT LIGHTING — metà illuminata, metà in ombra.
Key: 90° di lato (esattamente laterale), nessun fill.
Rim: opposto al key, debole (o assente per effetto totale).
Ratio key:fill = 8:1 o più → low-key, tensione massima.
Usa per: dark product, villain, horror, automotive di notte, tech dark.
"""
cx, cy, cz = obj_center
h = subject_height
t = (cx, cy, cz + h * 0.5)
key = add_area("Split_Key", energy=200, size=0.20,
location=(cx - h*5.0, cy, cz + h*1.5), # 90° laterale
target=t, color=(1.00, 0.80, 0.58)) # warm tungsten
rim = add_area("Split_Rim", energy=25, size=0.20,
location=(cx + h*4.5, cy + h*2.0, cz + h*1.5),
target=t, color=(0.70, 0.85, 1.00)) # cold blue
return key, rim
def light_rig_high_key(obj_center=(0,0,0.04), subject_height=0.08):
"""
HIGH-KEY LIGHTING — tutto luminoso, ombre quasi assenti.
Ratio key:fill = 1:1 → allegro, pulito, commerciale.
Molte sorgenti grandi da angoli diversi.
Usa per: pubblicità alimentare, cosmetici, lifestyle positivo,
cartoon, bambini, prodotti "fresh".
"""
cx, cy, cz = obj_center
h = subject_height
t = (cx, cy, cz + h * 0.5)
key = add_area("HK_Key", energy=90, size=0.60,
location=(cx - h*3.0, cy - h*2.0, cz + h*4.0),
target=t, color=(1.00, 0.97, 0.90))
fill = add_area("HK_Fill", energy=80, size=1.20,
location=(cx + h*3.0, cy - h*1.5, cz + h*3.0),
target=t, color=(0.95, 0.97, 1.00))
top = add_area("HK_Top", energy=60, size=1.00,
location=(cx, cy - h*0.5, cz + h*6.0), # overhead
target=t, color=(1.00, 1.00, 1.00))
bounce= add_area("HK_Bounce",energy=40, size=1.50,
location=(cx, cy - h*2.0, cz - h*0.5), # sotto (bounce)
target=t, color=(0.95, 0.98, 1.00))
return key, fill, top, bounce
def light_rig_low_key(obj_center=(0,0,0.04), subject_height=0.08):
"""
LOW-KEY LIGHTING — scuro, drammatico, poca luce.
Ratio key:fill = 8:1 → noir, mystery, tensione.
Una sola sorgente principale, nessun fill.
Usa per: horror, thriller, villain, whisky/spirits dark,
automotive notturno, oggetti di lusso scuro.
"""
cx, cy, cz = obj_center
h = subject_height
t = (cx, cy, cz + h * 0.5)
key = add_area("LK_Key", energy=250, size=0.25,
location=(cx - h*3.5, cy - h*1.5, cz + h*4.0),
target=t, color=(1.00, 0.78, 0.48)) # molto warm/amber
# Nessun fill → quasi tutto in ombra
# Rim opzionale per separare dal fondo nero
rim = add_area("LK_Rim", energy=30, size=0.15,
location=(cx + h*3.0, cy + h*4.0, cz + h*2.0),
target=t, color=(0.60, 0.80, 1.00)) # cool blue contrasto
return key, rim
def light_rig_food_side(obj_center=(0,0,0.05)):
"""
FOOD SIDE LIGHTING — luce laterale per texture cibo.
Key laterale 45° → rivela texture (granulosa, ruvida, fresca).
Fill molto debole per non schiacciare le ombre di rilievo.
Back light per separazione e traslucenza.
Standard industria food photography (riviste, ristoranti premium).
Usa con: Cycles (SSS), cm_organic(), piano legno o ardesia.
"""
cx, cy, cz = obj_center
key = add_area("Food_SideKey", energy=70, size=0.60,
location=(cx - 0.30, cy + 0.00, cz + 0.25), # laterale 90°
target=(cx, cy, cz), color=(1.00, 0.92, 0.80)) # warm 4500K
fill = add_area("Food_Fill", energy=12, size=1.40,
location=(cx + 0.35, cy - 0.20, cz + 0.15),
target=(cx, cy, cz), color=(0.90, 0.95, 1.00))
back = add_area("Food_Back", energy=45, size=0.40,
location=(cx + 0.05, cy + 0.45, cz + 0.20), # dietro-laterale
target=(cx, cy, cz), color=(1.00, 0.95, 0.88))
return key, fill, back
def light_rig_food_window(obj_center=(0,0,0.05)):
"""
FOOD WINDOW LIGHT — simula finestra naturale laterale.
Grande sorgente softbox = finestra diffusa.
Ratio key:fill = 2:1 → morbido, lifestyle.
"""
cx, cy, cz = obj_center
window = add_area("Food_Window", energy=55, size=1.20,
location=(cx - 0.45, cy, cz + 0.30), # finestra laterale
target=(cx, cy, cz), color=(1.00, 0.97, 0.90))
bounce = add_area("Food_Bounce", energy=25, size=1.60,
location=(cx + 0.45, cy, cz + 0.05), # riflettore bianco opposto
target=(cx, cy, cz), color=(0.95, 0.98, 1.00))
return window, bounce
HDRI — Illuminazione Ambiente Ibrida
Best practice moderna: HDRI per global illumination + key light AREA per controllo artistico. HDRI da solo → realismo, ma niente controllo sulle ombre. HDRI + key → realismo con forma scultorea. Solo AREA → controllo totale, meno realismo.
def setup_hdri_world(scene, hdri_path,
hdri_strength=0.6,
hdri_rotation_z=0.0,
add_solid_background=False,
bg_color=(0.03, 0.03, 0.04)):
"""
Setup world HDRI — illuminazione ambiente realistica.
Richiede Cycles per beneficio completo (GI vera).
Eevee usa l'HDRI per probe/reflection ma non per GI vera.
hdri_path: percorso al file .hdr o .exr
hdri_strength: 0.3-0.8 → ridotto (key AREA domina)
1.0-2.0 → pieno (HDRI è la luce principale)
hdri_rotation_z: ruota HDRI per orientare le ombre (radianti)
add_solid_background: True → sfondo colore solido + HDRI solo per illuminazione
WORKFLOW IBRIDO CONSIGLIATO:
1. setup_hdri_world(sc, path, strength=0.5) → GI e rimbalzi
2. add_area("Key", energy=80, ...) → controllo forma
↓ HDRI fornisce rimbalzi di luce naturali
↓ Key AREA fornisce direzione, ombre scultoree
"""
import math
world = scene.world
if world is None:
world = bpy.data.worlds.new("World")
scene.world = world
world.use_nodes = True
tree = world.node_tree
nodes = tree.nodes
links = tree.links
nodes.clear()
# Texture coordinate per rotazione
tc = nodes.new("ShaderNodeTexCoord")
map = nodes.new("ShaderNodeMapping")
map.inputs["Rotation"].default_value = (0, 0, hdri_rotation_z)
env = nodes.new("ShaderNodeTexEnvironment")
env.image = bpy.data.images.load(hdri_path)
if add_solid_background:
# HDRI per illuminazione, colore solido per sfondo
# (Cycles — Light Path node)
lp = nodes.new("ShaderNodeLightPath")
mix = nodes.new("ShaderNodeMixRGB")
mix.blend_type = 'MIX'
bg_node = nodes.new("ShaderNodeBackground")
bg_node.inputs["Color"].default_value = (*bg_color, 1.0)
hdri_bg = nodes.new("ShaderNodeBackground")
hdri_bg.inputs["Strength"].default_value = hdri_strength
mix_bg = nodes.new("ShaderNodeMixShader")
out = nodes.new("ShaderNodeOutputWorld")
links.new(tc.outputs["Generated"], map.inputs["Vector"])
links.new(map.outputs["Vector"], env.inputs["Vector"])
links.new(env.outputs["Color"], hdri_bg.inputs["Color"])
links.new(lp.outputs["Is Camera Ray"], mix_bg.inputs["Fac"])
links.new(bg_node.outputs["Background"], mix_bg.inputs[1])
links.new(hdri_bg.outputs["Background"], mix_bg.inputs[2])
links.new(mix_bg.outputs["Shader"], out.inputs["Surface"])
else:
# HDRI puro per background e illuminazione
bg = nodes.new("ShaderNodeBackground")
bg.inputs["Strength"].default_value = hdri_strength
out = nodes.new("ShaderNodeOutputWorld")
links.new(tc.outputs["Generated"], map.inputs["Vector"])
links.new(map.outputs["Vector"], env.inputs["Vector"])
links.new(env.outputs["Color"], bg.inputs["Color"])
links.new(bg.outputs["Background"], out.inputs["Surface"])
return world
# Preset HDRI per scena — strength ridotta (key AREA domina)
HDRI_PRESETS = {
"studio_neutral": {"strength": 0.40, "notes": "Studio neutro, niente rimbalzi esterni"},
"overcast_sky": {"strength": 0.60, "notes": "Luce diffusa naturale, ombre morbide"},
"golden_hour": {"strength": 0.50, "notes": "Warm sunset, colore ambra"},
"night_city": {"strength": 0.30, "notes": "Scena notturna urbana, rimbalzi colorati"},
"hdri_only_no_key": {"strength": 1.20, "notes": "HDRI unica sorgente, realistico puro"},
}
CAMERA
def setup_camera(scene, collection,
location=(0.26, -0.32, 0.16),
target=(0, 0, 0.04),
lens=85,
dof_distance=None,
dof_fstop=2.8,
clip_start=0.001,
clip_end=100.0):
"""
Configura la camera e la punta verso il target.
FOCALI CONSIGLIATE:
35mm → architettura, ambienti, campo lungo
50mm → uso generale, "occhio umano"
85mm → product shot piccoli oggetti (tazze, gioielli) — PREFERITA
135mm → ritratto, dettaglio, comprimi la profondità
200mm → macro simulato, molto compresso
DOF (Depth of Field):
dof_distance: distanza dal piano a fuoco in BU (None = disabilitato)
dof_fstop: apertura diaframma — più basso = più sfocatura
1.4 → molto sfocato (macro artistico)
2.8 → sfocatura moderata (product standard)
5.6 → leggera sfocatura
11.0 → tutto a fuoco
POSIZIONE CAMERA — regole pratiche:
Angolo 3/4 classico: elevation ~35°, azimuth ~40°
Top-down: elevation 70-85° (food, gioielli su superficie)
Eye level: elevation 5-15° (architettura, auto)
Leggermente sotto: elevation -5° (edificio → aspetto maestoso)
"""
from mathutils import Vector
# Crea o recupera camera
if "Camera" in bpy.data.cameras:
cam_data = bpy.data.cameras["Camera"]
else:
cam_data = bpy.data.cameras.new("Camera")
cam_data.lens = lens
cam_data.clip_start = clip_start
cam_data.clip_end = clip_end
# DOF
if dof_distance is not None:
cam_data.dof.use_dof = True
cam_data.dof.focus_distance = dof_distance
cam_data.dof.aperture_fstop = dof_fstop
else:
cam_data.dof.use_dof = False
# Crea o recupera oggetto camera
if "Camera" in bpy.data.objects:
cam_obj = bpy.data.objects["Camera"]
cam_obj.data = cam_data
else:
cam_obj = bpy.data.objects.new("Camera", cam_data)
collection.objects.link(cam_obj)
cam_obj.location = location
scene.camera = cam_obj
# Punta la camera verso il target
direction = (Vector(target) - Vector(location)).normalized()
up = Vector((0, 0, 1))
if abs(direction.dot(up)) > 0.99:
up = Vector((1, 0, 0))
right = direction.cross(up).normalized()
up = right.cross(direction).normalized()
from mathutils import Matrix
R = Matrix((right, up, -direction)).transposed()
cam_obj.rotation_euler = R.to_euler()
return cam_obj
def camera_from_object_size(obj_width_bu, obj_height_bu,
elevation_deg=35, azimuth_deg=40,
lens=85, margin=1.3):
"""
Calcola posizione camera ottimale per inquadrare l'oggetto.
Ritorna dict con location, target, lens.
obj_width_bu, obj_height_bu: dimensioni dell'oggetto in Blender Units
elevation_deg: angolo di elevazione dalla orizzontale
azimuth_deg: angolo orizzontale dall'asse Y
margin: fattore di margine (1.3 = 30% extra)
"""
import math
# FOV verticale per focale 85mm su sensore 36mm full frame
# fov_v = 2*atan(18/f) in radianti
fov_half_v = math.atan(18.0 / lens)
diag = math.sqrt(obj_width_bu**2 + obj_height_bu**2)
dist = (diag / 2) / math.tan(fov_half_v) * margin
el = math.radians(elevation_deg)
az = math.radians(azimuth_deg)
x = dist * math.cos(el) * math.sin(az)
y = -dist * math.cos(el) * math.cos(az)
z = dist * math.sin(el) + obj_height_bu / 2
return {
"location": (x, y, z),
"target": (0, 0, obj_height_bu / 2),
"lens": lens,
}
RENDER ENGINE & SETTINGS
def setup_render(scene,
engine="EEVEE",
resolution=(1280, 720),
samples=64,
output_path=None,
use_compositing=False):
"""
Configura render engine e output.
EEVEE (predefinito):
Pro: veloce, anteprima in tempo reale
Contro: no SSS vero, no caustiche, riflessi approssimati
Samples: 32-64 per preview, 128+ per produzione
CYCLES:
Pro: SSS fisicamente corretto, vetro, caustiche, GI vera
Contro: lento (anche 10-60 min per frame)
Samples: 128 per preview, 512-1024 per produzione
Usare SEMPRE per: porcellana con SSS, frutta, pelle, vetro
ATTENZIONE Eevee: per vedere SSS in Eevee, abilitare
scene.eevee.use_gtao = True (AO)
scene.eevee.use_ssr = True (screen-space reflections)
"""
sc = scene
# Engine
if engine.upper() in ("EEVEE", "EEVEE_NEXT"):
try:
sc.render.engine = "BLENDER_EEVEE_NEXT"
except:
sc.render.engine = "BLENDER_EEVEE"
# Opzioni Eevee
try:
sc.eevee.taa_render_samples = samples
sc.eevee.use_gtao = True
sc.eevee.use_ssr = True
sc.eevee.ssr_quality = 0.5
except AttributeError:
pass
elif engine.upper() == "CYCLES":
sc.render.engine = "CYCLES"
sc.cycles.samples = samples
sc.cycles.use_denoising = True
try:
sc.cycles.denoiser = "OPTIX" # GPU NVIDIA
except:
sc.cycles.denoiser = "OPENIMAGEDENOISE"
# Risoluzione
sc.render.resolution_x = resolution[0]
sc.render.resolution_y = resolution[1]
sc.render.resolution_percentage = 100
sc.render.use_compositing = use_compositing
# Output
if output_path:
sc.render.filepath = output_path
sc.render.image_settings.file_format = "PNG"
sc.render.image_settings.color_mode = "RGBA"
return sc
# Preset rapidi:
def render_preview(scene, output_path):
"""Anteprima veloce EEVEE 720p 32 samples."""
setup_render(scene, "EEVEE", (1280, 720), samples=32,
output_path=output_path)
setup_color_management(scene, exposure=-0.2, look="Medium High Contrast")
def render_product(scene, output_path, white_subject=False):
"""Product shot EEVEE 1080p 128 samples."""
setup_render(scene, "EEVEE", (1920, 1080), samples=128,
output_path=output_path)
if white_subject:
cm_product_white(scene)
else:
cm_product_neutral(scene)
def render_quality(scene, output_path, white_subject=False):
"""Alta qualità Cycles 1080p 512 samples (SSS, vetro, pelle)."""
setup_render(scene, "CYCLES", (1920, 1080), samples=512,
output_path=output_path)
if white_subject:
cm_product_white(scene)
else:
cm_organic(scene)
SETUP COMPLETO — Template Funzione Principale
def setup_full_scene(scene_name="ProductShot",
subject_obj=None,
subject_type="small_white",
output_path=None):
"""
Setup completo in una chiamata.
subject_type:
"small_white" → porcellana, ceramica, carta (tazza, piatto)
"small_colored" → oggetti a colori medi
"small_dark" → metallo scuro, pelle nera
"food" → cibo, frutta (richiede Cycles per SSS)
"furniture" → oggetti medi (sedie, lampade)
"architectural" → edifici, scene esterne
"""
import bpy, math
sc = bpy.context.scene
# Collection luci
if "Lights" in bpy.data.collections:
lights_col = bpy.data.collections["Lights"]
else:
lights_col = bpy.data.collections.new("Lights")
sc.collection.children.link(lights_col)
# Centro oggetto (usa bounding box se disponibile)
if subject_obj:
bpy.context.view_layer.update()
verts = [subject_obj.matrix_world @ v.co
for v in subject_obj.data.vertices]
from mathutils import Vector
mn = Vector((min(v.x for v in verts), min(v.y for v in verts), min(v.z for v in verts)))
mx = Vector((max(v.x for v in verts), max(v.y for v in verts), max(v.z for v in verts)))
center = tuple((mn + mx) / 2)
obj_w = (mx - mn).x
obj_h = (mx - mn).z
else:
center = (0, 0, 0.04)
obj_w, obj_h = 0.1, 0.08
# Scegli preset luci
if subject_type in ("small_white",):
light_rig_small_object_white(sc, lights_col, center)
cm_product_white(sc)
engine, samples = "EEVEE", 128
elif subject_type == "food":
light_rig_food(sc, lights_col, center)
cm_organic(sc)
engine, samples = "CYCLES", 512
elif subject_type == "furniture":
light_rig_furniture(sc, lights_col, center)
cm_product_neutral(sc)
engine, samples = "EEVEE", 128
elif subject_type == "architectural":
light_rig_architectural(sc, lights_col)
cm_architectural(sc)
engine, samples = "CYCLES", 256
…(truncated)