Я часто сталкиваюсь с копированием кода между несколькими шейдерами. Это включает в себя как определенные вычисления или данные, используемые всеми шейдерами в одном конвейере, так и общие вычисления, которые нужны всем моим вершинным шейдерам (или любой другой стадии).
Конечно, это ужасная практика: если мне нужно где-то изменить код, я должен убедиться, что я изменю его везде.
Есть ли лучшая практика для сохранения СУХОГО ? Люди просто добавляют один общий файл ко всем своим шейдерам? Они пишут свой собственный элементарный препроцессор в стиле C, который анализирует #include
директивы? Если в отрасли есть общепринятые модели, я бы хотел следовать им.
Ответы:
Есть куча подходов, но ни один не идеален.
Можно совместно использовать код, используя
glAttachShader
для объединения шейдеров, но это не позволяет совместно использовать такие вещи, как объявления структуры или#define
константы -d. Это работает для обмена функциями.Некоторым людям нравится использовать массив строк, переданных
glShaderSource
как способ добавления общих определений перед вашим кодом, но у этого есть некоторые недостатки:#version
из-за следующего утверждения в спецификации GLSL:Из-за этого заявления,
glShaderSource
не может использоваться для добавления текста перед#version
объявлениями. Это означает, что#version
в вашиglShaderSource
аргументы должна быть включена строка , что означает, что интерфейсу компилятора GLSL нужно как-то сказать, какую версию GLSL предполагается использовать. Кроме того, отсутствие указания#version
сделает компилятор GLSL по умолчанию для использования GLSL версии 1.10. Если вы хотите, чтобы авторы шейдеров указывали#version
внутри скрипта стандартным способом, то вам нужно как-то вставить#include
-s после#version
оператора. Это можно сделать, явно проанализировав шейдер GLSL, чтобы найти#version
строку (если она есть) и сделать ваши включения после нее, но имея доступ к#include
Директива может быть предпочтительнее контролировать легче, когда эти включения должны быть сделаны. С другой стороны, поскольку GLSL игнорирует комментарии перед#version
строкой, вы можете добавить метаданные для включений в комментарии в верхней части вашего файла (черт.)Теперь возникает вопрос: есть ли стандартное решение для
#include
или вам нужно накатить собственное расширение препроцессора?Существует
GL_ARB_shading_language_include
расширение, но она имеет некоторые недостатки:"/buffers.glsl"
(как используется в#include "/buffers.glsl"
) соответствует содержимому файлаbuffer.glsl
(который вы загрузили ранее)."/"
абсолютных путей в стиле Linux. Эта нотация обычно незнакома программистам на Си и означает, что вы не можете указать относительные пути.Обычный дизайн заключается в реализации собственного
#include
механизма, но это может быть непросто, поскольку вам также необходимо проанализировать (и оценить) другие инструкции препроцессора, например#if
, для правильной обработки условной компиляции (например, защиты заголовка).Если вы реализуете свой собственный
#include
, у вас также есть некоторые свободы в том, как вы хотите его реализовать:GL_ARB_shading_language_include
).В качестве упрощения вы можете автоматически вставлять элементы защиты заголовков для каждого включения в свой уровень предварительной обработки, чтобы ваш процессорный уровень выглядел следующим образом:
(Благодарим Трента Рида за показ вышеописанной техники.)
В заключение , не существует автоматического, стандартного и простого решения. В будущем решении вы могли бы использовать некоторый интерфейс SPIR-V OpenGL, и в этом случае компилятор GLSL-SPIR-V может находиться за пределами GL API. Наличие компилятора вне среды выполнения OpenGL значительно упрощает реализацию таких вещей, как,
#include
поскольку это более подходящее место для взаимодействия с файловой системой. Я полагаю, что в настоящее время широко распространенный метод состоит в том, чтобы просто реализовать собственный препроцессор, который работает так, как должен быть знаком любой программист на Си.источник
Обычно я просто использую тот факт, что glShaderSource (...) принимает массив строк в качестве входных данных.
Я использую основанный на json файл определения шейдера, который определяет, как составляется шейдер (или программа, если быть более точным), и там я указываю, что препроцессор определяет, какие мне могут понадобиться, формы, которые он использует, файл шейдеров вершин / фрагментов, и все дополнительные файлы "зависимости". Это просто наборы функций, которые добавляются к источнику перед фактическим источником шейдера.
Просто добавьте, AFAIK, что Unreal Engine 4 использует директиву #include, которая анализируется и добавляет все соответствующие файлы перед компиляцией, как вы и предлагали.
источник
Я не думаю, что существует общее соглашение, но если бы я предположил, я бы сказал, что почти каждый реализует некоторую простую форму текстового включения в качестве шага предварительной обработки (
#include
расширение), потому что это очень легко сделать так. (В JavaScript / WebGL вы можете сделать это, например, с помощью простого регулярного выражения). Преимущество этого состоит в том, что вы можете выполнять предварительную обработку в автономном режиме для «релизных» сборок, когда больше не нужно менять код шейдера.На самом деле, признак того, что этот подход является общим является тот факт , что расширение АРБ было введено для этого:
GL_ARB_shading_language_include
. Я не уверен, стало ли это основной функцией на данный момент или нет, но расширение было написано для OpenGL 3.2.источник
Некоторые люди уже указали, что
glShaderSource
могут принимать массив строк.Кроме того, в GLSL компиляция (
glShaderSource
,glCompileShader
) и компоновка (glAttachShader
,glLinkProgram
) шейдера разделены.Я использовал это в некоторых проектах для разделения шейдеров между определенной частью и частями, общими для большинства шейдеров, которые затем компилируются и передаются всем программам шейдеров. Это работает и не сложно реализовать: вам просто нужно поддерживать список зависимостей.
Что касается ремонтопригодности, я не уверен, что это победа. Наблюдение было то же самое, давайте попробуем факторизовать. Хотя это действительно позволяет избежать повторений, накладные расходы на технику кажутся значительными. Более того, последний шейдер сложнее извлечь: вы не можете просто объединить источники шейдеров, так как объявления заканчиваются в порядке, который некоторые компиляторы отклонят или будут дублированы. Таким образом, это затрудняет проведение быстрого теста шейдера в отдельном инструменте.
В конце концов, эта техника решает некоторые проблемы сушки, но она далека от идеала.
Что касается побочной темы, я не уверен, оказывает ли этот подход какое-либо влияние с точки зрения времени компиляции; Я читал, что некоторые драйверы действительно компилируют шейдерную программу только при линковке, но я не измерял.
источник