Как стать автором
Обновить

Комментарии 18

Нетрудно видеть, что реализация алгоритма на Direct3D 11 работает быстрее, чем на OpenGL 4.3.

Скорее всего из-за того, что используется discard в шейдере.
Пробовали ли найти причину?
Я сомневаюсь, что причина кроется в использовании discard, на HLSL я, например, использую clip. На небольших-средних объемах реализация на OpenGL почти не уступает, проблемы начинаются на больших объемах. Как я писал, существенно бОльшая часть времени кадра в этом случае тратится на сортировку и смешивание фрагментов, так что у меня единственное предположение — это перегруженный фрагментный шейдер и, как следствие, низкий fillrate. Перегрузка шейдера возникает, скорее всего, из-за большого количества используемых временных регистров и обращений к storage block'у с произвольным доступом. Произвольный доступ, к слову, еще и кэш гробит. Ну, и последнее, вполне возможно, что оптимизатор компилятора HLSL чуть лучше.
А почему вы считаете, что discard может быть проблемой?
Опыт показывает, что замедление происходит в основном в 3 случаях:
1. Много переключений между буферами
2. discard
3. Неправильно построенный шейдер. К примеру в плане if-конструкций, когда много временных переменных внутри блока.

Я с Direct3D и HLSL вообще не знаком и на сколько мне подсказывает поиск, clip и discard далеко не тоже самое. Тут есть интересный ответ.
Спасибо за ссылку, действительно интересно, подумаю над этим :)
Интересная статья. Чисто с точки зрения познавательности. За это в карму плюсик.

ИМХО данный метод еще долго(может быть никогда?) не пойдет глобально в продакшн.
В повседневных геймдев задачах очень редко встает необходимость делать пересекающиеся полупрозрачные объекты.
И как правило полупрозрачные объекты имеют простые формы с минимумом пересечений — оконные стекла, посуда.
А эти ситуации вполне решаются ручной сортировкой без потери производительности.
ИМХО данный метод еще долго(может быть никогда?) не пойдет глобально в продакшн.

Тут вы ошибаетесь. Как раз работаю над продуктом, не игра, где это очень нужно. Но мы пока не используем OIT, т.к. модели очень деталезированны и имеют очень много полигонов. Не на каждом устройстве хватает производительности, а нужно поддерживать большой спектр.
НЕ игры — это другое дело.
И, наверняка, будут отдельные игры, в которых это реально будет нужно.
Ну там какая нибудь детская игра с кучей мыльных пузырей.

Я имею ввиду глобальное использование, например, как стало с бампом. Единственный шанс стать глобальным инструментом — это вынести функционал на уровень конвеера, чтобы он работал автоматически без телодвижений программиста. Потому что программистам в 99% случаев такой функционал не нужен.
Теперь вас понял. Тут-то соглашусь.
Конечно было бы круто, если бы это все автоматически происходило.
Я бы сказал что в игровой идустрии уже применяются похожие методы. Обычно используют фоллбеки — шейдеры попроще, которые заменяют более продвинутые в случае если драйвер не умеет или производительность не позволяет.
Задача блендинга кучи полупрозрачного и неотсортированного в геймдеве повсеместна: партиклы например, или волосы персонажей.

И там похожие методы используют, только хитрее кладут их на железо. Например сортируют только 4 ближайших объекта, остальное блендят как придется. При это буфер под каждый список получается фиксированного размера — меньше дорогих переходов по «указателям».
волосы очень легко сортируются с минимумом артефактов еще на момент построения модели.

по поводу системы частиц — с одной стороны согласен, с другой — эмиттеры легко сортируются, а внутри эмиттера разные типы частиц просто отображаются в правильном порядке, как правило дополнительная сортировка не нужна.
Вопрос.
У вас написано: «Выставляем задний буфер как нулевой render target, индекс 1 получит текстура головных элементов, индекс 2 – структурированный буфер элементов списков;»
Почему Backbuffer первый? Это же то, что мы выводим на экран.
Почему он не должен рендериться в последнюю очередь, по завершению всех вычислений?
Обратите внимание на дальнейшие шаги: полупрозрачная часть рисуется поверх при помощи полноэкранного квада. Поэтому в данном случае, непрозрачную часть можно рисовать в системный BB сразу.
Спасибо за быстрый ответ!
Теперь, если я правильно понял:
0-й пасс. Мы рисуем только непрозрачные объекты. В Backbuffer, формат текстуры R8G8B8A8_UNorm, флаги RenderTarget | ShaderResource. Тест глубины есть, по Z-buffer-у, все как обычно. Для чистоты эксперимента, я хочу от этого пасса отказаться.

1-й пасс. Рисуем прозрачные объекты в Unordered текстуру (или в Backbuffer?). Шейдер с флагом [earlydepthstencil]. Формат R32_Uint, флаги ShaderResource | Unordered Access. Размер по размеру экрана. Но дальше тест глубины без Z-буффера?

2-й пасс. Рисуем в структурированный буфер? (SizeInBytes = 30000000, ShaderResource | UnorderedAccess, StructureByteStride = 12, OptionFlag = BufferStructured) Заполняет его тот самый шейдер, который выполняет сортировку фрагментов insertionSort(index, sortedFragments, counter) и выводит float4(color, alpha). Оно не выводит на экран ничего, что не удивительно. Как это все дорисовать в Backbuffer?

И еще вопрос по: RWTexture2D headBuffer; каким образом шейдер знает, что нужно записывать значения именно в ту unordered текстуру, которую я ему выделил?
Смотрите, непрозрачную часть вы должны нарисовать до того, как начинаете работать с полупрозрачной, так как непрозрачная часть должна заполнить буфер глубины. Без этого полупрозрачные объекты не смогут быть перекрыты непрозрачными. earlydepthstencil отбрасывает такие фрагменты до обработки, а шейдер строит списки связности. Затем вы начинаете рисовать экранный квад, в котором читаете из текстуры, хранящей в списки связности, получаете N фрагментов, сортируете и смешиваете. Результирующий цвет рисуется прямо на BB в случае DX11.

>каким образом шейдер знает, что нужно записывать значения именно в ту unordered текстуру, которую я ему выделил?

Вы выставляете ее как render target.
OpenGL умеет производить ранний тест глубины только для фрагментных шейдеров без «побочных эффектов». К сожалению, к «побочным эффектам» относится запись в storage block.
Это решается с помощью спецификатора:
layout(early_fragment_tests) in;
Да, верное замечание, похоже, должно сработать
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории