Хабрахабр

[Перевод] Динамическая локальная экспозиция

Привет, Хабр! Представляю вашему вниманию перевод статьи «Dynamic Local Exposure» автора John Chapman.

image

В данной статье я представлю пару идей о динамической локальной экспозиции в HDR рендеринге. У Барта Вронски уже есть отличная статья на эту тему и я очень рекомендую ее прочитать прямо сейчас, если вы еще этого не сделали; идеи здесь, в большей степени, основаны на его статье. В конце я включил несколько других замечательных ссылок.

Low/High Dynamic Range

В старые добрые времена (1990-е) игры рендерились непосредственно в отображаемом LDR (узкий динамический диапазон) формате (гамма пространство, 8 бит). Это было просто и дешево, но, с другой стороны, значительно мешало созданию действительно фотореалистичной картинки.

С таким движением к фотореализму приходит реальная проблема: как мы можем отобразить HDR изображение в LDR?
В настоящее время, особенно с появлением PBR (physically-based rendering), игры рендерятся с гигантским динамическим диапазоном в линейном пространстве с более высокой точностью.

Глобальная автоэкспозиция

Стандартный подход к автоматическому контролю экспозиции заключается в измерении средней (или средней логарифмической) яркости сцены, опционально с 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 уровня (1x1).

Из-за своей глобальной природы, этот метод страдает от сильных затенений либо засветов областей изображения, в которых есть отклонение от средней яркости:

image

Хотя это соответствует способности глаза адаптироваться к изменениям уровня освещенности, общий эффект довольно далёк от того, что мы действительно воспринимаем в реальном мире.

Локальная автоэкспозиция

Если мы генерируем среднюю яркость при помощи downsampling, для получения локальной средней яркости у нас есть доступ к более низким mip уровням luminance buffer (4).

float Lavg = exp(textureLod(txLuminance, uv, uLuminanceLod).x;

Обратите внимание, для того, чтобы это сработало, гистерезис следует применять только на последнем шаге (при записи 1x1 mip уровня), в противном случае будут артефакты.

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

image

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

image

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

image

Все еще выглядит отвратительно, но уже лучше.

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

image

Результат вообще не естественный; выглядит как 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

image

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

image

Автоматический коэффициент смешивания

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

На изображении ниже у нас широкий динамический диапазон; в основном средне-низкие значения яркости и несколько областей с высокой интенсивностью (небо в окнах):

image

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

image

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

image

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

image

Генерацию максимальной яркости сцены можно сделать тривиально во время расчета яркости, применяя гистерезис для сглаживания результата тем же способом, что и для среднего значения. Данные наблюдения намекают на простой метод смешивания локальных и глобальных значений: если разница между средней и максимальной яркостью сцены больше, то и коэффициент смешивания локальной экспозиции должен быть больше. Поэтому мы можем расширить предыдущий фрагмент кода следующим образом:

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. Это означает, что измерение яркости нужно сделать непосредственно перед применением экспозиции. Если это неприемлемо, то решением будет генерация локальной яркости отдельно от среднего и максимального значений.

Подходит ли подобный метод к конкретной игре, полностью зависит от контента и желаемого визуального стиля. Хотя я думаю, что использование локальной и глобальной яркостей сцены вместе является “верным” подходом к созданию сбалансированного, естественно выглядящего изображения, качество результата, очевидно, субъективно. Мне было бы интересно услышать и другие идеи на этот счет.

Ссылки

  1. Localized Tonemapping (Bart Wronski)
  2. Implementing a Physically Based Camera (Padraic Hennessy)
  3. Moving Frostbite to PBR (Sébastien Lagarde, et al.)
  4. A Closer Look at Tonemapping (Matt Pettineo)
  5. The Importance of Being Linear (Larry Gritz, et al.)
  6. Advanced Techniques and Optimization of HDR/VDR Color Pipelines (Timothy Lottes)

HDR изображения взяты из sIBL Archive.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»