Одной из классических задач компьютерной графики является задача определения видимости — определить какие именно объекты (и какие их части) будут видны (или не видны) для данного положения наблюдателя — HSR (Hidden Surface Removal).
Сейчас эта задача благополучная переложена на аппаратно-реализованный z-буфер. Однако оказывается, что даже аппаратно реализованный z-буфер для сцен большой сложности (и с использованием сложный шейдеров) может дать явно недостаточное быстродействие. Причина этого — z-буфер требует вывода всех граней и их растеризации, давая тем самым затраты порядка O(N), где N — число выводимых объектов.
Использование сложных шейдеров еще больше усугубляет данную ситуацию — цена вывода для каждого полученного при растеризации грани фрагмента заметно возвращает и, если на каждый пиксел результирующего изображения приходится много фрагментов (отброшенных z-буфером) — так называемая depth complexity, то вывод всех фрагментов всех граней оказывается очень дорогостоящим.
[Unity] Оптимизация. Occlusion culling. Зачем нужен. Как пользоваться
Затраты, связанные со сложными шейдерами можно заметно сократить за счет использование так называемого z-prepass — сначала вся сцена выводится только в буфер глубины (отключаются фрагментные шейдеры, запись во все остальные буфера). За счет отключения всего лишнего мы получаем довольно дешевый проход, в результате которого в z-буфере оказывается точные значения глубины для всех видимых пикселов.
После этого идет второй проход, где отключена запись в z-буфер и в качестве теста глубины используется GL_EQUAL. Это позволяет сразу же отбрасывать все невидимые фрагменты без вызова фрагментного шейдера для них (современные GPU поддерживает возможность пропускать вызовы фрагментного шейдера для заведомо невидимых фрагментов).
В результате мы получаем, что с точки зрения фрагментного шейдера мы получаем zero overhead, однако от необходимости обработки, растеризации и выполнения теста глубины это не спасает — алгоритм все равно имеет сложность O(N).
Тут можно воспользоваться целым рядом подходов, позволяющим находить и отбрасывать заведомо невидимые грани/объекты. За счет этого удается понизить вычислительную сложность алгоритма — в идеале хочется получить зависимость линейную по количеству действительно видимых граней.
Все подобные подходы называются culling и обычно активно используют различные пространственные индексы.
Простейшим вариантом culling’а является frustum culling — отсечение по пирамиде видимости. В этом случае сразу же отбрасываются объекты, заведомо не попадающие в усеченную пирамиду видимости для камеры. За счет этого количество граней, подаваемых на вход z-буфера можно сократить в несколько раз. Более того, использование пространственных индексов позволяет очень быстро получить список попадающих в пирамиду объектов — без полного перебора всех объектов.
Рис 1. frusum culling — только объекты E, F, G и H могут быть видны.
Однако гораздо большую выгоду для многих сцен может дать учет загораживающей способности самих выводимых объектов — так расположенный прямо перед камерой объект закрывает от камеры довольно большую часть сцены. Так объект A полностью закрывает собой от камеры объекты B и C (см. рис 2).
Рис 2. Occlusion culling.
Если же учитывать закрывание сразу группой объект, то получается т.н. occluder fusion — несколько объектов, взятые вместе, закрывают гораздо большую часть сцены, чем взятые по отдельности (см. рис 3).
Рис 3. Occluder fusion — ни один из объектов A и B, взятый по отдельности, не может закрыть собой объект C. Однако взятые вместе они полностью закрывают его.
Закрывающую способность отдельных объектов еще можно моделировать на CPU — обычно вводятся так называемые закрыватели (occluders) — невидимые и более простые объекты — используемые для построения «закрытых» областей пространства. По каждому из таких закрывателей строится пирамида, представляющая собой часть пространства, закрытую данным закрывателем от наблюдателя. Однако подобные закрывающие объекты обычно создаются и расставляются вручную и не поддерживают occluder fusion.
Для того, чтобы эффективно использовать occluder fusion необходим быстрый доступ к содержимому z-буфера, т.е. необходима аппаратная поддержка occlusion test’ов самим GPU.
В OpenGL аппаратная поддержка occlusion test’ов осуществляется через два расширения — ARB_occlusion_query и NV_conditional_render.
Первое из этих расширений (ARB_occlusion_query) позволяет вывести группу объектов и запросить GPU о количестве их фрагментов, прошедших тест глубины. За счет этого можно получить простой способ проверки видимости для объектов и групп объектов — достаточно просто вывести ограничивающее тело для заданных объектов (запретив записи во все буфера) и посмотреть сколько фрагментов из растровой развертки этого ограничивающего тела прошли тест глубины.
Сам запрос на число проходящих тест глубины фрагментов осуществляется через специальный объект — query. Каждый query-объект идентифицируется при помощи беззнакового отличного от нуля целого числа (подобно текстурам). Создание и и уничтожение query-объектов осуществляются при помощи следующих функций :
void glGenQueriesARB ( GLsizei n, GLuint * ids ); void glDeleteQueriesARB ( GLsizei n, const GLuint * ids );
При помощи функции glIsQueryARB можно проверить является ли заданное число идентификатором какого-либо query-объекта.
GLboolean glIsQueryARB ( GLuint id );
Функции glBeginQueryARB и glEndQueryARB служат для обозначения тестовых объектов, т.е. тех объектов, для которых считается число фрагментов, прошедших тест глубины (при этом запись во все буфера может запрещена — важно лишь выполнение теста). Все объекты выводимые между вызовами glBeginQueryARB и glEndQueryARB являются тестовыми.
void glBeginQueryARB ( GLenum target, GLuint queryId ); void glEndQueryARB ( GLenum target );
В качестве параметра target используется константа GL_SAMPLES_PASSED_ARB, а параметр queryId задает query-объект, используемый для получения результата. Обратите внимание, что запрос видимости (occlusion query) является асинхронным и их может быть много — т.е. можно создать целую серия подряд идущих запросов видимости, каждый такой запрос будет иметь свой query-объект и свою пару glBeginQueryARB и glBeginQueryARB (эти пары не могут пересекаться и быть вложенными).
Для получения информации о результате и состоянии запроса служат следующие функции:
void glGetQueryObjectivARB ( GLuint id, GLenum pname, int * params ); void glGetQueryObjectuivARB ( GLuint id, GLenum pname, GLuint * params ); void glGetQueryivARB ( GLenum target, GLenum pname, int * params );
При помощи этих функций можно получить состояние запроса (готов или еще нет), результат запроса, а также разрядность используемого счетчика в битах.
Поскольку запрос является асинхронным, то важно иметь возможность быстрой проверки готов ли результат. Для этого служит следующий фрагмент кода.
GLuint resultReady; glGetQueryObjectuivARB ( queryId, GL_QUERY_RESULT_AVAILABLE_ARB,
Для получения самого результата (т.е. количества прошедших тест глубины фрагментов) служит следующий код:
GLuint result; glQueryObjectuiv ( queryId, GL_QUERY_RESULT_ARB,
Для получения числа бит счетчика фрагментов служит приведенный ниже код.
int numBits; glQueryObjectiv ( GL_SAMPLES_PASSED_ARB, GL_QUERY_COUNTER_BITS_ARB,
Однако в силу устройства GPU запрос на видимость (occlusion query) обладает латентностью, т.е. его результат доступен не сразу же после запроса, а лишь спустя некоторое время. Однако существует возможность узнать готов ли уже результат, т.е. можно в ожидании готовности выполнять какие-либо другие операции.
Основная часть этой латентности связана с необходимостью дождаться когда соответствующие примитивы будут растеризованы и обработаны: обычно все команды работают в асинхронном режиме, т.е. посланные команды попадают в командный буфер и выполняются по мере освобождения ресурсов. Тем самым чтобы получить ответ на запрос видимости необходимо дождаться, когда посланные тестовые грани действительно будут полностью обработаны.
Значительно сократить ожидание может использование расширения NV_conditional_render. Данное расширение позволяет связать выполнение заданного набора команд с переданным ранее запросом на видимость (occlusion query). Тем самым, не нужно дожидаться пока результат запроса будет готов — можно сразу же отправить данные, но с пометкой условного рендеринга. Если в результате переданного запроса будет установлена невидимость, то вывод соответствующих объектов будет просто пропущен.
Расширение NV_conditional_render вводит всего две новых команды, обозначающие начало и конец «условного рендеринга».
void glBeginConditionalRenderNV ( GLuint queryId, GLenum mode ); void glEndConditionalRenderNV ();
Параметр queryId задает запрос на видимость (occlusion query), результат которого (GL_SAMPLES_PASSED_ARB) будет использован для решения, стоит ли выводить объекты из данного блока условного рендеринга.
Параметр mode задает, что надо делать для случая, если результаты запроса все-таки не готовы (такое может быть из-за параллельного характера обработки данных на GPU, поэтому желательно чтобы между заданием occlusion query и началом блока условного рендеринга были другие команды). Возможными значениями для данного параметра являются GL_QUERY_WAIT_NV, GL_QUERY_NO_WAIT_NV, GL_QUERY_BY_REGION_WAIT_NV и GL_QUERY_BY_REGION_NO_WAIT_NV.
Режим GL_QUERY_WAIT_NV задает, что в случае, когда к началу рендеринга результаты запроса видимости еще не готовы, необходимо их дождаться и по ним принять решение выводить объекты или нет.
Режим GL_QUERY_NO_WAIT_NV означает, что в случае неготовности результатов, следует сразу же вывести объекты, т.е. в этом случае происходит «нормальный» рендеринг.
Два последних режима сильно завязаны на реализацию. Для них считается, что конкретная реализация может разбить фреймбуфер на несколько областей (регионов) и отдельно для каждого из них проверить запрос видимости (т.е. есть ли хотя бы один прошедший тест глубины пиксел для данного региона). И в этом случае может происходить разбиение «условно выводимых» объектов по регионам и их вывод. Т.е. это может оказаться более эффективным, нежели обычный рендеринг, но как это должно поддерживаться не оговорено и полностью зависит от реализации.
Таким образом использование «условного рендеринга» позволяет не дожидаться, пока запрос на видимость вернется с GPU на CPU, а просто передать данные и позволить GPU самому решить нужно ли выводить эти данные.
Источник: steps3d.narod.ru
Occlusion Culling — что это такое и с чем его кушать? :))
Ну в общем что это отсечение граней перекрытых другими гранями это я понял. Может кто-нибудь дать объяснение более
детальное?
Например, если есть какое-нибудь дерево отсечения, узлы(листья) описаны параллепипедами, как можно отсечь параллепипеды
которые закрыты другими параллепипедами(используя функции ОГЛ)? 🙂
#1
23:24, 16 сен 2005
arb occlusion query
#2
23:25, 16 сен 2005
на этом сайте http://opengl.gamedev.ru/doc/
раздел Запросы на проверку видимости (occlusion query).
#3
0:08, 17 сен 2005
Спасибо, но одними описаниями функций я вряд ли обойдусь. Может сами что-нибудь по теме рассказажете? :))
#4
0:24, 17 сен 2005
на gamedev.net есть статья, найти, думаю, не проблема =)
#5
11:39, 17 сен 2005
включаешь occlusion query, рисуешь что-нибудь, выключаешь. Примерно к следущему кадру видеокарта скажет сколько пикселей нарисовлось от этого обьекта.
#6
12:24, 17 сен 2005
SergeyN19
Блин! Объяснил. :((
to all
Допустим у меня есть массив кубиков, как можно проверить какие кубики видны, а какие закрываются другими кубиками?
ЗЫ: еще ведь может быть что один кубик закрывает несколько и наоброт! Что в этом случае делать?
ЗЗЫ: как в играх делается , проверяются кубики деревап отсечения или полигоны?
#7
14:02, 17 сен 2005
В общих чертах что-то типа этого:
Используем баунды объектов, рендерим их в zbuffer (все)
Потом рендерим каждый баунд отдельно с occlusion query, если получили значение 0 (т.е. 0 пикселей было нарисовано), то эту фигуру не рисуем.
Узнаем фигуры, которые нужно рисовать — рендерим сцену.
Т.е. если один объект полностью перекрывает другой, то он будет отсечен occlusion cullingом.
Деревья тут не используются, основная соль в occlusion query.
#8
14:46, 17 сен 2005
trurl
Что значит рендерим в z-buffer? 🙂
Как я понял: берем бибоксы и просто рендерим их отдельно(отдельным проходом)? И потом смотрим какие пиксели отрисовались, а какие нет, если пиксели объекта не нарисовались, то забиваем на трианглы принадлежащие этому бибоксу(если дерево юзаем). Правильно?
Как выглядит реализация проверки пикселей попавших в камеру? И вообще, тут кода по теме можешь немного бросить? :))
#9
16:02, 17 сен 2005
Пишешь в яндексе Arb occlusion query, получишь ссылку на спецификацию этого расширения, там есть описание пример использования и тд. А смысл ты понял верно.
#10
0:10, 18 сен 2005
resurected_timofei
Реализацию можешь показать? :))
#11
12:48, 18 сен 2005
mocia
Early Z test и occlusion query появились вроде бы одновременно в картах поколения DX9.
В картах FX-серии query,по слухам,работает не очень хорошо.Early Z test помогает хорошо,особенно
на тяжелых шейдерах.И Кармак это конкретно использует(юзает:))) в D3.
На картах GF6xxx ++(про Radeon не знаю-наверное аналогично) можно (и нужно-имхо) использовать
Early Z test и occlusion query вместе.Сам недавно пробовал такое на GF6600-примерно получается добавка к fps
от -10%(за «легкой»стенкой ничего нет) до 150%(за легкой стенкой куча всего тяжелого)
Причем 3/4 добавки получается от Early Z test.
Это все на DX9 делалось,но принцип одинаковый и ничего сложного в этих штуках нет.
Можно этот метод обсудить,кстати.Я,ессно,не во всем до конца уверен.Впрочем,на ваше усмотрение.
PS.бибоксы использовать нельзя,конечно.И самого главного не было сказано-в каком порядке рендерить .
#12
18:29, 18 сен 2005
Ilia
ссылку про «early z» plz
#13
0:28, 19 сен 2005
Скачал пример с Ultimate в нем написано как юзать ARB Query, честно говоря. какой идиот придумал рендерить сначала сцену,
а потом «объекты для query запросов»? Получается что рендерятся и query и вся геометрия. И где оптимайз?! Или я туплю. :((
В общем вопрос: я подготовил кубики из узлов дерева отсечения(рисуются с помощью GL_TRIANGLES), рендерить, как я понял,
надо между парой «glBeginQueryARB( GL_SAMPLES_PASSED_ARB, query);glEndQueryARB( GL_SAMPLES_PASSED_ARB );», а
смотреть какие фрагменты отрисовались функцией: glGetQueryObjectuivARB( query, GL_QUERY_RESULT_ARB,
. и собственно вопрос: где и как мне надо все это дело рендерить чтобы не закрыть «полезную» геометрию? :))
Источник: www.gamedev.ru
Окно Occlusion Culling
Occlusion Culling это функция, отключающая рендеринг тех объектов, которые в данные момент не видит камера (они закрыты другими объектами). В компьютерной 3D графике это не происходит автоматически. Чаще всего сначала отрисовываются объекты, расположенные дальше от камеры и уже поверх них отрисовываются ближние к камере объекты (это называется “overdraw”). Occlusion Culling отличается от Frustum Culling.
Frustum Culling отключает только рендеринг объектов, не попадающих в область обзора камеры, не трогая при этом скрытые по overdraw объекты. Обратите внимание, что Frustum Culling полезен даже при использовании Occlusion Culling.
Occlusion сulling, используя виртуальную камеру, проходит по сцене для построения иерархии потенциально видимых наборов объектов. Эти данные используются в рантайме каждой камерой для определения того что она видит, и что нет. Опираясь на полученную информацию, Unity обеспечивает рендеринг только видимых объектов. Это уменьшает количество draw calls и увеличивает производительность игры.
Данные для occlusion culling состоят из ячеек. Каждая ячейка — частичка сцены. Ячейки образуют бинарное дерево. Occlusion Culling использует два дерева. Первое View Cells(для статичных объектов), второе Target Cells(для движущихся объектов).
View Cells содержит список индексов, который определяет видимость статичных объектов с более высокой точностью.
Об этом важно помнить при создании объектов, потому что нужен хороший баланс между размерами объектов и ячеек. В идеале, у вас не должно быть ячеек, которые очень малы по сравнению с объектами. И в то же время, у вас не должно быть объектов, охватывающих большое количество ячеек. Иногда полезно разбивать большие объекты на части.
Иногда полезно объединять маленькие объекты между собой (для уменьшения draw calls), и, пока они остаются в одной ячейке, occlusion culling будет корректно работать. Коллекцию ячеек и информацию о видимости, определяющую какие ячейки выдимы из другой ячейки, называют PVS (Potentially Visible Set).
You can use the ‘overdraw’ scene rendering mode to see the amount of overdraw that is occuring, and the stats information pane in the game view to see the amount of triangles, verts, and batches that are being rendered. Below is a comparison of these before and after applying occlusion culling.
Настройка Occlusion Culling
Для правильного использования Occlusion Culling есть некоторые правила. Во-первых, ваша геометрия на уровне должна быть разбита на куски разумных размеров. Это еще нужно для того, чтобы делить уровень на небольшие, хорошо определенные области, отделенные друг от друга большими объектами типа стен, зданий и т.д.
Идея заключается в том, что каждый индивидуальный меш будет либо включен, либо выключен (в зависимости от данных occlusion culling). Так, если у вас есть объект, содержащий всю мебель в комнате, то рендерится будет либо все, либо ничего. В этом случае лучше делать каждый предмет мебели отдельным мешем, тогда каждый из них будет отрендерен в зависимости от точки обзора камеры.
You need to tag all scene objects that you want to be part of the occlusion to Occluder Static in the Inspector. The fastest way to do this is to multi-select the objects you want to be included in occlusion calculations, and mark them as Occluder Static and Occludee Static.
When should you use Occludee Static? Completely transparent or translucent objects that do not occlude, as well as small objects that are unlikely to occlude other things, should be marked as Occludees, but not Occluders. This means they will be considered in occlusion by other objects, but will not be considered as occluders themselves, which will help reduce computation.
When using LOD groups, only the base level object (LOD0) may be used as an Occluder.
Окно Occlusion Culling
For most operations dealing with Occlusion Culling, you should use the Occlusion Culling Window (Window > Rendering > Occlusion Culling)
In the Occlusion Culling Window, you can work with occluder meshes, and Occlusion Areas.
Во вкладке Object окна Occlusion Culling Window при выделенном на сцене компоненте Mesh Renderer, вы можете изменять соответствующие Static flags:
Во вкладке Object окна Occlusion Culling, при выделенном Occlusion Area, вы можете работать с соответствующими свойствами OcclusionArea. Для более подробной информации, перейдите в раздел Occlusion Area
ПРИМЕЧАНИЕ: По умолчанию, если вы не создаете областей для occlusion culling , она применяется ко всей сцене.
NOTE: Whenever your camera is outside occlusion areas, occlusion culling will not be applied. It is important to set up your Occlusion Areas to cover the places where the camera can potentially be, but making the areas too large incurs a cost during baking.
Occlusion Culling — Запекание
The occlusion culling bake window has a “Set Default Parameters” button, which allows you to reset the bake values to Unity’s default values. These are good for many typical scenes, however you’ll often be able to get better results by adjusting the values to suit the particular contents of your scene.
Свойства
Smallest Occluder | The size of the smallest object that will be used to hide other objects when doing occlusion culling. Any objects smaller than this size will never cause objects occluded by them to be culled. For example, with a value of 5, all objects that are higher or wider than 5 meters will cause hidden objects behind them to be culled (not rendered, saving render time). Picking a good value for this property is a balance between occlusion accuracy and storage size for the occlusion data. |
Smallest Hole | This value represents the smallest gap between geometry through which the camera is supposed to see. The value represents the diameter of an object that could fit through the hole. If your scene has very small cracks through which the camera should be able to see, the Smallest Hole value must be smaller than the narrowest dimension of the gap. |
Backface Threshold | Unity’s occlusion uses a data size optimization which reduces unnecessary details by testing backfaces. The default value of 100 is robust and never removes backfaces from the dataset. A value of 5 would aggressively reduce the data based on locations with visible backfaces. The idea is that typically, valid camera positions would not normally see many backfaces — for example, the view of the underside of a terrain, or the view from within a solid object that you should not be able to reach. With a threshold lower than 100, Unity will remove these areas from the dataset entirely, thereby reducing the data size for the occlusion. |
Когда вы закончите настройки этих значений, нажмите на кнопку Bake чтобы запустить обработку данных Occlusion Culling. Если вы не удовлетворены результатом, нажмите кнопку Clear для удаления ранее рассчитанных данных.
Occlusion Culling — Визуализация.
Все объекты в сцене влияют на ограничивающий объем размер, поэтому пытайтесь держать их все в видимых границах сцены.
When you’re ready to generate the occlusion data, click the Bake button. Remember to choose the Memory Limit in the Bake tab. Lower values make the generation quicker and less precise, higher values are to be used for production quality closer to release.
Имейте ввиду, что время, необходимое для вычисления данных окклюзии, будет зависеть от уровня ячеек, размера и качества которые вы укажите. Unity покажет статус PVS генерации в нижней части главного окна.
После того, как обработка завершится, вы увидите красочные кубы в зоне видимости. Цветные зоны — это регионы с одинаковыми данными окклюзии.
Если хотите удалить вычисленные ранее данные для Occlusion Culling, нажмите кнопку Clear.
Зона отсечения (Occlusion Area)(Pro версия)
Чтобы применить отсечение к подвижным (динамичным) объектам вам необходимо будет создать Occlusion Area, затем изменить её размеры, чтобы она покрывала собой всё то пространство, в котором будут перемещаться эти объекты (само-собой, что такие объекты не могут быть помечены как статичные). Зоны отсечения можно создавать, просто добавляя компонент Occlusion Area к пустым игровым объектам (в меню Component->Rendering->Occlusion Area).
After creating the Occlusion Area, check the Is View Volume checkbox to occlude moving objects.
Size | Определяет размер зоны отсечения. |
Center | Позволяет выставить центр зоны отсечения. По-умолчанию его значение равно 0,0,0 и расположен он как правило в центре цветного бокса. |
Is View Volume | Определяет, где может находиться камера. Выставляйте этот флажок, если вам необходимо отсечь статичные объекты именно в этой зоне отсечения. |
After you have added the Occlusion Area, you need to see how it divides the box into cells. To see how the occlusion area will be calculated, select Edit and toggle the View button in the Occlusion Culling Preview Panel.
Тестирование полученного отсечения (Testing the generated occlusion)
После того как расчёт был произведён, вы можете протестировать то, что получилось в итоге, путём активации функции отсечения объектов (в Occlusion Culling Preview Panel в режиме визуализации) и перемещая Main Camera в видовом окне сцены.
Во время перемещения камеры по сцене (находитесь вы в игровом режиме или нет), вы заметите как различные объекты сцены будут то исчезать, то появляться. Важность данного процесса состоит в том, что таким образом вы будете отлавливать ошибки результата расчётов отсечений. Вы распознаете объекты, которые выдают ошибки по тому, как они будут внезапно выскакивать в видовом окне во время перемещения камеры по сцене. Одним из выходов из данной ситуации является либо смена выходного разрешения (если вы имеете дело с целевыми объёмами), либо перемещение объектов с места на место таким образом, чтобы они больше не мерцали и тем самым не вызывали дальнейших проблем. Для отладки проблем, связанных с отсечением объектов, вы можете перемещать к проблемным областям главную камеру для их точечной проверки.
Когда процесс расчётов будет завершён, вы увидите в окне проекции цветные параллелепипеды. Синие кубы представляют являются ячейками Target Volumes. Белые кубы же являются ячейками View Volumes. Они служат для разбиения этих объёмов на мелкие части. Если параметры были выставлены правильно, вы заметите что часть объектов в сцене перестала быть видимой.
Это потому что либо они находятся вне поля зрения камеры, либо они закрыты другими объектами.
Если даже после завершения всех расчётов в вашей сцене, все объекты по прежнему видны, т.е. не отсекаются, значит вам надо попробовать разбить их на более мелкие части, которые умещались бы внутри ячеек.
Источник: docs.unity3d.com