Дизайн ігрового двигуна - Ubershader - Дизайн управління шейдером [закрито]


18

Я хочу впровадити гнучку систему Ubershader з відкладеним затіненням. Моя теперішня ідея - створити шейдери з модулів, які мають певні функції, такі як FlatTexture, BumpTexture, Mapping Displacement тощо. Також є невеликі модулі, які декодують колір, роблять тональне відображення тощо. Це має перевагу, яку я можу замініть певні типи модулів, якщо GPU не підтримує їх, щоб я міг адаптуватися до поточних можливостей GPU. Я не впевнений, чи хороший цей дизайн. Я боюся, що я можу зробити поганий вибір дизайну зараз, а пізніше заплатити за це.

Моє запитання - де я можу знайти ресурси, приклади, статті про те, як ефективно впровадити систему управління шейдером? Хтось знає, як це роблять великі ігрові двигуни?


3
Недостатньо довго, щоб реально відповісти: ви добре зробите цей підхід, якщо ви почнете малим і нехай він зростає органічно, керований вашими потребами, замість того, щоб намагатися створити MegaCity-One з шейдерів вперед. По-перше, ви пом’якшуєте свої найбільші занепокоєння щодо того, що ви робите занадто багато дизайну на передній план і платите за це пізніше, якщо це не виходить, по-друге, ви уникаєте зайвої роботи, яка ніколи не звикне.
Патрік Х'юз

На жаль, ми більше не приймаємо запитання про "запит на ресурси".
Gnemlock

Відповіді:


23

Напівпоширений підхід полягає в тому, щоб зробити те, що я називаю шейдерними компонентами , аналогічно тому, що я думаю, що ви викликаєте модулі.

Ідея схожа на графік після обробки. Ви пишете фрагменти коду шейдера, який включає в себе і необхідні входи, і згенеровані виходи, і потім код, щоб фактично працювати над ними. У вас є список, який позначає, які шейдери потрібно застосовувати в будь-якій ситуації (чи потрібен цей матеріал складовою детального відображення, чи включений відкладений або перехідний компонент тощо).

Тепер ви можете взяти цей графік і генерувати з нього код шейдера. Це здебільшого означає "вставлення" коду фрагментів на місце, при цьому графік гарантував, що вони вже в необхідному порядку, а потім вставляти в шейдерні входи / виходи за необхідності (в 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. Використовуйте те, що найкраще підходить для вас.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.