Фоновое затенение что такое
Как настроить графику в играх, чтобы компьютер не тормозил
И сохранить приличный вид локаций
Современные видеоигры становятся красивее, реалистичнее и… тяжелее.
В этой статье мы расскажем, какими опциями можно пожертвовать и не заметить разницы, а какие лучше трогать по минимуму, чтобы не испортить впечатление от игры некачественным визуалом.
Что вы узнаете из этого материала
Почему игра может тормозить
Производительность в видеоиграх принято мерить значениями FPS — frames per second. Это кадровая частота, то есть количество кадров в секунду. Чем выше FPS, тем плавнее картинка.
Во время игры значения FPS меняются: если нагрузка на компьютер увеличивается в более сложной игровой сцене, количество кадров в секунду падает. Человеческий глаз улавливает малейшие изменения в кадровой частоте: резкое снижение уровня мы воспринимаем как те самые «тормоза».
Объясню на примере. Предположим, вы исследуете игровой уровень, в котором ничего не происходит, — FPS стабильно высокая. Затем начинается динамичная сцена с кучей спецэффектов, и кадровая частота падает, потому что нагрузка на ПК резко повышается. Действие на экране становится более рваным и дерганным, это раздражает.
Чтобы избежать торможения, в играх принято ограничивать «потолок» FPS, даже если компьютер может время от времени выдавать более высокие значения. Глаз привыкает к определенной частоте кадров, поэтому низкая, но стабильная FPS приятнее, чем «плавающая».
Добиться стабильной FPS можно двумя способами: либо занизить настройки графики так, чтобы компьютер с легкостью выдавал 60 FPS, либо сделать их умеренно средними или даже высокими — но выставить ограничение в 30 FPS. В первом случае изображение будет плавным, но не слишком подробным, а во втором — более детализированным и четким, но одновременно и более дерганным.
негласный стандарт для количества кадров в секунду. Если игра ну очень требовательная, достаточно стабильных 30 FPS
Чем мощнее машина, тем больше FPS она успевает обрабатывать. Но требования игр растут с каждым годом: компьютеры, которые были самыми мощными несколько лет назад, уже не так хорошо работают с современным геймплеем. К примеру, на топовом для 2014 года ПК (i7 4770k, GTX 980, 16 ГБ ОЗУ) недавняя Cyberpunk 2077 может тормозить: чтобы этого избежать, игроку придется повозиться с настройками графики, которые позволят увеличить производительность.
30 FPS достаточно, если вы играете в сюжетную или кинематографичную игру: качество изображения будет выше, а игровой процесс не пострадает. Но некоторые игры рассчитаны минимум на 60 FPS — например, многопользовательские шутеры Counter-Strike или Call of Duty, где важна скорость реакции.
Выше 60 кадров в секунду поднимать графику нет смысла: большинство мониторов просто не смогут воспроизвести FPS выше. Дело в том, что у каждого дисплея есть еще и частота обновления экрана, которая зачастую равна 60 Гц: это означает, что картинка на дисплее обновляется 60 раз в секунду. Соответственно, если кадровая частота окажется выше частоты обновления экрана, на мониторе попросту не будет видно разницы.
Одни настройки графики ресурсоемкие, а другие — не очень. Более того, занижение некоторых параметров может практически не сказаться на качестве картинки, но убрать пресловутые «тормоза».
Например, опции для света, теней и отражений. Они сильно влияют на производительность, но их изменения не всегда заметны на экране — некоторые сцены всего лишь потеряют красивые блики и полутона.
Ниже я перечислил самые распространенные настройки графики в порядке их влияния на качество графики. Параметры с припиской «Без потерь» обычно высвобождают немало FPS, но картинку портят не сильно. А там, где указано «С заметными потерями» или «Выжать еще чуть-чуть», придется пойти на компромиссы.
Важно помнить, что советы могут подойти не для всех игр: в разных проектах графика работает по-разному. Эффект от смены параметров может оказаться как слабее, так и значительно сильнее. Чтобы быть точно уверенным в результате и не тратить много времени на настройку, можно воспользоваться специальным гайдом для конкретной игры — например, для Cyberpunk 2077.
Но готовые решения есть не для всех игр. Эта статья поможет настроить игру самостоятельно, учитывая возможности конкретного компьютера. Вот чем можно пожертвовать в угоду производительности, не потеряв в качестве, — или намеренно ухудшив изображение.
Урок №39. SSAO в OpenGL
Обновл. 14 Дек 2020 |
На этом уроке мы рассмотрим, что такое SSAO в OpenGL и как его использовать.
Фоновое освещение — это фиксированная световая константа, добавляемая к общему освещению сцены для имитации рассеивания света. В реальном мире свет рассеивается во всех направлениях и с различной интенсивностью, поэтому опосредованно освещенные части сцены также должны иметь различную интенсивность освещения. Одним из видов аппроксимации непрямого освещения является фоновое затенение (сокр. «AO» от англ. «Ambient Occlusion»), которое пытается имитировать непрямое освещение путем затемнения складок, отверстий и поверхностей, расположенных близко друг к другу. Эти области в значительной степени перекрыты окружающей геометрией, из-за чего у лучей света становится меньше шансов выбраться наружу, а поэтому подобные области кажутся темнее. Взгляните на углы и складки вашей комнаты — вы увидите, что свет в них кажется немного темнее.
Ниже приведен пример изображения сцены с использованием метода фонового затенения и без него. Обратите внимание, что между складками и в углах (фоновое) освещение выглядит более темным:
Хотя рассматриваемый эффект и не бросается в глаза так явно, как остальные, но изображение с фоновым затенением действительно выглядит намного более реалистичным из-за присутствия небольших, но при этом не менее существенных деталей освещения, придающих всей сцене гораздо большее ощущение глубины.
Алгоритмы расчета AO являются довольно ресурсоемкими, поскольку они должны учитывать окружающую геометрию. На первый взгляд кажется, что для каждой точки в пространстве можно было бы испускать большое количество лучей, чтобы определить величину её фонового затенения, но этот процесс очень быстро упрется в ограничения производительности для решений, работающих в реальном времени. В 2007 году компания Crytek в своей игре под названием Crysis использовала методику, известную как SSAO (от англ. «Screen-Space Ambient Occlusion»). Вместо реальных геометрических данных для определения величины фонового затенения, вышеупомянутый метод задействует буфер глубины сцены в координатах экранного пространства. По сравнению с обычным AO рассматриваемый подход является невероятно быстрым в вычислительном плане и дает правдоподобные результаты, что делает его де-факто стандартом для моделирования фонового затенения в реальном времени.
Основы метода SSAO довольно просты: для каждого фрагмента на «экранном прямоугольнике» мы вычисляем коэффициент затенения, основанный на окружающих фрагмент значениях глубины. Затем данный коэффициент используется для уменьшения или обнуления компонента затенения фрагмента. Коэффициент затенения получают путем взятия нескольких выборок значений глубины из некоторой сферической окрестности (ядра выборки) заданного фрагмента и сравнения значения в каждой точки выборки с текущим значением глубины фрагмента. Количество точек выборок, имеющих более высокое значение глубины, чем глубина фрагмента, представляет собой коэффициент затенения.
Как вы можете видеть из рисунка, каждая точка выборки глубины (серого цвета), находящаяся внутри некоторого геометрического объекта, вносит свой вклад в итоговое значение коэффициента затенения; чем больше точек выборок окажутся внутри объекта(ов), тем меньше фонового освещения должен в конечном итоге получить фрагмент.
Становится понятно, что качество и точность эффекта напрямую связаны с количеством точек выборок, которые мы используем. Если их слишком мало, то точность метода резко снижается, и мы получаем артефакт в виде некоторой полосатости/сегментации изображения, называемый бандингом (от англ. «banding»); если же точек выборок слишком много, то падает производительность алгоритма. Мы можем уменьшить требование к используемому количеству выборок, введя некоторую случайность в ядро выборки. Произвольно вращая ядро выборки каждого фрагмента, мы можем получить высококачественные результаты с гораздо меньшим количеством непосредственно самих выборок. Однако данная случайная составляющая вносит заметную картину шума, которую нам придется исправить, применив к полученным результатам размытие. Ниже приведено изображение (любезно предоставленное John Chapman), демонстрирующее эффект бандинга и влияние случайной составляющей на конечный результат:
Как вы можете видеть на картинке слева, при использовании SSAO с недостаточным количеством точек выборки мы получаем заметный эффект бандинга. Вводя некоторую случайную составляющую поворота и применяя размытие к конечному результату, мы полностью убираем эффект бандинга.
Метод SSAO, разработанный компанией Crytek, имел определенный узнаваемый визуальный стиль. Поскольку формой использованного ядра выборки была сфера, то плоские стенки выглядели серыми, поскольку половина точек выборки из ядра в конечном итоге оказывалась внутри окружающей геометрии. Ниже представлено SSAO-изображение Crysis в режиме «градации серого», на котором наглядно изображен описываемый эффект:
По этой причине мы будем использовать не сферическое ядро, а полусферическое ядро, ориентированное вдоль вектора нормали поверхности.
Производя выборки внутри изображенной выше нормально-ориентированной полусферы, мы, при вычислении коэффициента затенения, избавляем себя от расчетов составляющей затенения, вносимого фрагментами, лежащими ниже прилегающей поверхности. Благодаря этому получаются, как правило, более реалистичные результаты.
Буферы выборки
Поскольку при использовании SSAO необходимо вычислять коэффициент затенения фрагмента, то нам потребуются следующие данные:
вектор позиции каждого фрагмента;
вектор нормали каждого фрагмента;
цвет альбедо каждого фрагмента;
ядро выборки;
вектор произвольного поворота для каждого фрагмента, используемый для вращения ядра.
Используя позицию каждого фрагмента (в координатах видового пространства), мы можем ориентировать полусферическое ядро выборки вокруг (в координатах видового пространства) нормали поверхности фрагмента и применить это ядро для выборки значений из текстуры буфера позиций при различных смещениях. Для каждой фрагментной точки выборки из ядра выборки мы сравниваем её значение глубины со значением глубины в буфере позиций, чтобы определить величину затенения. Затем полученный коэффициент затенения используется для ограничения итогового значения компонента AO. Кроме того, задействовав для каждого фрагмента вектор поворота, у нас появится возможность значительно сократить необходимое количество выборок:
Поскольку SSAO — это метод экранного пространства, то мы рассчитываем его влияние на каждый фрагмент экранного прямоугольника. Это означает, что у нас нет никакой геометрической информации о сцене. То, что мы могли бы сделать, — это визуализировать геометрические данные каждого фрагмента в текстуры экранного пространства, которые затем отправим в шейдер SSAO, чтобы иметь доступ к геометрическим данным каждого фрагмента. Это очень похоже на настройку G-буфера отложенного рендеринга. По этой причине использование SSAO идеально сочетается с методом отложенного рендеринга, поскольку в G-буфере у нас уже есть векторы позиций и нормали.
Примечание: На этом уроке мы собираемся реализовать SSAO поверх немного упрощенной версии отложенного рендеринга из урока об отложенном затенении в OpenGL.
Информация о позиции каждого фрагмента и соответствующем векторе нормали доступна из объектов сцены, а поэтому фрагментный шейдер геометрического прохода следующий:
Learn OpenGL. Урок 5.10 – Screen Space Ambient Occlusion
Тема фонового освещения была затронута нами в уроке по основам освещения, но лишь вскользь. Напомню: фоновая составляющая освещения – суть постоянная величина, добавляемая во все расчеты освещения сцены для имитации процесса рассеяния света. В реальном же мире свет испытывает множество переотражений с разной степенью интенсивности, что приводит к столь же неравномерной засветке косвенно освещенных участков сцены. Очевидно, что засветка с постоянной интенсивностью не очень правдоподобна.
Одним из видов приближенного расчета затенения от непрямого освещения является алгоритм фонового затенения (ambient occlusion, AO), который имитирует ослабление непрямого освещения в окрестности углов, складок и прочих неровностях поверхностей. Такие элементы, в основном, значительно перекрываются соседствующей геометрией и потому оставляют меньше возможностей лучам света вырваться наружу, затемняя данные участки.
Ниже представлено сравнение рендера без и с использованием алгоритма AO. Обратите внимание на то, как падает интенсивность фонового освещения в окрестности углов стен и прочих резких изломов поверхности:
Стоит отметить, что алгоритмы расчета AO являются довольно ресурсоемкими, поскольку требуют анализа окружающей геометрии. В наивной реализации можно было бы просто в каждой точке поверхности выпустить множество лучей и определить степень её затенения, но такой подход весьма быстро достигает допустимого для интерактивных приложений предела ресурсоемкости. К счастью, в 2007 году компания Crytek опубликовала работу с описанием собственного подхода в реализации алгоритма фонового затенения в экранном пространстве (Screen-Space Ambient Occlusion, SSAO), который использовался в релизной версии игры Crysis. Подход рассчитывал степень затенения в экранном пространстве, используя лишь текущий буфер глубины вместо реальных данных об окружающей геометрии. Такая оптимизация радикально ускорила алгоритм по сравнению с эталонной реализацией и при этом давала по большей части правдоподобные результаты, что сделало данный подход приближенного расчета фонового затенения стандартом де-факто в индустрии.
Принцип, на котором основан алгоритм довольно прост: для каждого фрагмента полноэкранного квада рассчитывается коэффициент затенения (occlusion factor) на основе значений глубины окружающих фрагментов. Вычисленный коэффициент затенение далее используется для уменьшения интенсивности фонового освещения (вплоть до полного исключения). Получение коэффициента требует сбора данных о глубине от множества выборок из сферической области, окружающей рассматриваемый фрагмент, и сравнения этих значений глубины с глубиной рассматриваемого фрагмента. Число выборок, имеющих глубину бОльшую, нежели текущий фрагмент непосредственно и определяют коэффициент затенения. Посмотрите на данную схему:
Здесь, каждая серая точка лежит внутри некоторого геометрического объекта, а потому осуществляет вклад в значение коэффициента затенения. Чем больше выборок окажутся внутри геометрии окружающих объектов, тем меньше будет остаточная интенсивность фонового затенения в этой области.
Очевидно, что качество и реалистичность эффекта прямо зависит от числа сделанных выборок. При малом числе выборок точность алгоритма падает и приводит к появлению артефакта бэндинга (banding) или «полошения» из-за резких переходов между областями с сильно отличающимися коэффициентами затенения. Большое же число выборок просто убивает производительность. Рандомизации ядра выборок позволяет при схожих по качеству результатах несколько снизить число требуемых выборок. Подразумевается переориентация поворотом на случайный угол набора векторов выборок. Однако внесение случайности тут же приносит новую проблему в виде заметного шумового узора, что требует использования фильтров размытия для сглаживания результата. Ниже приведен пример работы алгоритма (автор – John Chapman) и его типичные проблемы: бэндинг и шумовой узор.
Как видно, заметное полошение из-за малого числа выборок неплохо убирается внесением рандомизации ориентации выборок.
Конкретная реализация SSAO от Crytek обладала узнаваемым визуальным стилем. Поскольку специалисты Crytek использовали сферическое ядро выборки это сказывалось даже на плоских поверхностях типа стен, делая их затененными – ведь половина объема ядра выборки оказывалась погруженной под геометрию. Ниже – скриншот со сценой из Crysis, изображенной в градациях серого на основе значения коэффициента затенения. Здесь хорошо виден эффект «серости»:
Для избегания такого влияния мы перейдем от сферического ядра выборки к полусфере, ориентированной вдоль нормали к поверхности:
Осуществляя выборку из такой полусферы ориентированной по нормали (normal-oriented hemisphere) нам не придется учитывать в расчете коэффициента затенения фрагменты, лежащие под поверхностью прилегающей поверхности. Такой подход убирает излишнее затенение в, в целом, дает более реалистичные результаты. Данном урок будет использовать подход с полусферой и немного доработанный код из блестящего урока по SSAO от John Chapman.
Буфер с исходными данными
Процесс вычисления коэффициента затенения в каждом фрагменте требует наличия данных об окружающей геометрии. Конкретно, нам потребуются следующие данные:
Поскольку SSAO является эффектом, реализующимся в экранном пространстве, то непосредственный расчет возможно выполнить отрендерив полноэкранный квад. Но тогда у нас не будет данных о геометрии сцены. Чтобы обойти такое ограничение, мы осуществим рендер всей необходимой информации в текстуры, которые позже будут использованы в шейдере SSAO для доступа к геометрической и прочей информации о сцене. Если вы внимательно следовали данным урокам, то уже должны узнать в описанном подходе облик алгоритма отложенного затенения. Во многом поэтому эффект SSAO как родной встает в рендер с отложенным затенением – ведь текстуры, хранящие координаты и нормали, уже доступны в G-буфере.
В данном уроке эффект реализуется поверх несколько упрощенной версии кода из урока об отложенном освещении. Если вы еще не ознакомились с принципами отложенного освещения – настоятельно советую обратиться к этому уроку.
Поскольку доступ к пофрагментной информации о координатах и нормалях уже должен быть доступен за счет G-буфера, то фрагментный шейдер стадии обработки геометрии достаточно прост:
Поскольку алгоритм SSAO является эффектом в экранном пространстве, а коэффициент затенения вычисляется на основе видимой области сцены, то есть смысл вести расчеты в видовом пространстве. В данном случае переменная FragPos, полученная из вершинного шейдера, хранит положение именно в видовом пространстве. Стоит удостовериться, что данные о координатах и нормалях хранятся в G-буфере в видовом пространстве, поскольку все дальнейшие расчеты будут осуществляться в нем же.
Существует возможность восстановления вектора положения на основе лишь известной глубины фрагмента и некоторого количества математической магии, что описано, например, у Matt Pettineo в блоге. Это, конечно, требующий бОльших затрат на расчеты способ, однако он избавляет от необходимости хранить данные о положении в G-буфере, что занимает уйму видеопамяти. Однако, ради простоты кода примера, мы оставим этот подход для личного изучения.
Текстура буфер цвета gPosition сконфигурирована следующим образом:
Данная текстура хранит координаты фрагментов и может быть использована для получения данных о глубине для каждой точки из ядра выборок. Отмечу, что текстура использует формат данных с плавающей точкой – это позволит координатам фрагментов не быть приведенными к интервалу [0., 1.]. Также обратите внимание на режим повтора – установлен GL_CLAMP_TO_EDGE. Это необходимо для устранения возможности не нарочно осуществить оверсэмплинг в экранном пространстве. Выход за пределы основного интервала текстурных координат даст нам некорректные данные о положении и глубине.
Далее займемся формированием полусферического ядра выборок и созданием метода случайной его ориентации.
Создание ориентированной по нормали полусферы
Итак, стоит задача создать набор точек выборки, расположенных внутри полусферы, сориентированной вдоль нормали к поверхности. Поскольку создание ядра выборки для всех возможных направлений нормали вычислительно недостижимо, то мы используем переход в касательное пространство, где нормаль всегда представляется как вектор в направлении положительной полуоси Z.
Предполагая радиус полусферы единичным процесс формирования ядра выборки из 64 точек выглядит так:
Здесь мы случайным образом выбираем координаты x и y в интервале [-1., 1.], а координату z – в интервале [0., 1.] (будь интервал таким же, как для x и y, мы бы получили сферическое ядро выборки). Результирующие вектора выборок окажутся ограничены полусферы, поскольку ядро выборки в конечном итоге будет сориентировано вдоль нормали к поверхности.
В данный момент все точки выборки случайно распределены внутри ядра, но в угоду качеству эффекта выборкам, лежащим ближе к началу координат ядра, стоило бы вносить больший вклад в расчете коэффициента затенения. Это можно реализовать за счет изменения распределения сформированных точек выборки, увеличив их плотность около начала координат. Такую задачу легко выполнить с помощью функции интерполяции с ускорением:
Функция lerp() определена как:
Такой трюк дает нам модифицированное распределение, где большинство точек выборки лежат вблизи начала координат ядра.
Каждый из полученных векторов выборки будет использован для смещения координаты фрагмента в видовом пространстве для получения данных об окружающей геометрии. Для получения приличных результатов при работе в видовом пространстве может потребоваться внушительное количество отсчетов, что неизбежно ударит по производительности. Однако, внесение псевдослучайного шума или поворота векторов выборок в каждом обрабатываемом фрагменте, позволит значительно снизить требуемое число выборок при сравнимом качестве.
Случайный поворот ядра выборки
Итак, внесение случайности в распределение точек ядра выборки позволяет значительно снизить требование к числу этих точек для получения достойного качества эффекта. Можно было бы создать случайный вектор поворота для каждого фрагмента сцены, но это слишком затратно по памяти. Эффективней создать небольшую текстуру, содержащую набор случайных векторов поворота, а затем просто использовать её с установленным режимом повтора GL_REPEAT.
Создадим массив 4х4 и заполним случайными векторами поворота, сориентированными вдоль вектора нормали в касательном пространстве:
Поскольку ядро выровнено вдоль положительной полуоси Z в касательном пространстве, то компонент z оставляем равным нулю – это обеспечит поворот только вокруг оси Z.
Далее создадим текстуру размером также 4х4 и зальем туда наш массив векторов поворота. Обязательно используйте режим повтора GL_REPEAT для тайлинга текстуры:
Что ж, теперь у нас готовы все данные, необходимые для непосредственной реализации алгоритма SSAO!
Шейдер SSAO
Шейдер эффекта будет исполняться для каждого фрагмента полноэкранного квада, вычисляя коэффициент затенения в каждом из них. Поскольку результаты будут использованы в еще одной стадии рендера, создающей итоговое освещение, нам потребуется создание еще одного объекта фреймбуфера для хранения результата работы шейдера:
Поскольку результат работы алгоритма – единственное вещественное число в пределах [0., 1.], то для хранения будет достаточно создать текстуру с единственной доступной компонентой. Именно поэтому в качестве внутреннего формата для буфера цвета ставится GL_RED.
В целом процесс рендера стадии SSAO выглядит примерно следующим образом:
Шейдер shaderSSAO принимает нужные ему текстуры G-буфера как входные данные, а также шумовую текстуру и ядро выборки:
Обратите внимание на переменную noiseScale. Наша маленькая текстура с шумом должна быть затайлена по всей поверхности экрана, но поскольку текстурные координаты TexCoords заключены в пределах [0., 1.] этого не произойдет без нашего вмешательства. В этих целях мы вычисляем множитель для текстурных координат, который находится как отношение размера экрана к размеру шумовой текстуры:
Поскольку при создании шумовой текстуры texNoise мы установили режим повтора в GL_REPEAT, то теперь она будет повторяться множество раз на поверхности экрана. Имея на руках величины randomVec, fragPos и normal мы можем создать матрицу TBN трансформации из касательного пространства в видовое:
Используя процесс Грамма-Шмидта мы создаем ортогональный базис, случайно наклоненным в каждом фрагменте на основе случайного значения randomVec. Важный момент: поскольку в данном случае нам неважно, чтобы матрица TBN была точно сориентирована вдоль поверхности треугольника (как в случае с parallax mapping’ом, прим. пер.), то нам не нужны предрасчитанные данные о касательных и бикасательных.
Далее мы проходим по массиву ядра выборки, переводим каждый вектор выборки из касательного пространства в видовое и получаем его сумму с текущим положением фрагмента. Затем сравниваем величину глубины получившейся суммы со значением глубины, полученной выборкой из соответствующей текстуры G-буфера.
Пока звучит запутанно, разберем это по шагам:
Здесь kernelSize и radius являются переменными, контролирующими характеристики эффекта. В данном случае они равны 64 и 0.5 соответственно. На каждой итерации мы переводим вектор ядра выборки в видовое пространство. Далее прибавляем к полученному значению смещения выборки в видовом пространстве значение положения фрагмента в видовом пространстве. При этом значение смещения умножается на переменную radius, которая управляет радиусом ядра выборки эффекта SSAO.
После этих шагов нам следует преобразовать полученный вектор sample в экранное пространство, для того, чтобы мы могли осуществить выборку из текстуры G-буфера, хранящей положения и глубины фрагментов, используя полученное спроецированное значение. Поскольку sample находится в видовом пространстве, нам потребуется матрица проекции projection:
После преобразования в клиповое пространство мы вручную осуществляем перспективное деление простым делением компонент xyz на w компоненту. Полученный вектор в нормализованных координатах устройства (NDC) переводится в интервал значений [0., 1.] дабы его можно было использовать как текстурные координаты:
Используем компоненты xy вектора sample для выборки из текстуры положений G-буфера. Получим значение глубины (z компоненты), соответствующее вектору выборки при взгляде с позиции наблюдателя (это первый не заслоненный видимый фрагмент). Если при этом полученная глубина выборки оказывается больше, чем сохраненная глубина, то мы увеличиваем коэффициент затенения:
Обратите внимание на смещение bias, которое добавляется к исходной глубине фрагмента (в примере установлена в 0.025). Это смещение не всегда является обязательным, но наличие переменной позволяет управлять тем, как выглядит эффект SSAO, а также, в определенных ситуациях, убирает проблемы с рябью в затененных областях.
Но и это еще не все, поскольку такая реализация приводит к заметным артефактам. Он проявляется в тех случаях, когда рассматривается фрагмент, лежащий вблизи края некоторой поверхности. В таких ситуациях алгоритм при сравнении глубин неизбежно захватит и глубины поверхностей, которые могут лежать очень далеко позади рассматриваемой. В этих местах алгоритм ошибочно сильно увеличит степень затенения, что создаст заметные темные ореолы по краям объектов. Лечится артефакт введением дополнительной проверки на расстояние (пример за авторством John Chapman):
Проверка будет ограничивать вклад в коэффициент затенения только для значений глубины, лежащих в пределах радиуса выборки:
Также мы применяем функцию GLSL smoothstep(), которая реализует плавную интерполяцию третьего параметра в пределах между первым и вторым. При этом возвращая 0, если третий параметр меньше или равен первому, либо 1, если третий параметр больше либо равен второму. Если разница глубин оказывается в пределах radius, то её величина будет плавно сглажена в интервале [0., 1.] в соответствии с данной кривой:
Если бы мы использовали четкие границы в условиях проверки глубины, то это добавило бы артефакты в виде резких границ в тех местах, где значения разницы глубин оказываются вне пределов radius.
Последним штрихом мы нормализуем величину коэффициента затенения, используя размер ядра выборки и записываем результат. Также мы инвертируем итоговое значение, вычитая его из единицы, дабы можно было использовать конечное значение напрямую для модуляции фоновой составляющей освещения без дополнительных действий:
Для сцены с лежащим знакомым нам нанокостюмом, выполнение SSAO шейдера дает следующую текстуру:
Как видно, эффект фонового затенения создает неплохую иллюзию глубины. Одно только выходное изображение шейдера уже позволяет различить детали костюма и убедиться, что он действительно лежит на полу, а не левитирует на некотором расстоянии от него.
И все же эффект далек от идеала, поскольку шумовой узор, привнесенный текстурой случайных векторов поворота, легко заметен. Для сглаживания результата расчета SSAO мы применим фильтр размытия.
Размытие фонового затенения
После построения результата SSAO и перед финальным сведением освещения необходимо провести размытие текстуры, хранящей данные о коэффициенте затенения. Для этого мы заведем еще один фреймбуфер:
Тайлинг шумовой текстуры в экранном пространстве обеспечивает вполне определенные характеристики случайности, которые можно использовать в свою пользу при создании фильтра размытия:
Итого у нас на руках есть текстура с данными фонового затенения для каждого фрагмента на экране – все готово для стадии финального сведения изображения!
Применение фонового затенения
Этап применения коэффициента затенения в итоговом расчете освещения на удивление прост: для каждого фрагмента достаточно просто умножить значение фоновой составляющей источника света на коэффициент затенения из подготовленной текстуры. Можно взять готовый шейдер с моделью Блинна-Фонга из урока по отложенному затенению и немного его подправить:
Серьезных изменений здесь всего два: переход к расчетам в видовом пространстве и умножение компоненты фонового освещения на значение AmbientOcclusion. Пример сцены с единственным синим точечным источником света:
Полный исходный код лежит здесь.
Проявление эффекта SSAO сильно зависит от параметров типа kernelSize, radius и bias, зачастую их тонкая подстройка – само собой разумеющееся занятие художника при проработке той или иной локации/сцены. Нет каких-то «лучших» и универсальных сочетаний параметров: для одних сцен хорош малый радиус ядра выборки, другие выигрывают от увеличенного радиуса и числа выборок. В примере используется 64 точки выборки, что, откровенно говоря, избыточно, но вы всегда можете отредактировать код и посмотреть, что получится при меньшем числе выборок.
Кроме перечисленных юниформов, которые отвечают за настройку эффекта, существует возможность явно регулировать выраженность эффекта фонового затенения. Для этого достаточно возвести коэффициент в степень, контролируемую еще одним юниформом:
Советую потратить некоторое время на игру с настройками, поскольку это даст лучшее понимание о характере изменений в итоговой картинке.
Подводя итог, стоит сказать, что хотя визуальный эффект от применения SSAO и достаточно слабозаметный, но в сценах с хорошо расставленным освещением он неоспоримо добавляет заметную толику реализма. Иметь такой инструмент в своем арсенале безусловно ценно.