Напівпоширений підхід полягає в тому, щоб зробити те, що я називаю шейдерними компонентами , аналогічно тому, що я думаю, що ви викликаєте модулі.
Ідея схожа на графік після обробки. Ви пишете фрагменти коду шейдера, який включає в себе і необхідні входи, і згенеровані виходи, і потім код, щоб фактично працювати над ними. У вас є список, який позначає, які шейдери потрібно застосовувати в будь-якій ситуації (чи потрібен цей матеріал складовою детального відображення, чи включений відкладений або перехідний компонент тощо).
Тепер ви можете взяти цей графік і генерувати з нього код шейдера. Це здебільшого означає "вставлення" коду фрагментів на місце, при цьому графік гарантував, що вони вже в необхідному порядку, а потім вставляти в шейдерні входи / виходи за необхідності (в GLSL це означає визначення вашого "глобального" в , вихідні та рівномірні змінні).
Це не те саме, що підхід підрядника. Ubershaders - це те, коли ви вставляєте весь код, необхідний для всього, в один набір шейдерів, можливо, використовуючи #ifdefs та уніформу тощо, щоб увімкнути та вимкнути функції під час їх компіляції чи запуску. Я особисто зневажаю підхід убершадера, але деякі досить вражаючі двигуни AAA використовують їх (зокрема, Crytek приходить на думку).
Можна обробляти шматки шейдерів кількома способами. Найсучасніший спосіб - і корисний, якщо ви плануєте підтримувати GLSL, HLSL та консолі - це написати аналізатор для мови шейдерів (можливо, максимально наближений до HLSL / Cg або GLSL, наскільки ви можете максимально "зрозуміти" вашими розробниками. ), які потім можуть бути використані для перекладу від джерела до джерела. Інший підхід - просто загортати шматки шейдерів у файли XML тощо, наприклад,
<shader name="example" type="pixel">
<input name="color" type="float4" source="vertex" />
<output name="color" type="float4" target="output" index="0" />
<glsl><![CDATA[
output.color = vec4(input.color.r, 0, 0, 1);
]]></glsl>
</shader>
Зверніть увагу, що при такому підході ви можете зробити декілька розділів коду для різних API чи навіть версію кодового розділу (так що у вас є версія GLSL 1.20 та версія GLSL 3.20). Ваш графік може навіть автоматично виключати шматки шейдерів, які не мають сумісного кодового розділу, так що ви можете отримати напівграційну деградацію на старішій техніці (так що на кшталт звичайного картографування чи іншого, що виключається, на старій апараті, яка не може підтримувати її без необхідності програміста зробити купу явних перевірок).
Зразок XMl може генерувати щось подібне до (вибачте, якщо це недійсне GLSL, минуло певний час, коли я піддався цьому API):
layout (location=0) in vec4 input_color;
layout (location=0) out vec4 output_color;
struct Input {
vec4 color;
};
struct Output {
vec4 color;
}
void main() {
Input input;
input.color = input_color;
Output output;
// Source: example.shader
#line 5
output.color = vec4(input.color.r, 0, 0, 1);
output_color = output.color;
}
Ви можете бути трохи розумнішими та генерувати більш "ефективний" код, але, чесно кажучи, будь-який компілятор шейдерів, що не є загальним лаєм, збирається видалити надлишки з цього створеного для вас коду. Можливо, новіший GLSL дозволяє вам також вводити ім'я файлу в #line
команди, але я знаю, що старіші версії дуже дефіцитні, і це не підтримує.
Якщо у вас є декілька фрагментів, їхні входи (які не подаються у вигляді виводу шматка предка на дереві) об'єднуються у блок введення, як і виходи, а код просто зв'язаний. Трохи додаткової роботи робиться для того, щоб забезпечити збіг етапів (вершина проти фрагмента) та макет вхідних атрибутів вершин "просто працювати". Ще одна приємна перевага при такому підході полягає в тому, що ви можете писати явні рівномірні та вхідні індекси прив'язки атрибутів, які не підтримуються в старих версіях GLSL, і обробляти їх у вашій генерації / прив'язці бібліотеки. Так само ви можете використовувати метадані для налаштування своїх VBO та glVertexAttribPointer
дзвінків, щоб забезпечити сумісність і щоб все "просто працювало".
На жаль, вже немає такої хорошої міжбібліотечної бібліотеки API. Cg наближається, але у нього є підтримка OpenGL на картах AMD і може бути надзвичайно повільною, якщо ви використовуєте будь-які, але основні функції генерації коду. Рамка ефектів DirectX також працює, але, звичайно, має нульову підтримку для будь-якої мови, крім HLSL. Існують деякі неповні / неповторні бібліотеки для GLSL, які імітують бібліотеки DirectX, але, враховуючи їх стан, востаннє, коли я перевіряв, я просто писав власну.
Підхід ubershader означає лише визначення "відомих" директив препроцесора для певних функцій, а потім перекомпіляцію для різних матеріалів з різною конфігурацією. наприклад, для будь-якого матеріалу зі звичайною картою ви можете визначити, USE_NORMAL_MAPPING=1
а потім у своєму піксельному шафі просто встановіть:
#if USE_NORMAL_MAPPING
vec4 normal;
// all your normal mapping code
#else
vec4 normal = normalize(in_normal);
#endif
Великою проблемою тут є поводження з цим для попередньо складеного HLSL, де потрібно попередньо компілювати всі комбінації, які використовуються. Навіть за допомогою GLSL вам потрібно мати можливість належним чином генерувати ключ усіх директив препроцесора, що використовуються, щоб уникнути перекомпіляції / кешування однакових шейдерів. Використання форменого одягу може зменшити складність, але на відміну від уніформи препроцесора не знижує кількість інструкцій і все ще може мати незначний вплив на продуктивність.
Щоб зрозуміти, обидва підходи (а також просто вручну написання тони варіацій шейдерів) використовуються в просторі AAA. Використовуйте те, що найкраще підходить для вас.