Есть куча подходов, но ни один не идеален.
Можно совместно использовать код, используя glAttachShaderдля объединения шейдеров, но это не позволяет совместно использовать такие вещи, как объявления структуры или #defineконстанты -d. Это работает для обмена функциями.
Некоторым людям нравится использовать массив строк, переданных glShaderSourceкак способ добавления общих определений перед вашим кодом, но у этого есть некоторые недостатки:
- Сложнее контролировать то, что должно быть включено из шейдера (для этого вам нужна отдельная система).
- Это означает, что автор шейдера не может указать GLSL
#versionиз-за следующего утверждения в спецификации GLSL:
#Version директива должна произойти в шейдере , прежде чем что -то еще, для комментариев и белого пространства , за исключением.
Из-за этого заявления, 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расширение, но она имеет некоторые недостатки:
- Он поддерживается только NVIDIA ( http://delphigl.de/glcapsviewer/listreports2.php?listreportsbyextension=GL_ARB_shading_language_include )
- Это работает, определяя строки включения заранее. Поэтому перед компиляцией необходимо указать, что строка
"/buffers.glsl"(как используется в #include "/buffers.glsl") соответствует содержимому файла buffer.glsl(который вы загрузили ранее).
- Как вы, возможно, заметили в пункте (2), ваши пути должны начинаться с
"/"абсолютных путей в стиле Linux. Эта нотация обычно незнакома программистам на Си и означает, что вы не можете указать относительные пути.
Обычный дизайн заключается в реализации собственного #includeмеханизма, но это может быть непросто, поскольку вам также необходимо проанализировать (и оценить) другие инструкции препроцессора, например #if, для правильной обработки условной компиляции (например, защиты заголовка).
Если вы реализуете свой собственный #include, у вас также есть некоторые свободы в том, как вы хотите его реализовать:
- Вы можете передать строки раньше времени (как
GL_ARB_shading_language_include).
- Вы можете указать обратный вызов включения (это делается библиотекой DirectX D3DCompiler).
- Вы могли бы реализовать систему, которая всегда читает непосредственно из файловой системы, как это делается в типичных приложениях Си.
В качестве упрощения вы можете автоматически вставлять элементы защиты заголовков для каждого включения в свой уровень предварительной обработки, чтобы ваш процессорный уровень выглядел следующим образом:
if (#include and not_included_yet) include_file();
(Благодарим Трента Рида за показ вышеописанной техники.)
В заключение , не существует автоматического, стандартного и простого решения. В будущем решении вы могли бы использовать некоторый интерфейс SPIR-V OpenGL, и в этом случае компилятор GLSL-SPIR-V может находиться за пределами GL API. Наличие компилятора вне среды выполнения OpenGL значительно упрощает реализацию таких вещей, как, #includeпоскольку это более подходящее место для взаимодействия с файловой системой. Я полагаю, что в настоящее время широко распространенный метод состоит в том, чтобы просто реализовать собственный препроцессор, который работает так, как должен быть знаком любой программист на Си.