WF 11: ARCHVIZ DAY→NIGHT
RU: Sun+HDRI morph, lightmix, 2 renders blend EN: Sun+HDRI morph, lightmix, 2 renders blend Один .blend , который даёт два состояния — яркий день и ночь — без дублирования сцены, и один параметр daynight от 0 (ночь) до 1 (день), который переключает их. Ключевая мысль: день и ночь различаются не «ярк
ID: WF-11 | Type: ArchViz | Difficulty: ⭐⭐ RU/EN Bilingual
🎯 ЦЕЛЬ / Goal
RU: Sun+HDRI morph, lightmix, 2 renders blend EN: Sun+HDRI morph, lightmix, 2 renders blend
Один .blend, который даёт два состояния — яркий день и ночь — без дублирования
сцены, и один параметр daynight от 0 (ночь) до 1 (день), который переключает их.
Ключевая мысль: день и ночь различаются не «яркостью», а составом света. Днём
источник один — солнце, и всё остальное почти не работает. Ночью источников много
(фонари, окна, интерьер), они разного цвета и стоят на разной высоте. Если просто
потушить Sun.energy, получится не ночь, а выключенный свет.
📋 ЧТО ПОНАДОБИТСЯ
| Компонент | Версия | Зачем |
|---|---|---|
| Blender | 4.x | Cycles (EEVEE годится для превью) |
| HDRI day + HDRI night | Poly Haven, 1–2k | рефлексы в стекле и на полу |
| Модель помещения/экстерьера | GLB/FBX | геометрия |
| Эмиссивные материалы на окнах | — | «живые» окна ночью |
🛠 НАСТРОЙКА
Этап 1. Одна ручка на весь свет
Все источники, HDRIs и эмиссия завязываются на одно свойство сцены. Это и есть «lightmix» из постановки — не микс-нода, а один параметр.
import bpy, math
scene = bpy.context.scene
scene["daynight"] = 1.0 # 1 = день, 0 = ночь
scene.id_properties_ui("daynight").update(
min=0.0, max=1.0, description="0 = ночь, 1 = день")
def drive(owner, path, index, expr):
"""Драйвер от scene['daynight'] с именем переменной t."""
fc = owner.driver_add(path, index) if index >= 0 else owner.driver_add(path)
d = fc.driver
d.type = 'SCRIPTED'
v = d.variables.new()
v.name, v.type = "t", 'SINGLE_PROP'
v.targets[0].id = scene
v.targets[0].data_path = '["daynight"]'
d.expression = expr
return d
[!NOTE] Шаг с
id— не формальность. УDriverTargetесть отдельное полеid_type, и его значение по умолчанию —'OBJECT': присваивание сцены проверяется по нему и падает сTypeError: DriverTarget.id expected a Object type, not Scene(проверено на 5.2.1 LTS). Если в твоей версии ругается именно на эту строку, поставь рядомv.targets[0].id_type = 'SCENE'до присваиванияid. Список допустимых значений:[i.identifier for i in v.targets[0].bl_rna.properties["id_type"].enum_items].
Этап 2. Солнце: высота, энергия, температура
bpy.ops.object.light_add(type='SUN', location=(0, 0, 10))
sun = bpy.context.object
sun.name = "Sun"
# Половина углового диаметра Солнца (~0.53°) — иначе тени неестественно резкие
sun.data.angle = math.radians(0.53)
sun.rotation_euler = (math.radians(52), 0, math.radians(-70))
# Ночью солнце уходит под горизонт и гаснет. Квадрат — чтобы в сумеречный
# момент не было вспышки на линейной шкале.
drive(sun, "rotation_euler", 0, "0.12 + 0.79*t") # 7° → 52°
drive(sun.data, "energy", -1, "0.02 + 4.8*t*t")
# Цвет: ночью холодный и синий, днём тёплый. Три драйвера — по одному на канал.
drive(sun.data, "color", 0, "0.45 + 0.55*t")
drive(sun.data, "color", 1, "0.60 + 0.36*t")
drive(sun.data, "color", 2, "1.00 - 0.18*t")
Этап 3. World: небо днём, HDRI ночью
world = bpy.data.worlds.new("ArchViz") if not bpy.data.worlds else bpy.data.worlds[0]
scene.world = world
world.use_nodes = True
nt = world.node_tree
nt.nodes.clear()
tex_env = nt.nodes.new("ShaderNodeTexEnvironment")
tex_sky = nt.nodes.new("ShaderNodeTexSky")
mix = nt.nodes.new("ShaderNodeMixShader")
bg = nt.nodes.new("ShaderNodeBackground")
bg_day = nt.nodes.new("ShaderNodeBackground") # почти прозрачный — свет даёт Sun
bg_night= nt.nodes.new("ShaderNodeBackground")
out = nt.nodes.new("ShaderNodeOutputWorld")
tex_sky.sky_type = 'NISHITA'
tex_sky.sun_elevation = math.radians(40)
tex_sky.sun_rotation = math.radians(-70)
tex_env.image = bpy.data.images.load("//hdri/night_street_1k.hdr")
bg_night.inputs["Strength"].default_value = 0.25
bg_day.inputs["Strength"].default_value = 0.0 # днём небо рисует Sun, а не фон
nt.links.new(tex_sky.outputs["Color"], bg.inputs["Color"])
nt.links.new(tex_env.outputs["Color"], bg_night.inputs["Color"])
nt.links.new(tex_sky.outputs["Color"], mix.inputs[1])
nt.links.new(bg_night.outputs["Background"], mix.inputs[2])
nt.links.new(bg.outputs["Background"], mix.inputs[1])
nt.links.new(mix.outputs["Shader"], out.inputs["Surface"])
[!NOTE] У
ShaderNodeTexSkyв разных версиях менялись имена входов (sun_elevation,sun_rotation,sun_intensity) и набор режимовsky_type. Проверь список входов у ноды в своей версии — если поле называется иначе, код упадёт наtex_sky.xxx.
Этап 4. Ночной свет: окна и интерьер
Окна — не лампы, а эмиссия на материале. Это даёт мягкое свечение, которое видно и в отражениях, и на фасаде.
# Окна
mat_win = bpy.data.materials["Mat_Window"]
pbsdf = mat_win.node_tree.nodes["Principled BSDF"]
drive(pbsdf.inputs["Emission Strength"], "default_value", -1, "6.0*(1.0 - t)")
# Уличные фонари: Point/Area, гаснут днём
for name, energy_day in (("Lamp_01", 180.0), ("Lamp_02", 140.0)):
ob = bpy.data.objects.get(name)
if ob:
drive(ob.data, "energy", -1, f"{energy_day}*(1.0 - t)")
# Интерьерный свет — самый слабый по мощности, но самый заметный для глаза
drive(bpy.data.objects["Interior_Fill"].data, "energy", -1, "45.0*(1.0 - t)")
Этап 5. Рендер двух состояний
scene.render.engine = 'CYCLES'
scene.cycles.samples = 256
scene.cycles.use_denoising = True
scene.render.resolution_x, scene.render.resolution_y = 1920, 1080
scene.render.image_settings.file_format = 'PNG'
scene.render.image_settings.color_depth = '16'
for name, t in (("day", 1.0), ("night", 0.0)):
scene["daynight"] = t
bpy.context.view_layer.update() # без этого драйверы не пересчитаются
scene.render.filepath = f"//render/interior_{name}.png"
bpy.ops.render.render(write_still=True)
Этап 6. Сведение в композит-нодах
scene.use_nodes = True
nt = scene.node_tree
nt.nodes.clear()
img_day = nt.nodes.new("CompositorNodeImage"); img_day.image = bpy.data.images.load("//render/interior_day.png")
img_night = nt.nodes.new("CompositorNodeImage"); img_night.image= bpy.data.images.load("//render/interior_night.png")
glare = nt.nodes.new("CompositorNodeGlare")
glare.glare_type = 'FOG_GLOW' # только ночью; для дня отключи ноду
mixc = nt.nodes.new("CompositorNodeMixRGB")
comp = nt.nodes.new("CompositorNodeComposite")
nt.links.new(img_day.outputs["Image"], mixc.inputs[1])
nt.links.new(img_night.outputs["Image"], mixc.inputs[2])
nt.links.new(glare.outputs["Image"], mixc.inputs[2])
nt.links.new(mixc.outputs["Image"], comp.inputs["Image"])
drive(mixc.inputs[0], "default_value", -1, "1.0 - t") # t=1 → день, t=0 → ночь
scene.render.filepath = "//render/interior_dn_"
bpy.ops.render.render(animation=True)
[!NOTE] В свежих версиях Blender нода Glare получила порты вместо полей:
Threshold,StrengthиQualityмогут быть входами. Еслиglare.glare_typeне пишется — открой ноду в композиторе и посмотри, что осталось свойством, а что стало входом.
Этап 7. Анимация перехода «день → ночь»
Сам переход делается анимацией одного ключа: 48 кадров (2 с при 24 fps) с линейной
интерполяцией. Все источники разъезжаются сами, потому что завязаны на daynight.
# Ключ на самом свойстве сцены — единственное, что нужно анимировать
scene["daynight"] = 1.0
scene.keyframe_insert('["daynight"]', frame=1)
scene["daynight"] = 0.0
scene.keyframe_insert('["daynight"]', frame=48)
# Тип интерполяции задаётся в Graph Editor (F-кривая сцены).
# Линейная обязательна, иначе между ключами будет «плато».
scene.frame_start, scene.frame_end = 1, 48
scene.render.filepath = "//render/dn_transition_"
bpy.ops.render.render(animation=True)
⚙️ КЛЮЧЕВЫЕ КОМАНДЫ И ПАРАМЕТРЫ
| Параметр | Значение | Когда менять |
|---|---|---|
scene["daynight"] |
0…1 | единственная ручка; 0.3–0.7 даёт «сумерки» |
sun.data.angle |
0.53° | до 2° = мягкие тени, «пасмурно» |
bg_night.Strength |
0.25 | поднимать, если ночью не видно отражений |
Emission Strength |
6.0 | выше 10 — окна «сгорают» в чистом виде |
cycles.samples |
256 | 512 для финального света с окнами |
📤 РЕЗУЛЬТАТ
| Параметр | Значение |
|---|---|
| Файлы | interior_day.png, interior_night.png (1920×1080, 16 бит) + interior_dn_####.png |
| Сегмент анимации | 2 состояния × 1 кадр, либо 24-кадровый переход при анимации daynight |
.blend |
один файл, в котором слайдер переключает свет без пересборки |
| Рендер | 256 сэмплов, 1920×1080, interior: 4–12 мин/кадр; два состояния ≈ 10–25 мин |
Переход «день→ночь» как видео делается анимацией одного ключа на scene["daynight"]
с линейным интерпейсом: 48 кадров (2 с при 24 fps) — и микшёр сам разедет свет,
HDRIs, эмиссию и фонари синхронно.
Проверяется одним скриптом. Он делает три вещи: пересчитывает драйверы и печатает, что каждый источник действительно поехал; считает драйверы по всем объектам сцены (ползунок есть, а половина света его не слушает — самая частая поломка этой схемы); сверяет, что на диске лежат оба состояния и вся серия перехода.
import bpy, os, glob
scene = bpy.context.scene
sun = bpy.data.objects["Sun"]
# 1. Пересчёт драйверов. В фоновом режиме (-b) одного
# view_layer.update() мало: сцену надо пометить update_tag, иначе
# значения остаются от предыдущего вызова.
def apply_daynight(t):
scene["daynight"] = t
scene.update_tag()
bpy.context.evaluated_depsgraph_get().update()
for t in (1.0, 0.5, 0.0):
apply_daynight(t)
print("t=%.1f наклон=%.3f рад энергия=%.3f красный=%.3f"
% (t, sun.rotation_euler.x, sun.data.energy, sun.data.color[0]))
# 2. Все ли источники подписаны на ползунок
def driven(owner):
ad = getattr(owner, "animation_data", None)
return len(ad.drivers) if ad else 0
lamps = [n for n in ("Lamp_01", "Lamp_02") if n in bpy.data.objects]
print("драйверов Sun.data:", driven(sun.data),
"| фонарей:", sum(driven(bpy.data.objects[n].data) for n in lamps),
"из", len(lamps))
# Драйвер на «Emission Strength» висит на дереве нод, а не на самой ноде
win = bpy.data.materials["Mat_Window"]
pb = next(n for n in win.node_tree.nodes if n.type == 'BSDF_PRINCIPLED')
print("драйверов на 'Emission Strength':", driven(pb.inputs["Emission Strength"].id_data))
# 3. Файлы на диске
out = bpy.path.abspath("//render")
print("day:", os.path.exists(os.path.join(out, "interior_day.png")),
"| night:", os.path.exists(os.path.join(out, "interior_night.png")))
seq = sorted(glob.glob(os.path.join(out, "interior_dn_*.png")))
print("кадров перехода:", len(seq), "| ожидалось:", scene.frame_end)
энергия на t=0.0 должна упасть практически до нуля (в примере выше — 0.02), а
красный канал — до 0.45: если оба остались дневными, драйверы не считаются и ты
рендеришь два одинаковых кадра. Ноль драйверов на фонаре означает, что имя объекта в
цикле не совпало с тем, что лежит в сцене — проверка по n in bpy.data.objects
именно для этого и сделана.
⚠️ ТИПОВЫЕ ОШИБКИ
| Симптом | Причина | Решение |
|---|---|---|
| Ночью сцена чёрная | Sun всё ещё даёт энергию либо HDRI не назначен |
проверить scene.world и tex_env.image; при t=0 энергия солнца должна быть ≈ 0 |
| Днём небо плоское | в Background дневного неба Strength = 0 |
это нормально: небо рисует Sun; если нужно и небо — верни небо в Background |
Переключил daynight, картинка не изменилась |
драйверы не пересчитались | bpy.context.view_layer.update() или scene.frame_set(frame + 1) |
| Слишком жёлтый ночной свет | не разделён цвет фонарей и окон | температуру задавать в light.data.color, а не тонировать кадр в композитe |
| Окна светятся днём | забыл Emission Strength в драйвер |
(1.0 - t) обязателен на каждой эмиссии |
| Тени «плывут» при анимации | солнце ключевано без Rotation/Rotation Mode |
проверить sun.rotation_mode и единицы (DEGREES) |
🔗 СВЯЗИ
- Прочие в категории «Воркфлоу»: WF 06: ARCHVIZ WORKFLOW, WF 24: PHOTOGRAMMETRY CLEANUP, WF 01: TEXT-TO-SCENE, WF 02: IMAGE-TO-3D, WF 03: TEXTURE PIPELINE
- По темам: CASE 01: ARCHVIZ INTERIOR, CASE 08: SCI-FI CORRIDOR MODULAR, WF 04: CHARACTER ANIMATION
- Теги: #archviz, #workflow
Связанные материалы
Подобраны по тегам и категории.