Arseny Kapoulkine:
Так, значит как и в прошлом году я буду кратко (по возможности) рассказывать о чем рассказывали на GDC (в этот раз - 2014), но в этот раз будет больше.
Каждый доклад будет в отдельном треде.
Сегодня я был на пачке докладов под общим названием Advanced Visual Effects with DirectX 11.
От любимых всеми вендоров и некоторых разработчиков настоящих игр.
Первым был дяденька из Activision:
Advanced Visual Effects with DirectX 11: Tessellation in Call of Duty: Ghosts.
Speaker: Wade Brainerd (Activision)
Форма доклада была неканонической - из слайдов был собран уровень в Call of Duty по которому мужик бегал и показывал всякое, в процессе перекомпилируя шейдеры чтобы показать ту или иную деталь поведения.
Игра ни разу не упала и вообще респект и уважуха.
Arseny Kapoulkine:
Рассказ был про тесселяцию, как и половина сегодняшних докладов.
Итак, есть игра Call of Duty, нужно потесселировать меши потому что хочется более красивые контуры итп.
Была выбрана схема тесселяции Catmull-Clark.
В начале доклада рассказывалось вкратце как она работает -
На входе обычный полигональный меш.
На выходе каждой итерации образуется меш который:
а. Состоит только из квадов (даже если исходный имел треугольники)
б. Тех квадов там в несколько раз больше чем в предыдущей итерации.
в. Меш более гладкий.
Детали есть в wikipedia: http://en.wikipedia.org/wiki/Catmull%E2%80%93Clark_subdivision_surface ну и elsewhere - google.
Особенность алгоритма в том что ему в целом все равно на какой топологии работать (т.е. меш не обязан быть manifold)
И он почти не дает артисту контроля за результатом.
Т.е. никаких доп. контрольных точек нет.
Единственное что может делать артист - он может делать edge/vertex split, дублируя вершины (так чтобы два соседних треугольника не имели общего ребра с т.з. топологии)
Таким образом гарантируя что меш не "сгладится" вокруг этого ребра.
Собственно, подход выбран, надо реализовывать.
Для реализации алгоритма выбирается структура half-edge mesh (читать например тут http://fgiesen.wordpress.com/2012/02/21/half-edge-based-mesh-representations-theory/)
Дальше обычную реализацию, которая фактически заключается в том что для каждого исходного полигона надо поусреднять всякие вершины и сгенерировать новых полигонов, можно реализовать с помощью Compute Shader.
Для этого нужен небольшой препроцесс, детали которого рассказаны не были.
Зато есть демка на WebGL от автора презентации - http://wadeb.com/subd/
По демке можно увидеть что алгоритм хорошо работает с "висящими" ребрами (без парных треугольников)
Теперь значит начинаются проблемы.
Канонический подход за один шаг алгоритма генерирует более подробную версию исходного меша (т.н. global subdivision), целиком.
В идеале нам хочется генерировать больше треугольников не везде, а только там где надо.
Давайте посмотрим на эту картинку (я тут гуглю иллюстрации, они были в слайдах)
https://facultypages.scad.edu/~mkesson/AppNotes/figures.hierarchsubdiv/subdiv_face_index.gif
В хорошем меше очень много регулярных квадов (в презентации регулярный это на самом деле квады-соседи со всех сторон, т.е. можно получить соседний квад по всем ребрам, и он только один)
Есть работы в которых выводится аналитическая формула, по которой можно тесселировать регулярный патч и получить тот же результат.
Т.е. если бы у нас были в модели только регулярные патчи то можно было бы использовать аппаратную тесселяцию.
Вот более сложная картинка:
http://workshopsfactory.files.wordpress.com/2009/07/cat2.jpg?w=510
Тут вот видно что в центре например чертова прорва регулярных патчей.
Peter Popov:
Йе! Катмулл-Кларк! Мне собственно совсем непонятно, как в динамике стыковать разные лоды. Потому что оно радикально плывет от контрольных точек. Делать в стиле CLOD - хрен пойми как и дорого.
а так получаются прикольные округлые хуйни. Абсолютно неконтролируемые. Во всяком случае у меня в прошлом веке так получалось.
Arseny Kapoulkine:
Все так, округлые и неконтролируемые. Единственный контроль артиста - сделать hard edge.
Говорят им нравится.
Arseny Kapoulkine:
И еще есть островки регулярных патчей, но их соединяет всякая некошерная геометрия.
Типа вершин со степенью 5 и прочей гадости.
Ну значит окей, сказали мужики, давайте исходные полигоны модели разделим на регулярные и нерегулярные.
Те которые нерегулярные будут тесселироваться с использованием compute shader в несколько проходов (чем больше степень subdiv тем больше проходов)
А те которые регулярные будут тесселироваться с использованием HW tesselator (+ hull/domain shaders)
Для этого надо в каждом патче в hull shader посчитать, насколько мы его хотим тесселировать.
А в domain shader на основании вершин патча и соседей аналитически посчитать сабдив.
И теперь у нас начинаются проблемы.
А, до проблем - этот подход называется Feature-Adaptive Subdivision.
Вот вроде базовая статья http://research.microsoft.com/en-us/um/people/cloop/tog2012.pdf
В процессе реализации алгоритма Activision-ом появилась open source реализация (наверное не на GPU) от Pixar, http://opensubdiv.org/
Ну так вот, о проблемах.
Как все наверное уже в курсе, хардварная тесселяция позволяет на каждый исходный патч указать степень тесселяции патча и ребер, ограниченную неким диапазоном (например [0..64])
Но не обязательно целочисленную, например.
В результате каждого шага global subdivision у catmull-clark получается так что каждое ребро делится пополам.
Т.е. N subdiv итераций соответствуют 2^N tess factor в хардварной тесселяции.
Проблема в том, что геометрия побита на две группы полигонов - regular и irregular - которые имеют общие ребра.
И эти ребра надо тесселировать одинаково.
Поэтому начинаем с того что на висящих ребрах regular патчей мы делаем tess factor 2^N (причем N - глобальный фактор которым мы subdivid-им нерегулярные фейсы)
Дальше проблема в том что при аналитическом подсчете результат catmull-clark (использующемся для регулярных патчей) получается результат немного отличающийся от итеративного метода.
God bless floating-point.
Поэтому в итеративном методе вершины на границе мы будем пересчитывать с использованием того же самого кода который используется в hull shader.
Там еще могут быть проблемы чуть разной точности вычислений из-за оптимизаций компилятора, но они с этим не столкнулись.
Дальше еще оказывается что catmull-clark слишком сильно растягивает UV.
Потому что он ориентируется на топологию геометрии, а у UV своя топология.
Насколько я понимаю, они боролись с этим запуская пересчет UV по такой же схеме как и подсчет позиции вершины, но с использованием топологических данных UV.
Ну и значит после всей этой возни мы наконец-то можем адаптивно посчитать tesselation factor для внутренних вершин и ребер регулярных сабмешей.
По вполне очевидной формуле "насколько сдвинется вершина в screen-space если тесселяцию запустить".
Все хорошо, только есть проблема - некоторые модели чуть более чем полностью состоят из irregular patches.
Поэтому достоинства регулярных патчей (адаптивная тесселяция) не работают.
Они сделали небольшой хак который позволил им считать регулярными некоторые патчи которые базовой схемой не учитываются, я его пропущу - что-то долго.
Это понятно в основном проблема арта, не весь арт был сделан с учетом тесселяции.
В итоге у нас есть схема при которой мы берем существующий меш, его чуть препроцессим в content pipeline чтобы отделить регулярные патчи от нерегулярных и вообще собрать half-edge mesh структуру, а потом в рантайме часть меша мы умеем тесселировать в фиксированное число раз.
А остальную (надежда что бОльшую) - адаптивно в зависимости от камеры.
Давайте пооптимизируем теперь.
Во-первых, патчей много, тесселировать их долго - надо их куллить. Используется простая эвристика - считается dot product view вектора с усредненной нормалью вершин.
Во-вторых, для каждого патча нужно посчитать факторы тесселяции.
Этот расчет условно сложный - там надо померять screen space ошибку ребер.
И есть проблема - утверждается, что scheduling hull шейдера производится per control point.
И кусок шейдера который выполняется per patch просто запускается в первом треде, а остальные в это время ждут.
Т.е. в API есть patchconstantfunc что теоретически должно позволить запускать все параллельно, но это видимо дополнительный буфер чтобы передавать атрибуты из стадии в стадию, поэтому некоторые вендоры (или все) выполняют patchconstantfunc только в первом треде при диспатче.
Поэтому длинные вычисления в patchconstantfunc - это плохо.
Год 2014 поэтому решение понятно какое - давайте посчитаем для каждого патча факторы тесселяции заранее, в compute шейдере.
Все данные про факторы тесселяции патчей собирается в большой буфер, запускается много раз CS который в кусчки буфера пишет данные, hull shader потом читает результат.
Ну и напоследок, после того как мы все потесселировали - можно добавить поддержку displacement mapping.
Оказывается, для дизайна уровней CoD все еще используется Q3 Radiant.
В Q3 Radiant очень сложно собирать сложную геометрию.
Зато в Q3 Radiant просто шлепать материалы с дисплейсментом на стенки.
Интересное про displacement - displacement идет по нормали.
Поэтому в vertex splits (на жестких ребрах) одна и та же копия вертекса может разъезжаться в разные стороны.
И получается дырка в геометрии.
Чтобы дырки не было, map compiler ее заранее сшивает парой треугольников-дегенератов.
И когда вершинки разъезжаются, дырки не образуется.
Displacement maps они блендят несколько сразу в одном материале, плюс немного смещается UV с использованием parallax mapping (не POM) чтобы менее заметны были артефакты семплинга displacement.
Уф, все, следующие будут покороче.
Kirill Kyalundzyuga:
там таблицы разбиения считаются на CPU, но тесселяция и интерполяция могут считаться на GPU с помощью CUDA/OpenCL/GLSL TF/GLSL Tesselation бэкэндов.
Arseny Kapoulkine:
А, значит то же самое, хорошо.
Arseny Kapoulkine:
Кто не понял про это - в D3D11 модели кусочек hull shader выполняется один раз, считая данные общие для всего патча, а потом остаток hull shader запускается один раз для каждой контрольной точки, см. синтаксис вот здесь http://msdn.microsoft.com/en-us/library/windows/desktop/ff476339(v=vs.85).aspx
Kirill Kyalundzyuga:
мне всё-таки кажется, что не vertex split, а vertex creases.
Arseny Kapoulkine:
Не очень понял куда относится твой комментарий.
Kirill Kyalundzyuga:
я про "Единственное что может делать артист - он может делать edge/vertex split, дублируя вершины (так чтобы два соседних треугольника не имели общего ребра с т.з. топологии)".
для контролирования sharpness по ребру есть vertex creases.
Sergei Miloikov:
Круто, спасибо.