Arseny Kapoulkine:
Advanced Visual Effects with DirectX 11: Grass, Fur and Hair.
Speakers: Karl Hillesland (AMD), Nicolas Thibieroz (AMD)
Чтобы поддержать высокий уровень докладов AMD, парни решили повторить доклад с прошлой GDC про волосы (http://blog.gamedeff.com/?p=639)
Т.е. собственно есть мощная технология TressFX (которая теперь AMD TressFX), давайте про нее поговорим!
Чтобы кошерно нарисовать волосы требуется их просимулировать на GPU (т.к. их много), нарисовать с AA, прозрачностью для моделирования толщины, самозатенением и хорошим освещением.
Начнем с рендера - как и в прошлом году, мы превращаем сегменты волос в тонкие квады, которые гарантированно покрывают пиксели, в пиксельном шейдере аналитически считаем coverage, который записываем в альфу, ну и еще шейдим волос с использованием shadow maps итп.
А потом результат записываем в per-pixel linked list.
В конце надо бы отсортировать по глубине и поблендить - сортировать все дорого поэтому находим 8 самых ближних фрагментов, их сортируем и блендим в правильном порядке, остальное блендим как получится.
Это все уже было, я не буду повторяться.
Теперь давайте попрофилируем чуток.
Вдруг оказывается что per-pixel linked lists конечно не супер быстрые, но есть более серьезная проблема - шейдинг фрагментов.
Волосы пышные, фрагментов много, при шейдинге надо семплить shadow maps, считать какие-то BRDF итп.
Давайте вспомним что сортировка для блендинга была тоже дорогой поэтому мы сортировали 8 самых ближних фрагментов.
И будем шейдить 8 самых ближних.
Из шейдера уберем собственно шейдинг, оставим запись gbuffer информации в per-pixel linked list.
Для их модели шейдинга было достаточно в gbuffer иметь глубину пикселя, alpha coverage и tangent вектор.
Затем в проходе который раньше сортировал и блендил мы как и раньше выделим 8 самых ближних фрагментов, и их пошейдим как следует.
А все остальные фрагменты пошейдим упрощенным BRDF и упрощенным кодом затенения (деталей не было)
Есть отдельно взятый R7970 в 1080p с 130k fur strands (меховыми волосинками?..)
На нем скорость выглядит так:
В оригинальном (forward) методе 2.80 ms занимает тупо шейдинг всего, без вывода в per-pixel linked list и без сортировки, 3.38 ms занимает все в сумме - т.е. стоимость per-pixel linked list условно 0.5 ms.
В deferred методе все в сумме занимает 2.13 ms.
Давайте профилировать дальше. У Лары были волосы, а мы теперь используем тот же код чтобы сделать мехового монстра - и у нас вместо 20k кривых волос - 130k кривых меха, это 920k сегментов линий.
Оригинальный метод использовал VS чтобы размножать вершины, но это фактически означает что мы превращаем (индексированный) список сегментов в неиндексированный список треугольников, тем самым увеличивая количество работы (т.к. не работает post-VS cache)
Решение - тупо сгенерируем IB.
Теперь надо сделать LOD, для этого в дальних уровнях детализации надо сделать меньше кривых (чтобы выиграть в скорости), сделать кривые толще (чтобы сохранить объем) и увеличить alpha cutoff (чтобы выиграть в скорости)
Alpha cutoff применяется на стадии заполнения per pixel linked list - если альфа фрагмента слишком маленькая то фрагмент можно в список не писать т.к. он не очень важен для результата.
Исходная реализация использовала два буфера для per-pixel linked list, один размером с экран с указателями на начало списков, и один в качестве пула нод.
Замена первого буфера текстурой позволяет увеличить cache coherency засчет того что GPU не хранит текстуры в памяти последовательно.
Исходный код сортировки тех самых 8 самых важных фрагментов использовал динамическую индексацию GPR.
Это дорого, поэтому его заменили на сравнения со статической индексацией регистров (sorting network)
Ну и до оптимизации CS которые симулируют физику не добрались. Этих CS несколько, и не все нужны для всех применений технологии (типа, некоторые шаги симуляции можно отключать для травы или меха)
Еще рассказали что симуляция делалась итеративными constraint солверами, которые пытались сохранять форму волос, сохранять длину каждого сегмента волоса итп.
Итеративные методы работают неплохо, кроме как для сохранения длины сегмента.
Поскольку итеративные методы обычно дают не жесткий constraint с некоторой инерцией.
Был использован некий метод Tridiagonal Matrix Formulation (https://www.youtube.com/watch?v=0zMlNhWYVXQ)
Ну и напоследок то что в production еще вроде не ушло.
Проблема per-pixel linked lists в том что не очень понятно, сколько памяти аллоцировать под списки и что делать при overflow списков (при overflow например часто теряются случайные части волосяного покрова)
Поэтому давайте сделаем k-buffer.
Будем хранить k (например 8 или 16) самых близких фрагментов для каждого пикселя.
При вставке нового фрагмента в k-buffer надо иногда заменить какой-нибудь существующий новым.
Т.е. надо найти самый дальний фрагмент, и если он дальше нового - заменить его новым.
Проблема в том что во-первых GPU - параллельная железка, таких запросов на вставку будет много одновременно.
Во-вторых атомарными инструкциями вставить фрагмент в k-буфер возможным не представляется.
И в-третьих если у вас есть PixelSync то вы работаете в другой компании, ыыы.
(Про pixel sync пацаны не упомянули, но вот например презентация в которой реализуется ровно этот же k-buffer: http://advances.realtimerendering.com/s2013/2013-07-23-SIGGRAPH-PixelSync.pdf)
Ну окей, давайте вспомним что в CS можно все и реализуем mutex.
Выделим бит в k-buffer под флажок блокировки, и будем как на CPU его в цикле пытаться поставить в 1 с помощью interlocked compare exchange.
Если за 16 итераций взять mutex не удалось то сдадимся и не будем записывать фрагмент (на всякий случай, чтобы не повесить GPU)
Ну и напоследок что делать с фрагментами которые не попали в k-buffer? В per-pixel linked list решении мы их блендили в случайном порядке, так и тут мы тоже можем их поблендить в случайном порядке - и потом при финальном compositing k-buffer вблендить "под" слои k-buffer.
Получился другой код который решает ту же проблему и имеет другие характеристики.
Из минусов есть синхронизация софтварным mutex-ом и типично больше bandwidth.
Из плюсов памяти может быть меньше.
У меня все, завтра видимо будет еще по результатам докладов.