Способ рендеринга отложенного освещения. Отсроченное рендеринг с использованием концепции концепции выборки на основе плитки

Полностью исходный код содержится в примерах, доступных по ссылке в конце статьи.

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

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

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

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

Рис 6. Часть сферы, действительно требующая обработки.

Как видно из данного рисунка необходимо вычислять освещенность только для тех фрагментов нелицевой части границы области влияния источника (сферы), для которых не пройден тест глубины.

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

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

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

Таким образом, первый проход запрещает запись во все буфера, кроме буфера трафарета, устанавливает отсечение лицевых граней, в качестве теста глубины задает режим GL_LESS, в качестве теста трафарета задает режим GL_ALWAYS и задает операцию GL_REPLACE для тех фрагментов, для которых не пройден тест глубины.

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

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

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

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

Поэтому стоимость освещения определяется фактически только количеством реально освещаемых пикселов для каждого источника света.

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

Однако далеко не для всех источников света нужны тени и даже для отбрасывающих тени источников света обновление их теневых карт можно осуществлять довольно редко (и используя сильно упрощенные модели при построении теневых карт).

Еще одним вариантом оптимизации работы с большим количеством источников света заключается в разбиении окна на набор одинаковых прямоугольников (см рис 7). После это на CPU для каждого такого прямоугольника составляется список всех источников света, освещающих его точки.

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

Рис 7.Разбиения окна на прямоугольники для локализации небольших источников света.

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

Для этого от "настоящих" источников света трассируется некоторое количество лучей и в точках попадания этих лучей на поверхности сцены создаются фиктивные источники света (цвет и интенсивность этих источников определяются цветом поверхности в месте попадания луча и расстоянием до исходного источника света).

При этом положения этих источников (равно как и их яркость и цвет) изменяются довольно редко и обычно нет никакой необходимости в отбрасывания этими источниками теней. Это позволяет достаточно эффективно использовать deferred shading для подобного имитирования глобальной освещенности.

В игре STALKER была также реализована еще одна интересная идея - кроме двух цветов в одном из свободных каналов G-буфера хранится номер (индекс) материала - materialId .

На этапе освещения materialId используется для выбора слоя из 3D-текстуры, определяющей свойства материала. Фактически основным свойством материала является то, как по двум скалярным произведениям - (n,l) и (n,h) - получить коэффициенты смешивания двух базовых цветов с учетом собственной светимости материала (в игре ряд объектов обладают собственной светимостью, например, глаза монстров).

Таким образом все свойства материала были сведены в одну трехмерную текстуру, для индексирования которой использовались два скалярных произведения ((n,l) и (n,h) ) и индекс материала. В результате получались коэффициенты для смешивания цветов и собственной светимости. При этом по утверждениям разработчиков выяснилось, что принципиально разных материалов крайне мало.

За счет этого удалось прийти к очень небольшому числу материалов (не более 10), размер текстуры для индексирования по (n,l) был взят равным 16, для индексирования по (n,h) - равным 256.

На самом деле использование materialId позволяет легко добавлять поддержку принципиально разных типов шейдеров - просто в шейдере освещения проверяется materialId и в зависимости от его значения выполняется заданная ветвь вычислений.

В DX10.1-версии deferred shading "а в игре STALKER:Чисто небо была использована интересная оптимизация - использовался G -буфер, состоящий всего из двух текстур. В одной текстуре формата RGBA8 хранился диффузный цвет (в RGB компонентах) и собственная светимость em (в альфа-компоненте). Вторая текстура (формата RGBA_16F) в первой компоненте хранила z eye , во второй и третьей - n x и n н , в четвертую были упакованы сразу две величины - materialId и ambient occlusion .

Рис 8. Строение G-буфера в игре STALKER:Чистое Небо.

За счет использования подобного подхода удалось заметно сократить количество бит на один пиксел. Обратите внимание на то, что используемые текстуры имеют разное количество бит на пиксел - подобная возможность (использования при MRT текстур с разным числом бит на пиксел) появилась на GPU с 4-й шейдерной моделью (GeForce 8xxx).

Построенный G -буфер можно также использовать и для реализации ряда других эффектов, таких как слоистый туман, мягкие частицы , screen-space ambient ocllusion(SSAO) и др.

Алгоритм deferred shading кроме ряда плюсов обладает и некоторыми недостатками - он не поддерживает полупрозрачные объекты и стандартные средства антиалиасинга довольно плохо ложатся на его архитектуру.

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

Это может дать и некоторые плюсы - так при рендеринге воды, можно использовать значения z для дна, для точного расчета преломления.

На сайте Humus" а можно скачать альтернативный вариант работы с полупрозрачными объектами в deferred shading , только этот пример требует DX10 (а значит и убожество под названием m$ vi$ta).

В книге ShaderX7 был предложен довольно красивый способ работы с полупрозрачными объектами, не требующий увеличения размера G -буфера. Идея подхода крайне проста - мы сохраняем альфа-значения для фрагментов в G -буфере (например в альфа-канале диффузного цвета) и полупрозрачный объект выводится через строку. При этом у нас в G -буфере оказывается как информация о полупрозрачной поверхности, так и информация о том, что лежит за ней.

Ниже приводится фрагментный шейдер первого прохода, используемый для рендеринга полупрозрачного объекта.

Deferred Lighting (отложенного освещения). См. для технической информации об отложенном освещении.

Note : Deferred Lighting is considered a legacy feature starting with Unity 5.0, as it does not support some of the rendering features (e.g. Standard shader, reflection probes). New projects should consider using Deferred Shading rendering path instead.

NOTE: Deferred rendering is not supported when using Orthographic projection. If the camera’s projection mode is set to Orthographic, the camera will always use Forward rendering.

Обзор

Тип рендеринга Deferred Lighting имеет высочайшее качество освещения и теней. При таком типе не существует ограничений на количество источников света, влияющих на один объект и всё источники света просчитывается попиксельно, что позволяет им корректно работать с картами нормалей и т.д. Кроме того, все источники света могут иметь тени и cookie текстуры.

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

С другой стороны, отложенное освещение не имеет настоящей поддержки anti-aliasing’а (сглаживание изображения) и не может обрабатывать полупрозрачные объекты (такие объекты должны отрисовываться с помощью упреждающего рендеринга). Также не поддерживается опция Receive Shadows (получать тени) компонента Mesh Renderer и culling masks (маски выборочного рендеринга по слоям) поддерживаются только в ограниченной форме. Если быть точнее, то вы можете использовать не более четырёх culling масок. Следовательно, culling маска слоёв должна содержать как минимум все слои минус 4 произвольных слоя, то есть, к примеру, должно быть установлено 28 слоёв из 32. Иначе могут появиться графические артефакты.

Требования

It requires a graphics card with Shader Model 3.0 (or later), support for Depth render textures and two-sided stencil buffers. Most PC graphics cards made after 2004 support deferred lighting, including GeForce FX and later, Radeon X1300 and later, Intel 965 / GMA X3100 and later. On mobile, all OpenGL ES 3.0 capable GPUs support deferred lighting, and some of OpenGL ES 2.0 capable ones support it too (the ones that do support depth textures).

Вопросы производительности

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

Конечно, источники света с тенями требуют намного больше ресурсов, чем источники света без теней. При отложенном освещении, объекты, отбрасывающие тени, всё ещё должны отрисовываться один или более раз для каждого источника освещения с отбрасываемыми тенями. Более того, шейдер освещения, применяющий тени, имеет большее потребление ресурсов при отрисовке, чем в случае с отключенными тенями.

Детали реализации

При включенном отложенном освещении, процесс отрисовки в Unity происходит в 3 прохода:-

  1. Базовый проход: объекты рендерятся для создания буферов экранного пространства с глубиной, нормалями и степенью зеркальности.
  2. Проход освещения: буферы, созданные на предыдущем шаге, используются для расчёта освещения в другой буфер экранного пространства.
  3. Финальный проход: объекты снова рендерятся. Они получают рассчитанное освещение, объединяют его с цветными текстурами и добавляют любое излучаемое освещение или освещение окружения.

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

Базовый проход

Базовый проход рендерит каждый объект один раз. Нормали экранного пространства и степень зеркальности рендерятся в одну ARDB32 Render Texture (с нормалями в RGB каналах и степенью зеркальности в A). Если платформа и оборудование позволяют считывать содержимое Z-буфера в виде текстуры, тогда глубина рендерится неявно. Если к Z-буферу нет доступа в качестве текстуры, тогда глубина рендерится в дополнительном проходе рендеринга с помощью замены шейдера .

Результатом базового прохода является Z-буфер, заполненный содержимым сцены и Render Texture с нормалями и степенью зеркальности.

Проход освещения

Проход освещения рассчитывает освещение, основанное на глубине, нормалях и степени зеркальности. Освещение рассчитывается в экранном пространстве, так что время, требуемое на расчёты не зависит от сложности сцены. Буфер освещения - это одна ARGB32 Render Texture, с диффузным освещением в RGB каналах и монохромным зеркальным освещением в канале A. Значения освещения кодируются логарифмически, для обеспечения большего динамического диапазона, чем обычно доступно при использовании ARGB32 текстуры. Единственная модель освещения, доступная при отложенном освещении - это Blinn-Phong.

Точечные и прожекторные источники света, не пересекающие ближайшую плоскость камеры, рендерятся как 3D формы, с включенным тестом Z-буфера против сцены. Это позволяет тратить немного ресурсов на рендеринг частично или полностью скрытых точечных и прожекторных источников света. Если направленные источники света и точечные/прожекторные источники света пересекают ближайшую плоскость камеры, то они рендерятся в виде полноэкранных квадов.

The above doesn’t apply to directional lights, which are always rendered as fullscreen quads.

Если у источника света включены тени, то они тоже рендерятся и применяются в этом проходе. Учтите, что тени не даются “даром”; требуется рендеринг отбрасывающих тень объектов и к ним должен применяться шейдер с более сложным расчётом освещения.

Единственная доступная модель освещения - Blinn-Phong. Если вы желаете использовать другую модель, вы можете изменить шейдер прохода освещения, разместив изменённую версию файла Internal-PrePassLighting.shader из набора в папке с именем “Resources” в вашей папке “Assets”.

Финальный проход

Финальный проход производит итоговое отрисованное отображение. Здесь все объекты отрисовываются ещё раз шейдерами, которые получают освещение, совмещают его с текстурами и добавляют излучаемое освещение. Карты освещения также применяются в финальном проходе. Вблизи камеры используется динамическое освещение и добавляется только запечённое непрямое освещение. Это переходит в полностью запечённое освещение вдали от камеры.

Детали упреждающего типа рендеринга

Подробности способа рендеринга вершинного освещения

EDIT: Я по-прежнему ищу какую-то помощь в использовании OpenCL или вычислительных шейдеров. Я бы предпочел продолжать использовать OGL 3.3 и не иметь дело с плохой поддержкой драйверов для OGL 4.3 и OpenCL 1.2, но я не могу думать о том, чтобы сделать этот тип затенения, не используя один из двух (чтобы соответствовать огню и плитка). Возможно ли реализовать отбраковку на основе плитки без использования GPGPU?

Я написал отложенную визуализацию в OpenGL 3.3. Прямо сейчас я не делаю никакого отбраковки для светового прохода (я просто представляю полноэкранный квадрат для каждого света). Это (очевидно) имеет тонну переутомления. (Иногда это ~ 100%). Из-за этого я искал способы улучшить производительность во время светового прохода. Кажется, что лучший способ (почти) каждого мнения - отбросить сцену с помощью экранных плит. Это был метод, используемый в Frostbite 2. Я прочитал презентацию от Эндрю Лауритцена во время SIGGRAPH 2010 (http://download-software.intel.com/sites/default/files/m/d/4/1/d/8/lauritzen_deferred_shading_siggraph_2010.pdf), и я не я полностью понимаю концепцию. (и в этом отношении, почему это лучше, чем что-либо другое, и если это лучше для меня)

В презентации Laurtizen перебирает отложенное затенение легкими объемами, квадрациклами и плитами для отбраковки сцены. По его данным, отсроченный рендеринг на основе плитки был самым быстрым (безусловно). Я не понимаю, почему это так. Я предполагаю, что это имеет какое-то отношение к тому, что для каждой плитки все огни собраны вместе. В презентации говорится, что один раз читайте G-Buffer, а затем вычисляйте освещение, но для меня это не имеет смысла. На мой взгляд, я бы выполнил это следующим образом:

For each tile { for each light effecting the tile { render quad (the tile) and compute lighting blend with previous tiles (GL_ONE, GL_ONE) } }

Это по-прежнему будет включать выборку G-Buffer. Я бы подумал, что выполнение этого будет иметь одинаковую (если не хуже) производительность, чем рендеринг экранного квадрата для каждого света. Из того, как это было сказано, похоже, что это происходит:

For each tile { render quad (the tile) and compute all lights }

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

1 ответ

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

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