четверг, 29 июля 2010 г.

Single point silhouette approximation

По-новой реализовал поиск силуэтов и построение теневого объёма на GPU. На этот раз я решил воспользоваться изюминкой Direct3D 11 и превратил структуру источника света в класс с собственными методами, т. к. гонять структуру по функциям aka C-style скучно.
Даже this поддерживается, хотя в SDK я не нашёл упоминания об этом keyword.


Раз уж я снова решил заняться мягкими тенями, то нужно рассказать об одной проблеме, которую необходимо преодолеть для достижения реалистичности пенумбры. Дело в том, что теневые объёмы (и карты теней тоже), строятся по упрощённой схеме - как они видны из центра источника света. Всё ок, пока нам нужны только примитивные, жёсткие тени или же мягкие тени, полученные каким-нить размазыванием жёстких (а что? пипл хавает). Но если источник света протяжённый, то shadow caster должен рассматриваться с позиций, по крайней мере, нескольких сэмплов на поверхности этого источника. В случае карт теней придётся рендерить кастер в отдельные карты N раз, в случае теневых объёмов - искать силуэт и вытягивать объём в геометрическом шейдере N раз. Это слишком сильно бьёт по производительности, и поэтому даже в случае мягких теней всё равно используют single point approximation.

К сожалению, такое отступление от честного подхода даром не проходит - пенумбра в некоторых случаях выглядит неестественно, как бы overshadowed, что портит впечатление. Особенно это заметно в случае penumbra wedges: авторы алгоритма как-то приводили картинки с искажённой пенумброй и эталоном, точно такие же отклонения были и у меня.


Кроме того, из-за аппроксимации силуэтные рёбра могут меняться неожиданным образом, перескакивая с ребра не ребро, если геометрия объекта грубая (коробка, например), что ведёт к penumbrae jumping.

В случае penumbra wedges выхода два: либо искать и рисовать теневые объёмы по N раз (а также рассчитывать N раз пенумбру в шейдере), либо искать силуэт каким-то особым образом, так, чтобы учесть все сэмплы на поверхности и. с. Учитывая, что теневые объёмы и так крайне прожорливы к филлрейту, первый вариант малореалистичен (хотя и очень прост в реализации). Интересен второй вариант, при котором учитываются все сэмплы, но дубликаты силуэтных рёбер при этом отбрасываются. Таким образом исходный теневой объём и пенумбра лишь немного "прибавят в весе". Неясно, насколько этот подход правилен, результаты сможет показать только тестовый рендер.

Хуже всего, что стандартными средствами Direct3D 11 невозможно отбрасывать рёбра-дубликаты, т. к. геометрический шейдер не имеет доступа к топологии всего меша. Кроме того, если на вход GS можно подавать индексированную геометрию, то на выходе получаем только неиндексированную. Т. е. shared by index вершины размножаются и мы никак не можем сохранить какое-то общее для них свойство (добавлены в силуэт или нет, например). Я продумал разные варианты поиска и отбрасывания дубликатов, и все они говорят одно - придётся подключать тормоз CPU. Но выходом может стать использование DirectCompute. Подключаем к CS буфер вершин и буфер индексов,  находим силуэтные рёбра и кладём их в append buffer. Вполне так неплохо может получиться: одно ребро - один тред, натравить целый мультипроцессор на меш, вот он нам всё быстро и посчитает.

Update

Реализовал на CPU расчёт от нескольких сэмплов и понял свою ошибку. Мы можем постараться и сделать силуэтное ребро уникальным (без дубликатов), но из него всё равно нужно вытягивать N квадов (где N - кол-во сэмплов, для которых ребро является силуэтным), при этом квады будут иметь разные направления! Другими словами, придётся рисовать теневой объём N раз.

Вот тесты для одного и четырёх сэмплов:



На этом простом примере, даже в wireframe ясно, насколько сильно разнится результат в случае протяжённого источника света, если мухлевать и считать честно. Неудивительно, что вроде бы "честный" метод penumbra wedges даёт нереалистичную пенумбру.

Надо считать честно, и это достаточно печально, т. к. по скорости не сулит ничего хорошего. Можно было бы попробовать рисовать один теневой объём (из центра и. с.), а вот пенумбру считать для всех уникальных силуэтных ребёр, но, к сожалению, расчёт пенумбры привязан к жёсткому теневому объёму. Дело в том, что само значение в пенумбре можно только отнять  или прибавить к исходному, и вот именно исходное значение и даётся жёстким теневым объёмом. В пиксельном шейдере его вычислить нет возможности, т. к. оно определяется топологией сцены.

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

Комментариев нет:

Отправить комментарий