Я хочу реализовать гибкую систему Ubershader с отложенным затенением. Моя текущая идея состоит в том, чтобы создавать шейдеры из модулей, которые имеют дело с определенными функциями, такими как FlatTexture, BumpTexture, Displacement Mapping и т. Д. Есть также небольшие модули, которые декодируют цвет, выполняют тональное отображение и т. Д. Это имеет то преимущество, что я могу заменить определенные типы модулей, если графический процессор не поддерживает их, поэтому я могу адаптироваться к текущим возможностям графического процессора. Я не уверен, что этот дизайн хорош. Боюсь, что сейчас я могу сделать плохой выбор дизайна, а потом заплатить за него.
Мой вопрос: где я могу найти ресурсы, примеры, статьи о том, как эффективно внедрить систему управления шейдерами? Кто-нибудь знает, как движки большой игры делают это?
источник
Ответы:
Полу распространенным подходом является создание того, что я называю компонентами шейдеров , аналогично тому, что я называю модулями.
Идея похожа на график пост-обработки. Вы пишете куски шейдерного кода, который включает в себя как необходимые входные данные, сгенерированные выходные данные, так и код, который фактически работает с ними. У вас есть список, в котором указано, какие шейдеры следует применять в любой ситуации (нужен ли этому материалу компонент рельефного отображения, включен ли компонент отложенного или прямого действия и т. Д.).
Теперь вы можете взять этот график и сгенерировать из него код шейдера. В основном это означает «вставку» кода чанков на место, с графиком, который гарантирует, что они уже находятся в нужном порядке, а затем вставку в шейдерные входы / выходы соответствующим образом (в GLSL это означает определение вашего «глобального» в , и равномерные переменные).
Это не то же самое, что подход Ubershader. Ubershaders - это место, где вы помещаете весь код, необходимый для всего, в один набор шейдеров, возможно, используя #ifdefs и uniforms и т.п., чтобы включать и выключать функции при их компиляции или запуске. Я лично презираю подход Ubershader, но некоторые довольно впечатляющие движки AAA используют их (в частности, Crytek приходит на ум).
Вы можете обрабатывать блоки шейдера несколькими способами. Самый продвинутый способ - и полезный, если вы планируете поддерживать GLSL, HLSL и консоли - это написать синтаксический анализатор для языка шейдеров (возможно, настолько близкий к HLSL / Cg или GLSL, насколько это возможно для максимальной «понятности» ваших разработчиков). ), который затем может быть использован для переводов от источника к источнику. Другой подход заключается в том, чтобы просто обернуть блоки шейдеров в файлы XML или тому подобное, например,
Обратите внимание, что при таком подходе вы можете создать несколько разделов кода для разных API или даже создать версию раздела кода (так что вы можете иметь версию GLSL 1.20 и версию GLSL 3.20). Ваш график может даже автоматически исключать фрагменты шейдера, у которых нет совместимого раздела кода, так что вы можете получить незначительную деградацию на старом оборудовании (например, обычное отображение или что-то еще, что просто исключено на старом оборудовании, которое не может его поддерживать, без необходимости программиста) сделать кучу явных проверок).
Затем образец XMl может сгенерировать что-то похожее (извините, если это недопустимый GLSL, прошло много времени с тех пор, как я подверг себя этому API):
Вы могли бы быть немного умнее и генерировать более «эффективный» код, но, честно говоря, любой шейдерный компилятор, который не является полным дерьмом, собирается удалить излишки из этого сгенерированного кода для вас. Может быть, более новый GLSL
#line
теперь позволяет вводить имя файла в команды, но я знаю, что более старые версии очень несовершенны и не поддерживают это.Если у вас есть несколько чанков, их входы (которые не предоставляются как выход чанком предка в дереве) объединяются во входной блок, как и выходы, а код просто соединяется. Проделана небольшая дополнительная работа, чтобы убедиться, что этапы совпадают (вершина против фрагмента) и что входные макеты атрибута вершины «просто работают». Еще одним приятным преимуществом этого подхода является то, что вы можете написать явные унифицированные индексы и индексы привязки входных атрибутов, которые не поддерживаются в более старых версиях GLSL, и обрабатывать их в своей библиотеке генерации / связывания шейдеров. Точно так же вы можете использовать метаданные при настройке ваших VBO и
glVertexAttribPointer
вызовов для обеспечения совместимости и того, что все «просто работает».К сожалению, такой хорошей библиотеки кросс-API уже нет. Cg довольно близок, но он поддерживает дерьмовую поддержку OpenGL на картах AMD и может быть очень медленным, если вы используете какие-либо, кроме самых основных функций генерации кода. Платформа эффектов DirectX также работает, но, конечно, не поддерживает ни один язык, кроме HLSL. Для GLSL есть несколько неполных / ошибочных библиотек, которые имитируют библиотеки DirectX, но, учитывая их состояние, когда я проверял в прошлый раз, я просто написал свою собственную.
Подход Ubershader просто означает определение «хорошо известных» директив препроцессора для определенных функций, а затем перекомпиляцию для разных материалов с различной конфигурацией. Например, для любого материала с картой нормалей, которую вы можете определить,
USE_NORMAL_MAPPING=1
а затем в своем пиксельном этапе ubershader просто:Большая проблема здесь заключается в обработке этого для предварительно скомпилированного HLSL, где вам нужно предварительно скомпилировать все используемые комбинации. Даже с GLSL вы должны иметь возможность правильно генерировать ключ всех используемых директив препроцессора, чтобы избежать перекомпиляции / кэширования идентичных шейдеров. Использование униформ может снизить сложность, но, в отличие от униформы препроцессора, не уменьшает количество команд и все же может оказывать незначительное влияние на производительность.
Просто чтобы прояснить, оба подхода (а также просто ручная запись тонны вариаций шейдеров) все используются в пространстве ААА. Используйте тот, который работает лучше для вас.
источник