Физически корректное освещение в UE4
Даниил Лихоманов — автор и основатель курсов по UE4 Unrealskills — рассказывает о тонкостях физически корректного освещения в Unreal Engine 4.
Это — результат исследования движка, поиска оптимального пайплайна настройки света с физически корректными данными. На момент моего исследования (лето 2019) в сети не было руководства о физическом свете, удалось раскопать только это видео с аналогичными догадками.
Сейчас уже есть весьма неплохой пайплайн с блупринт-хелпером, с которым я буду сравнивать собственный подход. Забегая вперёд скажу: судя по всему, мне удалось попасть в точку (сравнивая мой подход и второе видео). В конце я обозначу и сравню тонкости обоих вариантов.
Что такое Physically-Based Lighting (PBL) в контексте настройки движка?
- Близкое к реальному миру освещение за счёт приближенной к реальным значениям силы источников света
- Настройки камеры такие же, как при живой съёмке
Использование PBL даёт
- Математически правильную настройку освещения
- Корректное соотношение искусственного и естественного освещения
- Высокую точность яркости осветительных приборов (когда конкретную лампочку надо перенести из реальности в виртуальный мир вместе с характеристиками освещаемости)
- Возможность докопаться до сути и открыть новые глубины UE4 (мой случай)
Реальный мир
Наши глаза очень гибкие: переключаясь взглядом с яркого на темное и обратно мы не ощущаем какого-то изменения. Другими словами, зрение адаптивно. Нам одинаково виден вид из окна в солнечный день и комната, в которой мы стоим, хотя освещённость двух мест сильно отличается.
У фотокамер за это отвечает экспозиция: выравнивает яркость в зависимости от освещения. Обычно под каждую сцену подбирают свои значения экспозиции. Профессиональные фотографы ставят параметры для кадра исходя из опыта. Таблицы с примерными числами экспозиции под разные сцены можно найти на тематических форумах и википедии (ссылка ниже).
Прежде чем приступить к настройкам и экспериментам, мне пришлось глубоко нырнуть в теорию по освещению и фотографии, в формулы и понятия, которые там используются.
Люмены, люксы и канделы — единицы измерения света в реальном мире.
- Канделы — сила испускаемого света, без привязки к углу светового потока. Если увеличивать угол света и оставлять силу в КД постоянной, яркость будет постоянной, т.к. КД — это насколько ярко светит источник света, независимо от угла.
- Люмены — сила света, выпущенного источником. Если увеличить угол света, яркость уменьшится, т.к. количество выпущенного света остается постоянным.
- Люксы — сколько света попадает на поверхность.
Представим, что свет — поток воды. Есть кран (источник света), из него течёт вода (свет). Пусть мы льём обычной струёй воды силой в 500 люмен, то есть 500 Люмен воды выходит из крана за единицу времени. Рукой мы можем ощущать напор. Если мы сохраним 500 Люмен, но будем разбрызгивать их через душ, напор будет ощущаться гораздо слабее. Люмены — это сколько света выходит из источника.
Что касается Кандел — напор воды из крана и из душа будет ощущаться одинаково. Кандела — единица измерения, привязываемая к площади распространения. Таким образом, если мы зафиксируем 500 Кандел, при увеличении угла освещения увеличится и сила света. Другими словами, больше угол — больше испускаемого света. Канделы = напор.
Люксы. Или что нам на самом деле понадобится для стыковки реальности и виртуального освещения. Хотя Люмены и Канделы тоже не забудем.
Запомним соотношение: 1 лк = 1кд/м2
Люксы — количество света, принимаемого поверхностью, освещённость. От значений в люксах мы будем отталкиваться, чтобы переносить реальное освещение в параметры освещения UE4, потому что другими способами это выйдет крайне неточно или вообще не получится. Я пробовал ☺.
Есть несколько способов вычислить значение освещённости в реальной жизни. Профессионалы используют люксометры, но я предлагаю использовать смартфоны. Почти в каждом есть датчик освещённости.
Скачайте приложение, которое выдаёт значения в люксах. Photo Friend — хороший вариант, потому что рекомендует оптимальное значение EV100 (о нем позже), которое нам понадобится внутри движка. Приложения надо искать по запросу Lux meter.
Если вы пройдётесь по квартире или улице, держа телефон горизонтально, то получите значения освещённости той точки, где держите смартфон. Я проводил замеры телефоном для сопоставления результатов внутри движка. В UE4 есть аналогичная возможность измерять люксы в разных частях сцены — именно так мы будем сравнивать реальные и виртуальные значения.
Выше — значения, которые я замерил вручную, ниже — таблица с примерными значениями в разных условиях. Можно ориентироваться на эти числа при настройке.
Экспозиция и число EV100
Второй параметр, от которого зависит точность настройки света — экспозиция в сцене. Правильно выставленный свет даст корректное освещение по интенсивности, а правильно подобранный EV100 — корректное отображение кадра с этим освещением.
EV100 — это число экспозиции, которое используется для ее настройки.
Оно объединяет в себе 3 основные настройки камеры:
- ISO — сколько света попадает на матрицу
- Shutter Speed — скорость затвора
- Aperture — диафрагма
Так нам не понадобится тонко разбираться в настройках камеры, будет достаточно одного числа экспозиции. На изображении указаны стандартные настройки камеры в движке. Они находятся в PostProcessVolume, хотя особо и не пригодятся.
Рекомендую просто использовать таблицу EV100 из википедии.
Настройки редактора и сцены в UE4
Итак, с реальностью более-менее разобрались. Теперь можно погружаться в движок и искать точки соприкосновения.
Для тестов будем использовать простой плейн с белым материалом. Параметры RGB (1,1,1), без отражений (Roughness=1, Metallic=0). Чисто белый цвет нужен, чтобы считывать освещенность без потерь, т.е. получать действительные значения.
В качестве измерительного прибора нам понадобится Pixel Inspector, в котором мы и будем смотреть на значения Luminance.
Pixel Inspector дает информацию о пикселе под курсором. Инструмент начнет работу при нажатии кнопки с лупой. Нажав Esc можно заморозить значения в окне.
В Unreal Engine 4 для измерения освещённости поверхности используется параметр Luminance — количество отражённого поверхностью света. Найдите его в разделе HDR в Pixel Inspector’е.
Вот формулы, которые нужны для связки движкового Luminance и реального LUX (пожалуй, самая важная часть статьи!):
- Luminance = LUX / π
- LUX = Luminance * π
Откуда берётся число π? Здесь самая заковыристая часть, без которой ничего не срастется.
Задача: сопоставить значения из таблиц освещённости или телефонного приложения со значениями внутри движка.
Решение и объяснение: Замеряя значения Luminance внутри движка, получаем освещённость конкретной точки на поверхности. Скажем так: тыкаем в пиксель и получаем освещённость, которая пришла в этот пиксель с поверхности.
В табличных же данных и телефонных мы получаем значения освещённости вокруг прибора. Так, чтобы перевести одни данные в другие нужно умножить интенсивность точки на число пи.
Luminance * 3.14 = LUX
Для измерения Luminance в движке используем абсолютно белый Plane без отражений, как уже было сказано.
Замеряем, кстати, запечённый свет, SkyLight — Static, Directional Light — Stationary.
Чтобы получать корректные значения и дальше необходимо выставить 2 параметра в настройках проекта и перезапустить редактор:
Extend Default luminance in Auto Exposure settings — после этой настройки в параметрах автоэкспозиции постпроцессинга Min Brightness и Max Brightness можно будет указывать именно число EV100 вместо количества света, как есть по умолчанию.
Apply Pre-exposure before writing to the scene color — обеспечивает поддержку HDR-значений освещенности, сверхяркие участки не будут обрезаться/clamp’иться.
Если вы всё сделали правильно, при включении Visualize->HDR вы увидите, что гистограмма экспозиции имеет диапазон от -10 до 20.
Алгоритм действий
Принцип: берём сферическую HDR-текстуру и стараемся воссоздать освещение, которое на ней запечатлено так, чтобы получить физически корректные значения внутри движка, сопоставимые с аналогичными в реальном мире. Сперва подбираем экспозицию для тестового кадра, затем вычисляем интенсивность освещения.
По шагам:
Исходные материалы
- Посетите сайт hdrihaven.com и найдите понравившуюся HDR-карту (качайте 8К). Для EV-значений пользуйтесь табличкой из википедии. Ношение телефона по разным местам с открытой приложухой для люксов поможет набить глаз.
- Качаем HDR-карту и импортируем в движок.
- Ссылка на проект UE4 для тестов. Внутри уже лежат текстуры, левел для настройки света, левел с помещением для тестов.
В сцене есть:
- SkySphere — сфера с Emissive-материалом, на котором можно регулировать интенсивность. В материале полноценная HDR-карта, сам материал можно найти и скопировать себе в проект отсюда: /EngineContent/EditorMaterials/AssetViewer/M_SkyBox
- SkyLight с той же HDR-картой
- Плейн с белым материалом, будем на нём замерять интенсивность освещения
В настройках вьюпорта отключаем GameSettings в разделе Exposure, отключаем автоэкспозицию в ProjectSettings->Rendering.
Устанавливаем число EV100 в соответствии с окружением на HDR карте.
Здесь как раз необходимо найти референсы в интернете или посмотреть табличные данные. Для ракурса и HDR’ки, которую я выбрал подойдёт значение EV100=12..13, оно соответствует съёмке в яркий солнечный день.
Увеличиваем интенсивность материала на SkySphere до момента, когда вам всё понравится.
Придётся подбирать значения на глаз. Так или иначе вы должны получить такую яркость, которую хотите видеть в сцене, чтобы она выглядела натурально. В моём случае получилось 18000. При этом, скорее всего, ваш белый Plane будет чёрного цвета — пока так и должно быть.
На этом этапе хочу сослаться на второе видео из шапки статьи. Движок работает в линейном пространстве цветов и только потом применяет гамма-коррекцию к изображению, которое вы видите. Этот "фильтр" называется Tonemapper. По умолчанию гамма = 2.2, что соответствует фотографичным требованиям (субъективная оценка реалистичности получаемого изображения). Тем не менее, автор видео предлагает изменять значение до 3, либо регулировать его вручную. Введите через командную строку r.TonemapperGamma 3.0, совет хороший. При значении r.TonemapperGamma=0 будет значение 2.2. Не путайте с регулировкой Gamma в разделе PostProcessVolume->ColorGrading. Это изменения, которые пойдут поверх Tonemapper’а, я бы их вообще не трогал в контексте физического освещения.
Копируем значение интенсивности из материала в SkyLight.
Запекаем сцену и проверяем через Pixel Inspector значения Luminance на нашем тестовом плейне.
Пока что мы работаем с непрямым освещением, т.е. должны получить значения, которые соответствуют освещённости в тени.
Мои замеры были такими:
На улице:
В тени — 8-12 тыс. лк
На солнце — около 42 тыс. лк
В помещении:
У окна в тени — 3 тыс. лк
Посреди комнаты — 200 лк
4м от окна — 70 лк
Значение Luminance получилось около 10000, что соответствует около 31400 лк.
Нам надо добиться того, чтобы получилось 8-12 тыс. люксов (замер в тени), т.е. раза в три меньше. Luminance должно получиться около 3-3.5 тысяч. Для отправной точки так же можно пользоваться табличными данными, взяв, к примеру, освещённость в пасмурный день.
Значение 6500 для SkyLight с нашей HDR’кой получилось оптимальным для нужных цифр. К слову, значения интенсивности SkyLight указываются в cd/m2, что некорректно, ведь это множитель, а не сила света. Так что на это можно не обращать внимание.
Теперь настраиваем Directional Light.
Можно поступить двумя способами:
- Аналогично постараться попасть в подсчитанные значения (42 тыс. люксов на солнце)
- Вычислить примерную пропорцию на HDR-карте между солнцем и тенью, получить значение в люксах , к которому нужно прийти.
Цель — выставить такую интенсивность для солнца (Directional Light), чтобы она соответствовала табличным данным, либо, что будет лучше, соответствовала изображению вокруг, которым засвечивается сцена. Поэтому первый вариант — вручную методом тыка подобрать такую интенсивность, чтобы получить нужные 42 тыс. люксов, опираясь на табличные данные или свои измерения. А второй вариант опирается на HDR-карту таким образом, чтобы можно было примерно посчитать интенсивность света в тени, на солнце с фотографии и сопоставить с текущими значениями в тени, уже в сцене движка, чтобы получить необходимые числа для интенсивности на солнце, чтобы эта интенсивность соответствовала не табличным данным, а конкретно сейчас используемому изображению.
В любом случае нам надо иметь некоторое значение освещённости. Либо сразу из таблицы его берём (вариант а), либо сперва высчитываем и получаем вручную (вариант б). Сейчас используем второй вариант, потому что он подольше, интереснее и гипотетически точнее.
Таким образом, получается, что на свету должно получиться в 2600/715 = 3,63 раза светлее, чем в тени.
В тени Luminance показывает 3458, значит на свету должно быть 3458*3,63=12552.
Подобрав подходящее значение для Directional Light (Stationary) запекаем сцену еще раз. У меня интенсивность получилась 37200 Lux. Сравниваем значение с табличным для самопроверки.
После запекания значения станут немного выше, т.к. в просчёте участвуют и отскоки тоже. Эти изменения несущественны.
В конце концов мы добились нужного результата и получили освещение, близкое к освещению на HDR-карте.
Отмечу, что если бы я использовал собственные значения 42 тыс. лк, яркость солнца пришлось бы увеличивать. Думаю, что вариант с пропорцией более точный, если отталкиваться от HDR-текстуры.
Копирую источники света, постпроцесс и скайсферу из этой сцены в сцену с помещением.
Значения Luminance внутри помещения получились такими:
По правде говоря, значения не слишком информативны, так как на них влияет цвет стен, объекты рядом и другие факторы. Единственное, что можно проследить — примерные соотношения с реальными значениями, если замерить телефоном в похожем помещении.
Тонкости и нюансы
Вообще, как бы я ни старался и ни хотел, не удастся воплотить освещение точь-в-точь как на HDR текстуре или в реальности. Слишком много примерных вычислений, к тому же в течении даже одной минуты реальная освещенность может меняться в широком диапазоне. Но, на мой взгляд, вариант с проверкой конечной освещённости поверхности близок к правде.
Подытоживая, хочу сравнить этот подход с вариантом из второго видео по ютуб-ссылке.
В видео для настройки непрямого освещения используется произвольный участок на небе/HDR’ке и табличные данные солнечного освещения. Обычно небо имеет большой диапазон яркости и солнце может светить по-разному, в том числе в зависимости от наклона лучей. Так что мой вариант, вероятно, точнее, хотя эта точность вряд ли кому-то нужна, ведь она требует больше времени и усилий.
Автор видео дает специальный тулкит для измерения освещённости, но на произвольном участке HDR’ки (какой участок выберете — так и будет освещаться, можно и не угадать). Именно из-за этого получается, что несмотря на крутость тулкита освещение будет зависеть от того, насколько повезёт выбрать подходящий участок для вычислений. Но отрицать, что работать с ним действительно быстрее и проще я не могу =).
В общем и целом рекомендую самостоятельно попробовать оба варианта настройки и посмотреть, чем они отличаются.
Надеюсь, было интересно копнуть в Unreal Engine 4, и какой-то материал вы сможете использовать себе на благо. Всем яркого и тёплого солнца!