- PVSM.RU - https://www.pvsm.ru -
Привет! Представляю вашему вниманию перевод статьи «Dynamic Local Exposure» [1] автора John Chapman.

В данной статье я представлю пару идей о динамической локальной экспозиции в HDR рендеринге. У Барта Вронски уже есть отличная статья [2] на эту тему и я очень рекомендую ее прочитать прямо сейчас, если вы еще этого не сделали; идеи здесь, в большей степени, основаны на его статье. В конце я включил несколько других замечательных ссылок.
В старые добрые времена (1990-е) игры рендерились непосредственно в отображаемом LDR (узкий динамический диапазон) формате (гамма пространство, 8 бит). Это было просто и дешево, но, с другой стороны, значительно мешало созданию действительно фотореалистичной картинки.
В настоящее время, особенно с появлением PBR (physically-based rendering), игры рендерятся с гигантским динамическим диапазоном в линейном пространстве с более высокой точностью. С таким движением к фотореализму приходит реальная проблема: как мы можем отобразить HDR изображение в LDR?
Стандартный подход к автоматическому контролю экспозиции заключается в измерении средней (или средней логарифмической) яркости сцены, опционально с weight функцией, отдающей предпочтение значениям, близким к центру изображения. Это можно сделать очень эффективно с помощью параллельного уменьшения или путем многократного downsampling в mipmap у luminance buffer (буфер яркости). Последний подход имеет некоторые преимущества, о которых я расскажу в следующем разделе.
Средняя яркость впоследствии преобразуется в значение экспозиции, например, путем вычисления обратной величины максимально допустимой яркости сцены:
float Lavg = exp(textureLod(txLuminance, uv, 99.0).x);
float ev100 = log2(Lavg * 100.0 / 12.5);
ev100 -= uExposureCompensation; // optional manual bias
float exposure = 1.0 / (1.2 * exp2(ev100));
Получено из стандарта ISO расчета скорости на основе насыщения, полное объяснение см. в (3)
Так как потенциально средняя яркость нестабильна в динамических условиях, ее обычно сглаживают во времени с помощью экспоненциальной гистерезисной функции (2):
Lavg = Lavg + (Lnew - Lavg) * (1.0 - exp(uDeltaTime * -uRate));
Из-за своей глобальной природы, этот метод страдает от сильных затенений либо засветов областей изображения, в которых есть отклонение от средней яркости:

Хотя это соответствует способности глаза адаптироваться к изменениям уровня освещенности, общий эффект довольно далёк от того, что мы действительно воспринимаем в реальном мире.
Если мы генерируем среднюю яркость при помощи downsampling, для получения локальной средней яркости у нас есть доступ к более низким mip уровням luminance buffer (4).
float Lavg = exp(textureLod(txLuminance, uv, uLuminanceLod).x;
Обратите внимание, для того, чтобы это сработало, гистерезис следует применять только на последнем шаге (при записи 1x1 mip уровня), в противном случае будут артефакты.
В теории это отличная идея: каждая область изображения может иметь хорошую экспозицию, при этом быть в контрасте с соседними областями. Однако, на практике получается отвратительный результат:

Наиболее неприятными являются блочные “ореолы”, которые встречаются в областях с высокой контрастностью:

Однако их можно сгладить либо предварительной фильтрацией luminance buffer, либо просто с помощью бикубического сэмплинга:

Все еще выглядит отвратительно, но уже лучше.
Сэмплинг различных уровней mipmap у luminance меняет радиус ореола. Этот параметр полезен для контроля общего “внешнего вида” результата, а также для минимизации эффекта ореола, хотя и за счет общего уменьшения контраста (он становится фильтром границ) или потери локальности контроля экспозиции:

Все же сглаживания ореолов недостаточно. Результат вообще не естественный; выглядит как extreme “HDR photo” style, в отличие от того, что видит человек. Однако смешивая глобальное и локальное значения, мы можем получить лучшее из обоих миров:
float Llocal = exp(textureLod(txLuminance, uv, uLuminanceLod).x;
float Lglobal = exp(textureLod(txLuminance, uv, 99.0).x;
float L = mix(Lglobal, Llocal, uLocalExposureRatio);
// .. use L to compute the final exposure scale as before

Изменяя коэффициент смешивания, можно настроить локальную экспозицию так, чтобы в результате минимизировать артефакты и максимизировать воспринимаемый реализм:

Настройка коэффициента смешивания вручную нормальна в ситуациях, где у нас есть абсолютный контроль позиции камеры, освещения и т.д. Однако во многих случаях (например, игры под открытым небом с динамической сменой дня и ночи) этот уровень контроля попросту невозможен. В данном случае было бы неплохо генерировать коэффициент смешивания автоматически.
На изображении ниже у нас широкий динамический диапазон; в основном средне-низкие значения яркости и несколько областей с высокой интенсивностью (небо в окнах):

Без локальной экспозиции цвет неба теряется. В этом случае хотелось бы большой коэффициент смешивания:

Теперь рассмотрим изображение ниже, которое имеет небольшой динамический диапазон в основном с высоким значением яркости:

В этом случае применение локальной экспозиции слишком сильно уменьшает яркость “ярких” областей:

Данные наблюдения намекают на простой метод смешивания локальных и глобальных значений: если разница между средней и максимальной яркостью сцены больше, то и коэффициент смешивания локальной экспозиции должен быть больше. Генерацию максимальной яркости сцены можно сделать тривиально во время расчета яркости, применяя гистерезис для сглаживания результата тем же способом, что и для среднего значения. Поэтому мы можем расширить предыдущий фрагмент кода следующим образом:
float Llocal = exp(textureLod(txLuminance, uv, uLuminanceLod).x;
float Lglobal = exp(textureLod(txLuminance, uv, 99.0).x; // average in x
float Lmax = exp(textureLod(txLuminance, uv, 99.0).y; // max in y
float Lratio = min(saturate(abs(Lmax - Lglobal) / Lmax), uLocalExposureMax);
float L = mix(Lglobal, Llocal, Lratio);
// .. use L to compute the final exposure scale as before
Обратите внимание, что у нас на вход появился uLocalExposureMax для контроля абсолютной максимальной степени влияния локальной экспозиции. У меня хороший результат дал uLocalExposureMax < 0.3.
float Llocal = exp(textureLod(txLuminance, uv, uLuminanceLod).x;
float Lglobal = exp(textureLod(txLuminance, uv, 99.0).x; // average in x
float Lmax = exp(textureLod(txLuminance, uv, 99.0).y; // max in y
float Lratio = min(saturate(abs(Lmax - Lglobal) / Lmax), uLocalExposureMax);
float L = mix(Lglobal, Llocal, Lratio);
float ev100 = log2(L * 100.0 / 12.5);
ev100 -= uExposureCompensation; // optional manual bias
float exposure = 1.0 / (1.2 * exp2(ev100));
vec3 result = hdrColor * exposure;
result += bloom;
//etc
outColor.rgb = result;
Подход, изложенный выше, накладывает некоторые ограничения на то, когда нужно измерять яркость сцены. Обычно измерение выполняется сразу после прохода освещения, чтобы избежать адаптации particle эффектов, bloom и т.д. Однако, когда используется локальная яркость важно, чтобы настоящее значение, которое участвует в экспозиции, было представлено в luminance map. Это означает, что измерение яркости нужно сделать непосредственно перед применением экспозиции. Если это неприемлемо, то решением будет генерация локальной яркости отдельно от среднего и максимального значений.
Хотя я думаю, что использование локальной и глобальной яркостей сцены вместе является “верным” подходом к созданию сбалансированного, естественно выглядящего изображения, качество результата, очевидно, субъективно. Подходит ли подобный метод к конкретной игре, полностью зависит от контента и желаемого визуального стиля. Мне было бы интересно услышать и другие идеи на этот счет.
HDR изображения взяты из sIBL Archive [8].
Автор: migom
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/308622
Ссылки в тексте:
[1] «Dynamic Local Exposure»: https://john-chapman.github.io/2017/08/23/dynamic-local-exposure.html
[2] отличная статья: https://bartwronski.com/2016/08/29/localized-tonemapping/
[3] Implementing a Physically Based Camera: https://placeholderart.wordpress.com/2014/12/15/implementing-a-physically-based-camera-automatic-exposure/
[4] Moving Frostbite to PBR: https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
[5] A Closer Look at Tonemapping: https://mynameismjp.wordpress.com/2010/04/30/a-closer-look-at-tone-mapping/
[6] The Importance of Being Linear: https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch24.html
[7] Advanced Techniques and Optimization of HDR/VDR Color Pipelines: http://32ipi028l5q82yhj72224m8j.wpengine.netdna-cdn.com/wp-content/uploads/2016/03/GdcVdrLottes.pdf
[8] sIBL Archive: http://www.hdrlabs.com/sibl/archive.html
[9] Источник: https://habr.com/ru/post/440018/?utm_campaign=440018
Нажмите здесь для печати.