Главная » Хабрахабр » [Перевод] Learn OpenGL. Урок 5.7 — HDR

[Перевод] Learn OpenGL. Урок 5.7 — HDR

HDR

0 до 1. При записи во фреймбуфер значения яркости цветов приводятся к интервалу от 0. Из-за этой, на первый вгляд безобидной, особенности нам всегда приходится выбирать такие значения для освещения и цветов, чтобы они вписывались в это ограничение. 0. 0? Такой подход работает и даёт достойные результаты, но что случится, если мы встретим особенно яркую область с большим количеством ярких источников света, и суммарная яркость превысит 1. 0, будут приведены к 1. В результате все значения, большие чем 1. 0, что выглядит не очень красиво:

0, получаются большие области изображения, залитые одним и тем же белым цветом, теряется значительное количество деталей изображения, и само изображение начинает выглядеть неестественно. Так как для большого количества фрагментов цветовые значения приведены к 1.

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

0 до 1. Дисплей компьютера способен показывать цвета с яркостью в диапазоне от 0. Разрешая цветам фрагмента быть ярче единицы, мы получаем намного более высокий диапазон яркости для работы — HDR (high dynamic range). 0, но у нас нет такого ограничения при расчёте освещения. С использованием hdr яркие вещи выглядят яркими, тёмные вещи могут быть реально тёмными, и при этом мы будем видеть детали.

Содержание

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

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

Мы разрешаем при рендере использовать большой диапазон значений яркости, чтобы собрать информацию и о ярких, и о тёмных деталях сцены, и в конце мы преобразуем значения из диапазона HDR обратно в LDR (low dynamic range, диапазон от 0 до 1). HDR рендеринг работает примерно так же. Эти алгоритмы часто имеют параметр экспозиции, который позволяет лучше показывать яркие или тёмные области изображения. Это преобразование называется тональной компрессией (tone mapping), существует большое количество алгоритмов, нацеленных на сохранение большинства деталей изображения при конвертации в LDR.

Например, солнце имеет намного большую яркость света, чем что-нибудь типа фонарика, так почему бы не настроить солнце таким (например, присвоить ему яркость 10. Использование HDR при рендеринге позволят нам не только превышать LDR диапазон от 0 до 1 и сохранять больше деталей изображения, но также даёт возможность указывать реальную яркость источников света. Это позволит нам лучше настроить освещение сцены с более реалистичными параметрами яркости, что было бы невозможно при LDR рендеринге и диапазоне яркости от 0 до 1. 0)?

Просто отмасштабировать диапазон не будет хорошим решением, так как на изображении начнут преобладать яркие области. Так как дисплей показывают яркость только от 0 до 1, мы вынуждены конвертировать используемый HDR диапазон значений обратно к диапазону монитора. Этот преобразование называется тональной компрессией (tone mapping) и являетя финальным шагом HDR рендеринга. Однако мы можем использовать различные уравнения или кривые для преобразования значений HDR в LDR, что даст нам полный контроль над яркостью сцены.

Фреймбуферы с плавающей точкой

Если фреймбуфер использует нормализованный формат с фиксированной точкой (GL_RGB) для буферов цвета, то OpenGL автоматически ограничнивает значения перед сохранением во фреймбуфер. Для реализации HDR рендеринга нам нужен способ, чтобы предотвратить приведение значений к диапазону от 0 до 1 результатов работы фрагментного шейдера. Это ограничние применяется для большинства форматов фреймбуфера, кроме форматов с плавающей точкой.

0.. Чтобы хранить значения, выходящие из диапазана [0. 1], мы можем использовать буфер цвета со следующими форматами: GL_RGB16F, GL_RGBA16F, GL_RGB32F or GL_RGBA32F. 0. Такой буфер будем называть floating point фреймбуффером. Это прекрасно подходит для hdr рендеринга.

Создание floating point буфера отличается от обычного буфера только тем, что в нём используется другой внутренний формат:

glBindTexture(GL_TEXTURE_2D, colorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL);

В floating point фреймбуфере с форматами GL_RGB32F или GL_RGBA32F для хранения каждого цвета используется 32 бита — в 4 раза больше. Фреймбуфер OpenGL по умолчанию использует только 8 бит для хранения каждого цвета. Если не требуется очень высокая точность, то вполне достаточным будет формат GL_RGBA16F.

В коде к данной статье мы сначала рендерим сцену в floating point фреймбуфер и после этого выводим содержимое буфера цвета на полкоэкранный прямоугольник. Если к фреймбуферу присоединён floating point буфер для цвета, мы можем рендерить сцену в него с учётом того, что значения цвета не будут ограничены диапазоном от 0 до 1. Это выглядит примерно так:

glBindFramebuffer(GL_FRAMEBUFFER, hdrFBO); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // [...] рисуем сцену в hdr
glBindFramebuffer(GL_FRAMEBUFFER, 0); // рендерим hdr буфер цвета как 2д прямоугольник с другим шейдером
hdrShader.use();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, hdrColorBufferTexture);
RenderQuad();

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

std::vector<glm::vec3> lightColors;
lightColors.push_back(glm::vec3(200.0f, 200.0f, 200.0f));
lightColors.push_back(glm::vec3(0.1f, 0.0f, 0.0f));
lightColors.push_back(glm::vec3(0.0f, 0.0f, 0.2f));
lightColors.push_back(glm::vec3(0.0f, 0.1f, 0.0f));

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

#version 330 core
out vec4 FragColor; in vec2 TexCoords; uniform sampler2D hdrBuffer; void main()
{ vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb; FragColor = vec4(hdrColor, 1.0);
}

Однако, так как 2д прямоугольник рендерится в фреймбуфер по-умолчанию, выходные значения шейдера будут ограничены интервалом от 0 до 1, не смотря на то, что в некоторых местах значения больше 1. Мы берём входные данные из floating point буфера цвета и используем их в качестве выходных значений шейдера.

Так как мы используем HDR значения напрямую в качестве LDR, это эквивалентно отсутствию HDR. Становится очевидным, что слишком большие значения цвета в конце туннеля ограничены единицей, так как значительная часть изображения полностью белая, и мы теряем детали изображения, которые ярче единицы. Для этого применим тональную компрессию. Чтобы исправить это, мы должны отобразить различные значения цветов обратно в диапазон от 0 до 1 без потери каких-либо деталей изображения.

Тональная компрессия

Тональная компрессия — преобразование значений цвета, чтобы уместить их в диапазоне от 0 до 1 без потери деталей изображения, часто в сочетании с приданием изображению желаемого баланса белого.

Он отображает любые HDR значения в LDR диапазаон. Самый простой алгоритм тональной компрессии известен как алгоритм Рейнхарда (Reinhard tone mapping). Добавим этот алгоритм в предыдущий фрагментный шейдер, а так же применим гамма-коррекцию (и использование SRGB текстур).

void main()
{ const float gamma = 2.2; vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb; // тональная компрессия vec3 mapped = hdrColor / (hdrColor + vec3(1.0)); // гамма-коррекция mapped = pow(mapped, vec3(1.0 / gamma)); FragColor = vec4(mapped, 1.0);
}

пер. Прим. График функции: — при малых значениях х функция x/(1+x) ведёт себя примерно как х, при больших х — стремится к единице.

Алгоритм отдаёт предпочтение ярким областям, делая тёмные области менее отчётливыми. С тональной компрессией Рейнхарда мы больше не теряем деталей в ярких областях изображения.

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

Однако, на более сложных сценах вы часто будете встречаться с необходимостью хранить промежуточные HDR значения в floating point буферах, так что это вам пригодится. Стоит отметить, что мы можем использовать тональную компрессию напрямую в конце нашего шейдера для рассчёта освещения, и тогда нам вообще не понадобится floating point фреймбуфер.

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

Относительно простой алгоритм тональной компрессии с экспозицией выглядит так:

uniform float exposure; void main()
{ const float gamma = 2.2; vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb; // тональная компрессия с экспозицией vec3 mapped = vec3(1.0) - exp(-hdrColor * exposure); // гамма-коррекция mapped = pow(mapped, vec3(1.0 / gamma)); FragColor = vec4(mapped, 1.0);
}

пер: добавлю график и для этой функции c экспозицией 1 и 2: Прим.

Например, с большой экспозицией мы видим значительно больше подробностей на тёмных областях изображения. Здесь мы определили переменную для экспозиции, которая по умолчанию равна 1 и позволяет нам более точно выбрать баланс между качеством отображения тёмных и ярких областей изображения. Ниже приведены изображения туннеля с различными уровнями экспозиции. И наоборот, малая экспозиция делает неразличимыми тёмные области, но позволяет лучше увидеть яркие области изображения.

При изменении уровня экспозиции мы видим больше деталей сцены, которые были бы потеряны при обычном рендеринге. Эти изображения явно показывают преимущества hdr рендеринга. Аналогично, при высокой экспозиции очень хорошо видны детали в тёмных областях. Возьмите для примера конец туннеля — с нормальной экспозицией текстура дерева едва видна, но при низкой экспозиции текстуру превосходно видно.

Исходный код для демо здесь

Больше HDR

Некоторые алгоритмы лучше подчёркивают определённые цвета/яркости, некоторые алгоритмы показывают одновременно тёмные и яркие области, выдавая более красочные и детализированные изображения. Те два алгоритма тоновой компрессии, которые были показаны, являются лишь малой частью среди большого количества более продвинутых алгоритмов, каждый из которых имеет свои сильные и слабые стороны. В них определяется яркость сцены на предыдущем кадре и (медленно) изменяется параметр экспозиции, так что тёмная сцена потихоньку становится ярче, а яркая — темнее: схоже с привыканием человеческого глаза. Так же существует множество способов, известных как автоматичесий выбор экспозиции (automatic exposure adjustment) или адаптация глаз (eye adaptation).

В целях обучения в данной статье использовалась максимально простая сцена, так как создание большой сцены может быть сложным. Реальные преимущества HDR становятся лучше всего видны на больших и сложных сценах с серьёзными алгоритмами освещения. Несмотря на простоту сцены, на ней видны некоторые преимущества hdr рендеринга: в тёмных и светлых областях изображения не теряются детали, так как они сохраняются при помощи тоновой компрессии, добавление множественных источников света не приводит к появлению белых областей, и значения не обязаны умещаться в LDR диапазон.

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

Дополнительные ресурсы:

S. У нас есть телеграм-конфа для координации переводов. P. Если есть серьезное желание помогать с переводом, то милости просим!


Оставить комментарий

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

*

x

Ещё Hi-Tech Интересное!

[Перевод] Введение в ptrace или инъекция кода в sshd ради веселья

Конечно, это несколько искусственная задача, так как есть множество других, более эффективных, способов достичь желаемого (и с гораздо меньшей вероятностью получить SEGV), однако, мне показалось клёвым сделать именно так. Цель, которой я задался, была весьма проста: узнать введённый в sshd ...

Дайджест свежих материалов из мира фронтенда за последнюю неделю №339 (12 — 18 ноября 2018)

Предлагаем вашему вниманию подборку с ссылками на новые материалы из области фронтенда и около него.     Медиа    |    Веб-разработка    |    CSS    |    Javascript    |    Браузеры    |    Занимательное Медиа • Подкаст «Frontend Weekend» #79 – Олег Поляков об основании CodeDojo и о том, как это стало основным местом работы• Подкаст «Пятиминутка React» ...