Blender AI Wiki / Русский Поиск

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 (день), который переключает их. Ключевая мысль: день и ночь различаются не «ярк

🔄 Воркфлоу ⭐⭐ полная 8 блоков кода обновлено 2026-11-12

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)

🔗 СВЯЗИ

Связанные материалы

Подобраны по тегам и категории.