关于 blender-lighting
面向 Blender 创作者的灯光布置技能,提供按场景和主体选择灯型、设置三点布光及调整阴影的配方。还涵盖自发光主体和按材质类别调整的灯光方案。
主要功能
- 01五类灯光的选择指导
- 02主体适配的三点布光
- 03HDRI 与室内外灯光配方
- 04按材质类别调整灯光
- 05阴影与色温调节建议
适用场景
- 01为产品渲染搭建棚拍灯光
- 02为室内场景配置窗光与实用光源
- 03为玻璃或木质主体调节布光
- 04为自发光灯具设置环境与补光
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ roble3/ cc-blender-skill --skill blender-lighting安装后在 Claude Code 中输入/blender-lighting确认已加载。
附带文件evals/evals.jsonreferences/overview.md
详细介绍
Blender Lighting
像专业人士一样为场景布光:有章法、有意图,并使用物理上合理的数值。
五种灯光类型
| 类型 | 特性 | 用途 |
|---|---|---|
| AREA | 来自矩形表面的光线;自动产生柔和阴影 | 80% 的情况。窗户、柔光箱、荧光灯板 |
| SUN | 来自“无穷远”的平行光线 | 阳光、月光、远距离定向光 |
| POINT | 从一点向各个方向发光 | 灯泡、蜡烛、小型全向光源 |
| SPOT | 带衰减的锥形光束 | 舞台灯、车灯、聚焦光束 |
| HDRI/World | 360° 环境图像 | 逼真的环境光、户外、产品摄影 |
默认规则:除太阳光外,几乎所有情况都使用 Area 灯光。柔和阴影无需额外设置。
决策树
What's the mood?
├── Studio / commercial → Three-point lighting (key+fill+rim) + HDRI fill 0.3
├── Outdoor / sunlit → Sun + HDRI sky environment
├── Indoor cinematic → Sun through window + HDRI low + practicals (lamps as Point)
├── Dramatic / noir → Single Spot at high angle, no fill
├── Stylized / cartoon → Three-point with high contrast + saturated key color
└── Unsure → Three-point with HDRI grounding (works for 90% of cases)
参考外观衔接
如果目标是匹配原始图像/参考图像,而不是渲染出一般意义上好看的效果,请加载 reference-look-calibration。它负责测量色相/饱和度/明度、物体范围、辉光/光晕颜色以及前后外观指标。随后,本技能应在该校准目标范围内应用所需的材质/灯光/渲染更改。
配方
辅助函数:aim_at(light, target) — 面向主体布光时必需
下面的配方 1 将灯光放置在固定的世界坐标位置,并使用硬编码的旋转。对于位于世界原点、大小为 1 米的一般主体,这样做没问题。对于任何其他主体(小型珠宝、高大长剑、庞大的建筑),都需要让灯光对准主体。使用此辅助函数:
from mathutils import Vector
def aim_at(light_obj, target):
"""Aim a light at a world-space target.
target may be a Vector or a tuple/list (x, y, z) or a Blender object.
"""
target_pos = Vector(target.location) if hasattr(target, 'location') else Vector(target)
direction = (target_pos - light_obj.location).normalized()
light_obj.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler()
辅助函数:基于场景的灯光定位
from mathutils import Vector
def compute_scene_bbox_center(meshes):
"""Average bbox center over a list of mesh objects (world space)."""
import bpy
deps = bpy.context.evaluated_depsgraph_get()
all_verts = []
for o in meshes:
eval_obj = o.evaluated_get(deps)
em = eval_obj.to_mesh()
for v in em.vertices:
all_verts.append(o.matrix_world @ v.co)
eval_obj.to_mesh_clear()
xs = [v.x for v in all_verts]
ys = [v.y for v in all_verts]
zs = [v.z for v in all_verts]
center = Vector(((min(xs)+max(xs))/2, (min(ys)+max(ys))/2, (min(zs)+max(zs))/2))
extent = max(max(xs)-min(xs), max(ys)-min(ys), max(zs)-min(zs))
return center, extent
配方 0a — 根据主体类别调整的三点布光(用于编排器 E2E)
通用三点布光(下方的配方 0b)使用固定的能量比例。这适用于不透明主体(椅子、长剑),但不适用于玻璃(轮廓光会冲淡体积着色),对木材来说也太冷(会失去温暖感)。
传入 subject_class 提示来调整布光设置:
| 类别 | 主光:补光:轮廓光比例 | 主光色温 | 原因 |
|---|---|---|---|
'metal' | 4:1:2(默认) | 暖色 3200K | 标准三点布光能很好地呈现金属质感 |
'glass' | 3:1:1.2 | 中性 5500K | 柔和轮廓光——强烈的轮廓光会冲淡体积着色;提高补光亮度,让透射颜色显现 |
'wood' | 4:1:1.5 | 暖色 3000K | 较暖的主光能突显木材色调;减少轮廓光(木材不需要增强轮廓) |
'fabric' | 3:1:0.5 | 中性 5500K | 柔和且均衡;填充光能呈现光泽 |
'skin' | 4:1:1 | 暖色 3500K | 暖色主光呈现健康肤色;微妙的轮廓光可避免面部边缘过于生硬 |
'product' | 5:1:1.5 | 中性 5000K | 更高的对比度;呈现商业化、干净利落的效果 |
| (未指定) | 回退到配方 0b(默认) | 暖色 3200K |
import bpy, math
from mathutils import Vector
def aim_at(light_obj, target):
target_pos = Vector(target.location) if hasattr(target, 'location') else Vector(target)
direction = (target_pos - light_obj.location).normalized()
light_obj.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler()
def apply_three_point(subject_class='metal'):
"""Configure 3-point lighting with subject-class-aware ratios.
subject_class: 'metal' | 'glass' | 'wood' | 'fabric' | 'skin' | 'product' | 'metal' (default)
"""
profiles = {
'metal': dict(ratio=(4.0, 1.0, 2.0), key_color=(1.0, 0.95, 0.85), fill_color=(0.85, 0.9, 1.0), rim_color=(0.7, 0.85, 1.0)),
'glass': dict(ratio=(3.0, 1.0, 1.2), key_color=(1.0, 0.98, 0.95), fill_color=(0.95, 0.95, 1.0), rim_color=(0.95, 0.95, 1.0)),
'wood': dict(ratio=(4.0, 1.0, 1.5), key_color=(1.0, 0.92, 0.78), fill_color=(0.95, 0.95, 1.0), rim_color=(0.85, 0.92, 1.0)),
'fabric': dict(ratio=(3.0, 1.0, 0.5), key_color=(1.0, 0.97, 0.92), fill_color=(0.92, 0.95, 1.0), rim_color=(0.95, 0.95, 1.0)),
'skin': dict(ratio=(4.0, 1.0, 1.0), key_color=(1.0, 0.93, 0.82), fill_color=(0.95, 0.96, 1.0), rim_color=(0.92, 0.92, 1.0)),
'product': dict(ratio=(5.0, 1.0, 1.5), key_color=(1.0, 0.98, 0.95), fill_color=(0.98, 0.98, 1.0), rim_color=(0.98, 0.98, 1.0)),
}
p = profiles.get(subject_class, profiles['metal'])
# Compute scene bbox
subject_meshes = [o for o in bpy.data.objects if o.type == 'MESH' and o.name.startswith('GEO-')]
deps = bpy.context.evaluated_depsgraph_get()
all_verts = []
for o in subject_meshes:
eo = o.evaluated_get(deps); em = eo.to_mesh()
for v in em.vertices: all_verts.append(o.matrix_world @ v.co)
eo.to_mesh_clear()
xs = [v.x for v in all_verts]; ys = [v.y for v in all_verts]; zs = [v.z for v in all_verts]
center = Vector(((min(xs)+max(xs))/2, (min(ys)+max(ys))/2, (min(zs)+max(zs))/2))
biggest = max(max(zs)-min(zs), max(xs)-min(xs))
light_dist = max(biggest * 1.5, 1.0)
# Energy scales with distance²
base_energy = 100 * (light_dist / 1.5) ** 2
key_e, fill_e, rim_e = (base_energy * r for r in p['ratio'])
# Remove existing lights
for o in list(bpy.data.objects):
if o.type == 'LIGHT' and (o.name.startswith('LGT-key') or o.name.startswith('LGT-fill') or o.name.startswith('LGT-rim')):
bpy.data.objects.remove(o, do_unlink=True)
# KEY (warm, front-right above)
key = bpy.data.objects.new('LGT-key', bpy.data.lights.new('LGT-key', type='AREA'))
key.data.energy = key_e; key.data.size = 0.5; key.data.color = p['key_color']
bpy.context.collection.objects.link(key)
key.location = (center.x + light_dist*0.7, center.y - light_dist*0.7, center.z + light_dist*0.5)
aim_at(key, center)
# FILL (cool, opposite, weaker)
fill = bpy.data.objects.new('LGT-fill', bpy.data.lights.new('LGT-fill', type='AREA'))
fill.data.energy = fill_e; fill.data.size = 1.0; fill.data.color = p['fill_color']
bpy.context.collection.objects.link(fill)
fill.location = (center.x - light_dist*0.7, center.y - light_dist*0.5, center.z + light_dist*0.3)
aim_at(fill, center)
# RIM
rim_type = 'AREA' if subject_class == 'glass' else 'SPOT'
rim = bpy.data.objects.new('LGT-rim', bpy.data.lights.new('LGT-rim', type=rim_type))
rim.data.energy = rim_e; rim.data.color = p['rim_color']
if rim_type == 'AREA':
rim.data.size = 1.5 # larger soft-source for glass
else:
rim.data.spot_size = math.radians(50)
bpy.context.collection.objects.link(rim)
rim.location = (center.x, center.y + light_dist, center.z + light_dist*0.5)
aim_at(rim, center)
print(f"lighting:{subject_class} key:fill:rim={p['ratio']} dist={light_dist:.2f}m")
# Usage:
# apply_three_point('glass') # for the wine bottle
# apply_three_point('wood') # for the chair
# apply_three_point('metal') # for the sword (or omit; 'metal' is default)
配方 0b — 对准主体的三点布光(通用,不指定类别)
当有特定主体但无需考虑其类别时,请使用此配方代替配方 1。灯光会根据主体的最大尺寸按比例放置。
import bpy, math
from mathutils import Vector
def aim_at(light_obj, target):
target_pos = Vector(target.location) if hasattr(target, 'location') else Vector(target)
direction = (target_pos - light_obj.location).normalized()
light_obj.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler()
# Determine subject and its scale
subject_meshes = [o for o in bpy.data.objects if o.type == 'MESH' and o.name.startswith('GEO-')]
deps = bpy.context.evaluated_depsgraph_get()
all_verts = []
for o in subject_meshes:
eo = o.evaluated_get(deps); em = eo.to_mesh()
for v in em.vertices:
all_verts.append(o.matrix_world @ v.co)
eo.to_mesh_clear()
xs = [v.x for v in all_verts]; ys = [v.y for v in all_verts]; zs = [v.z for v in all_verts]
center = Vector(((min(xs)+max(xs))/2, (min(ys)+max(ys))/2, (min(zs)+max(zs))/2))
extent = max(max(xs)-min(xs), max(ys)-min(ys), max(zs)-min(zs))
light_dist = max(extent * 1.5, 1.0)
# Energy values scale roughly inversely with squared distance from subject — recipe targets
# physically reasonable values for a ~1m subject at ~1.5m light distance.
key_energy = 100 * (light_dist / 1.5) ** 2
fill_energy = key_energy * 0.3
rim_energy = key_energy * 0.8
# KEY (warm, front-right above)
key = bpy.data.objects.new('LGT-key', bpy.data.lights.new('LGT-key', type='AREA'))
key.data.energy = key_energy; key.data.size = 0.5; key.data.color = (1.0, 0.95, 0.85)
bpy.context.collection.objects.link(key)
key.location = (center.x + light_dist * 0.7, center.y - light_dist * 0.7, center.z + light_dist * 0.5)
aim_at(key, center)
# FILL (cool, opposite, weaker)
fill = bpy.data.objects.new('LGT-fill', bpy.data.lights.new('LGT-fill', type='AREA'))
fill.data.energy = fill_energy; fill.data.size = 1.0; fill.data.color = (0.85, 0.9, 1.0)
bpy.context.collection.objects.link(fill)
fill.location = (center.x - light_dist * 0.7, center.y - light_dist * 0.5, center.z + light_dist * 0.3)
aim_at(fill, center)
# RIM (cool, behind, separates subject from BG)
rim = bpy.data.objects.new('LGT-rim', bpy.data.lights.new('LGT-rim', type='SPOT'))
rim.data.energy = rim_energy; rim.data.color = (0.7, 0.85, 1.0); rim.data.spot_size = math.radians(50)
bpy.context.collection.objects.link(rim)
rim.location = (center.x, center.y + light_dist, center.z + light_dist * 0.5)
aim_at(rim, center)
print(f'lighting:three_point_aimed center={tuple(round(v,2) for v in center)} extent={extent:.2f}m dist={light_dist:.2f}m')
配方 0c — 实用灯光(场景包含自身的发光光源)
当主体本身就是或包含光源时——台灯灯泡、蜡烛火焰、发光屏幕的显示器、霓虹灯牌——场景需要采用不同的设置:
- 将世界背景调暗(强度为 0.10–0.20)。否则,灯泡的光照贡献会被环境光淹没。
- 降低或移除标准的三点布光中的补光/轮廓光。应让实用光源占主导。
- 保留微弱的环境补光(从相机方向投射的 8–15 W 面光源),以便看清灯具本身——仅使用实用光源渲染会让灯具形状的剪影陷入阴影。
- 将发光强度调得很高,适用于小型网格发光体(参见
blender-materials配方 11b——灯泡球体的强度需要达到 800–3000,才能呈现出真实灯泡的效果)。 - Cycles 的
max_bounces≥ 16,以正确照亮灯罩内部——灯泡的光需要在灯罩内反弹,再从开口射出。
import bpy
from mathutils import Vector
def aim_at(light_obj, target):
target_pos = Vector(target.location) if hasattr(target, 'location') else Vector(target)
direction = (target_pos - light_obj.location).normalized()
light_obj.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler()
# Dim world (let the practical dominate)
world = bpy.context.scene.world
world.use_nodes = True
nodes = world.node_tree.nodes
for n in list(nodes): nodes.remove(n)
output = nodes.new('ShaderNodeOutputWorld')
bg = nodes.new('ShaderNodeBackground')
bg.inputs['Color'].default_value = (0.02, 0.02, 0.03, 1.0)
bg.inputs['Strength'].default_value = 0.15
world.node_tree.links.new(bg.outputs['Background'], output.inputs['Surface'])
# Single subtle ambient fill from camera direction
fill = bpy.data.objects.new('LGT-ambient_fill', bpy.data.lights.new('LGT-ambient_fill', type='AREA'))
fill.data.energy = 8; fill.data.size = 1.0; fill.data.color = (0.85, 0.9, 1.0)
bpy.context.collection.objects.link(fill)
fill.location = (0.5, -0.8, 0.5)
aim_at(fill, Vector((0, 0, 0.3)))
# Cycles bounces
scene = bpy.context.scene
scene.cycles.max_bounces = 16
print('lighting:practical_setup')
实用光源的发光着色器(网格发光灯泡/蜡烛火焰等)会处理其余部分。场景看起来就像对发光主体进行的真实摄影——周围环境昏暗,实用光源投下温暖的光池,主体的轮廓也被柔和地照亮。
验证示例:参见 text-to-blender/assets/v1.1.0-validation/desk_lamp_emission.webp,了解此设置的效果(台灯灯泡发光清晰,桌面上有温暖的光池,灯具本身在暗色场景中清晰可见)。
配方 1 — 三点布光(经典设置)
import bpy, math
# KEY LIGHT (warm, front-right)
key_data = bpy.data.lights.new('LGT-key', type='AREA')
key_data.energy = 1000
key_data.size = 1.0
key_data.color = (1.0, 0.95, 0.85) # warm tungsten ~3200K
key = bpy.data.objects.new('LGT-key', key_data)
bpy.context.collection.objects.link(key)
key.location = (3, -3, 3.5)
key.rotation_euler = (math.radians(35), math.radians(45), 0)
# FILL LIGHT (cool, front-left, weaker)
fill_data = bpy.data.lights.new('LGT-fill', type='AREA')
fill_data.energy = 300
fill_data.size = 2.0
fill_data.color = (0.85, 0.9, 1.0) # cool sky ~6500K
fill = bpy.data.objects.new('LGT-fill', fill_data)
bpy.context.collection.objects.link(fill)
fill.location = (-3, -2, 2.5)
fill.rotation_euler = (math.radians(50), math.radians(-45), 0)
# BACK / RIM LIGHT (cool, behind subject)
rim_data = bpy.data.lights.new('LGT-rim', type='SPOT')
rim_data.energy = 600
rim_data.spot_size = math.radians(40)
rim_data.color = (0.7, 0.85, 1.0)
rim = bpy.data.objects.new('LGT-rim', rim_data)
bpy.context.collection.objects.link(rim)
rim.location = (0, 4, 3.0)
rim.rotation_euler = (math.radians(120), 0, math.radians(180))
print('lighting:three_point')
标准比例:
- 高调(商业):主光:补光 = 2:1
- 中调(肖像):4:1
- 低调(戏剧效果):8:1+
- 轮廓光:主光的 50–100%
配方 2 — HDRI 环境
import bpy
world = bpy.context.scene.world
world.use_nodes = True
nodes = world.node_tree.nodes
links = world.node_tree.links
# Wipe existing world nodes
for n in list(nodes):
nodes.remove(n)
# Output → Background ← Environment Texture ← Mapping ← Texture Coordinate
output = nodes.new('ShaderNodeOutputWorld'); output.location = (300, 0)
bg = nodes.new('ShaderNodeBackground'); bg.location = (100, 0)
bg.inputs['Strength'].default_value = 1.0
env = nodes.new('ShaderNodeTexEnvironment'); env.location = (-100, 0)
env.image = bpy.data.images.load('/path/to/your.hdr') # ← user provides path
mapping = nodes.new('ShaderNodeMapping'); mapping.location = (-300, 0)
tex_coord = nodes.new('ShaderNodeTexCoord'); tex_coord.location = (-500, 0)
links.new(tex_coord.outputs['Generated'], mapping.inputs['Vector'])
links.new(mapping.outputs['Vector'], env.inputs['Vector'])
links.new(env.outputs['Color'], bg.inputs['Color'])
links.new(bg.outputs['Background'], output.inputs['Surface'])
print('lighting:hdri')
专业资源:polyhaven.com/hdris 提供免费的 HDRI(CC0)。
配方 3 — 晴天户外
import bpy, math
sun_data = bpy.data.lights.new('LGT-sun', type='SUN')
sun_data.energy = 5.0
sun_data.color = (1.0, 0.95, 0.8) # golden hour
sun_data.angle = math.radians(0.5) # realistic sun size; bigger = softer
sun = bpy.data.objects.new('LGT-sun', sun_data)
bpy.context.collection.objects.link(sun)
sun.location = (0, 0, 10)
sun.rotation_euler = (math.radians(45), math.radians(15), 0)
print('lighting:outdoor_sun')
与 HDRI 天空环境(配方 2)结合,可获得自然的环境补光。
配方 4 — 室内窗光
import bpy, math
# Sun coming through window — cool blue, sharp
sun_data = bpy.data.lights.new('LGT-window_sun', type='SUN')
sun_data.energy = 3.0
sun_data.color = (0.85, 0.9, 1.0)
sun_data.angle = math.radians(2.0) # softer than direct sun
sun = bpy.data.objects.new('LGT-window_sun', sun_data)
bpy.context.collection.objects.link(sun)
sun.location = (5, -3, 4)
sun.rotation_euler = (math.radians(60), math.radians(-30), 0)
# Practical lamp — warm point light
lamp_data = bpy.data.lights.new('LGT-lamp', type='POINT')
lamp_data.energy = 60
lamp_data.color = (1.0, 0.7, 0.4) # warm bulb
lamp = bpy.data.objects.new('LGT-lamp', lamp_data)
bpy.context.collection.objects.link(lamp)
lamp.location = (-1, 2, 1.5)
print('lighting:indoor_window')
配方 5 — 戏剧化单光源
import bpy, math
spot_data = bpy.data.lights.new('LGT-drama', type='SPOT')
spot_data.energy = 800
spot_data.spot_size = math.radians(30)
spot_data.spot_blend = 0.3
spot_data.color = (1.0, 0.95, 0.85)
spot = bpy.data.objects.new('LGT-drama', spot_data)
bpy.context.collection.objects.link(spot)
spot.location = (2, -2, 6)
spot.rotation_euler = (math.radians(60), 0, 0)
print('lighting:dramatic')
如需完整的黑色电影风格,可搭配强体积效果(氛围)——体积效果的设置参见 references/overview.md。
配方 6 — 色温速查表
| 光源 | RGB |
|---|---|
| 蜡烛(1850K) | (1.0, 0.6, 0.3) |
| 白炽灯(3200K) | (1.0, 0.85, 0.6) |
| 暖色 LED(3000K) | (1.0, 0.8, 0.6) |
| 日落/金色(3500K) | (1.0, 0.85, 0.65) |
| 正午日光(5500K) | (1.0, 1.0, 1.0) |
| 阴天(6500K) | (0.95, 0.95, 1.0) |
| 蓝调时刻(8000K) | (0.8, 0.9, 1.0) |
专业混光:暖色主光(白炽灯)+ 冷色补光(日光)= 好莱坞风格的“金色/青绿色”效果。
配方 7 — 柔和与硬阴影调整
import bpy, math
light_data = bpy.data.lights['LGT-key']
# Make shadows softer
light_data.size = 2.0 # Area: bigger size = softer shadow
# Or for Sun:
# light_data.angle = math.radians(5) # bigger angle = softer shadow
# Make shadows harder (crisp)
# light_data.size = 0.1
# Or:
# light_data.angle = math.radians(0.5)
命名约定
| 前缀 | 含义 |
|---|---|
LGT-key | 主光 |
LGT-fill | 补光 |
LGT-rim / LGT-back | 轮廓光或背光 |
LGT-sun | 太阳光 |
LGT-{name} | 实用光源(灯具、蜡烛、霓虹灯等) |
常见问题
| 现象 | 解决方法 |
|---|---|
| 模型有一半漆黑一片 | 添加补光(面光源或 HDRI) |
| 渲染效果“扁平” | 提高主光与补光的比例;添加轮廓光 |
| 到处都是硬阴影 | 增大面光源尺寸或太阳光角度 |
| 整体太暗 | 提高视图变换曝光或 HDRI 强度 |
| 材质上没有反射 | 始终设置世界环境(HDRI) |
| 光源位于物体内部 | 检查世界坐标;光源必须能被相机看到 |
| 背光导致主体过曝 | 轮廓光能量 ≤ 主光能量 |
何时加载 references/overview.md
在以下情况下加载:
- 用户要求设置配方中未涵盖的效果(体积光束、灯光组、灯光链接)
- 需要超出默认值的 HDRI 旋转/强度调整
- 多光源场景(5 个以上灯具)需要整理
- 涉及具体的色彩科学或色彩管理设置(AgX、Filmic)
参考文档涵盖:对全部 5 种灯光类型的深入介绍、完整的 HDRI 工作流、用于在合成器中重新布光的灯光组,以及产品、肖像、建筑、角色和动画效果的配方。