Трубопровід рендерингу двигуна: загальний шейдер


10

Я намагаюся зробити 2D ігровий движок, використовуючи OpenGL ES 2.0 (iOS поки що). Я написав прикладний рівень в Objective C і окремий власний RendererGLES20 в C ++. Жоден специфічний виклик GL не проводиться за межами рендерінга. Це працює чудово.

Але у мене є деякі проблеми з дизайном при використанні шейдерів. Кожен шейдер має свої унікальні атрибути та обмундирування, які потрібно встановити безпосередньо перед основним викликом розіграшу (glDrawArrays в цьому випадку). Наприклад, для того, щоб намалювати деяку геометрію, я би зробив:

void RendererGLES20::render(Model * model)
{
    // Set a bunch of uniforms
    glUniformMatrix4fv(.......);
    // Enable specific attributes, can be many
    glEnableVertexAttribArray(......);
    // Set a bunch of vertex attribute pointers:
    glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, m->pCoords);

    // Now actually Draw the geometry
    glDrawArrays(GL_TRIANGLES, 0, m->vertexCount);

    // After drawing, disable any vertex attributes:
    glDisableVertexAttribArray(.......);
}

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

Чи є спосіб зробити об'єкт шейдера абсолютно загальним? Як, що робити, якщо я просто хочу змінити об'єкт шейдера і не турбуватися про перекомпіляцію джерела гри? Будь-який спосіб зробити рендерінга агностиком уніформи та атрибутів тощо ?. Незважаючи на те, що нам потрібно передавати дані у формі, яке найкраще місце для цього? Клас моделі? Чи відомий модельний клас щодо уніформи та атрибутів, визначених за шейдером?

Наступні шоу Клас актора:

class Actor : public ISceneNode
{
    ModelController * model;
    AIController * AI;
};

Клас модельного контролера: клас ModelController {клас IShader * шейдер; int textId; відтінок vec4; плаваюча альфа; struct Vertex * vertexArray; };

Клас шейдерів просто містить об'єкт шейдера, компілюючи та пов'язуючи підпрограми тощо.

У класі Game Logic я фактично рендерирую об'єкт:

void GameLogic::update(float dt)
{
    IRenderer * renderer = g_application->GetRenderer();

    Actor * a = GetActor(id);
    renderer->render(a->model);
}

Зауважте, що, хоча Actor розширює ISceneNode, я ще не почав впроваджувати SceneGraph. Я зроблю це, як тільки вирішу це питання.

Будь-які ідеї, як це покращити? Пов'язані моделі дизайну тощо?

Дякую, що прочитали запитання.



Не впевнений, що це точний дублікат, але я думаю, що це потрапляє до м'яса того, що ви намагаєтесь зробити.
Шон Міддлічч

Відповіді:


12

Можливо зробити вашу систему шейдерів більш керованою даними, щоб у вас не було так багато коду, що характерний для шейдера, для уніформи та вершинних форматів, а скоріше встановити їх програмно на основі метаданих, приєднаних до шейдерів.

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

Гаразд, спочатку поговоримо про вершинні формати (атрибути). Ви можете створити опис даних, створивши структуру, яка містить дані для передачі glVertexAttribPointer- індекс, тип, розмір і т.д. одного атрибута - і має масив цих структур для відображення всього формату вершин. Враховуючи цю інформацію, ви можете програмно налаштувати всі стани GL, пов'язані з атрибутами вершини.

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

Існує безліч способів зробити це; наприклад, у вас може бути стандартний набір імен для атрибутів у шейдері ("attrPosition", "attrNormal" тощо), а також деякі жорстко закодовані правила типу "позиція - 3 поплавці". Потім ви використовуєте glGetAttribLocationабо подібний для запиту, який атрибути використовує шейдер, і застосовуєте правила для побудови формату вершин. Інший спосіб - створити фрагмент XML, що визначає формат, вбудований в коментар до джерела шейдера та витягнутий вашими інструментами, або щось у цьому напрямку.

Для уніформи, якщо ви можете використовувати OpenGL 3.1 або новішої версії, добре використовувати однорідні буферні об'єкти (еквівалент OpenGL постійних буферів D3D). На жаль, у GL ES 2.0 таких немає, тому обмундирування потрібно обробляти індивідуально. Один із способів зробити це - створити структуру, що містить єдине розташування для кожного параметра, який ви хочете встановити - матрицю камери, окулярну потужність, матрицю світу тощо. Цей підхід залежить від існування стандартного набору параметрів, що ділиться на всіх шейдерах. Не кожен шейдер повинен використовувати кожен параметр, але всі параметри повинні бути в цій структурі.

Кожен шейдер мав би екземпляр цієї структури, і коли ви завантажуєте шейдер, ви запитуєте його про розташування всіх параметрів, використовуючи glGetUniformLocationі стандартизовані імена. Тоді, коли вам потрібно встановити уніформу від коду, ви можете перевірити, чи він присутній у цьому шейдері, і просто пошукати його місцезнаходження та встановити його.

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