Как я могу переключаться между 2 шейдерами, не добавляя все больше и больше вызовов отрисовки? - PullRequest
0 голосов
/ 18 октября 2018

Я хотел бы создать класс рендерера, который может переключаться между 2 или более шейдерами, не добавляя все больше и больше вызовов отрисовки.

Я имею в виду, что у нас есть 2 шейдера - A и B - и метод, который принимаетшейдер, позиция, размер, например, для создания четырехугольника.

, и я хочу добавить эти данные (положение, размер) и передать их в вершину A (так это вызовы первого рисования), а затем добавить другие данные ввершина B (так что это 2-й вызов отрисовки) и снова добавьте данные в шейдер A (так что это должно быть еще 2 вызова отрисовки, потому что мы уже использовали шейдер A где-то раньше).И, в конце, идите, хотя рисуем вызовы и рисуем сцену.

У меня есть класс RenderData, который добавляет вызовы рисования, вершины, данные элемента и т. Д.

struct DrawCall
{
    //it may have more data like texture, clip rect, camera, etc.
    Shader* shader = nullptr;
};

struct Vertex
{
    Vector2 position;
}

class RenderData
{
public:
    RenderData();
    ~RenderData();

    void Free() {           
    vertexBuffer.clear();
    shader.clear();
    drawCall.clear();
    elementBuffer.clear();
    }

    void Draw(const Rect& dest);

    void AddDrawCall();
    inline DrawCall* getDrawCall() { return drawCall.size() > 0 ? &drawCall.back() : nullptr; }

    void UpdateShader();
    void PushShader(Shader* shader);
    void PopShader();
    inline Shader* getShader() { return shader.size() > 0 ? shader.back() : nullptr; }

    uint currentVertexIndex = 0;

    vector<Vertex> vertexBuffer;   // Vertex data
    vector<Shader*> shader;
    vector<DrawCall> drawCall;
    vector<uint> elementBuffer; // Index data
}

void RenderData::AddDrawCall()
{
    DrawCall dc;
    dc.shader = getShader();
    drawCall.push_back(dc);
}

void RenderData::UpdateShader()
{
    Shader* currentShader = getShader();
    DrawCall* currentDraw = getDrawCall();
    if (!currentDraw || currentDraw->shader != currentShader) {
        AddDrawCall();
        return;
    }

    DrawCall* prevDraw = drawCall.size() > 1 ? currentDraw - 1 : nullptr;
    if (prevDraw->shader == currentShader) {
        drawCall.pop_back();
    } else { currentDraw->shader = currentShader; }
}

void RenderData::PushShader(Shader* shader)
{
    this->shader.push_back(shader);
    UpdateShader();
}

void RenderData::PopShader()
{
    Custom_Assert(shader.size() > 0, "Cannot PopShader() with size < 0!\n");
    shader.pop_back();
    UpdateShader();
}

void RenderData::Draw(const Rect& dest)
{
    //dest -> x, y, w and h
    //setup vertices 
    vertexBuffer.push_back(...);
    vertexBuffer.push_back(...);
    vertexBuffer.push_back(...);
    vertexBuffer.push_back(...);

    //setup elements
    elementBuffer.push_back(...);
    elementBuffer.push_back(...);
    elementBuffer.push_back(...);
    elementBuffer.push_back(...);
    elementBuffer.push_back(...);
    elementBuffer.push_back(...);
}

и класс Renderer2D, который имеетнесколько объектов: vao, vbo, ebo, RenderData и несколько методов:

Create() -> он создает ebo и ebo

RenderClear() -> it Free() RenderData, настраивает область просмотра

RenderPresent -> он создает и связывает vao, связывает vbo, добавляет атрибуты и данные vbo, связывает ebo и добавляет данные ebo, проходит через DrawCall& drawCall : renderData.drawCall, использует программу шейдера и рисует элементы;

void Renderer2D::Create()
{     
    //gens and binds
    vbo = vbo->Create(TYPE::ARRAY, USAGE::DYNAMIC_DRAW));
    //gens and binds
    ebo = ebo->Create(TYPE::ELEMENT, USAGE::DYNAMIC_DRAW));
}

void Renderer2D::RenderClear()
{
    setRenderViewport(0, 0, 1280, 720);
    renderData.Free();
}

    void Renderer2D::RenderPresent()
{
    vao = vao->Create();

    vbo->BindBuffer();

    vbo->AddAttribute(0, 2, GL_FLOAT, false, sizeof(Vertex), (const void*)offsetof(Vertex, position));

    vbo->AddData(renderData.vertexBuffer.size() * sizeof(Vertex), renderData.vertexBuffer.data());

    ebo->BindBuffer();
    ebo->AddData(renderData.elementBuffer.size() * sizeof(uint), renderData.elementBuffer.data());

    for (auto& drawCall : renderData.drawCall) {
        drawCall.shader->UseProgram();

        vao->DrawElements(drawCall.elemCount, GL_UNSIGNED_INT, nullptr);
    }

    //delete vertex array
    vao->Free();
}

как это работает:

int main()
{
    Renderer2D renderer2D;
    renderer2D.Create();

    Shader A("shader.vtx", "shader.frag");
    Shader B("shader.vtx", "shader2.frag");

    while(!quit) {
        renderer2D.RenderClear();

        //Push A shader = add 1st draw call
        renderer2D->PushShader(&A);
        renderer2D->Draw({ 100.0f, 100.0f, 50.0f, 50.0f });
        renderer2D->PopShader();

        //Push B shader = add 2nd draw call
        renderer2D->PushShader(&B);
        renderer2D->Draw({ 200.0f, 200.0f, 50.0f, 50.0f });
        renderer2D->PopShader();

        //Push A shader = do not add 3rd draw call, use already existing one
        //This version adds 3rd draw call instead of using existing one
        renderer2D->PushShader(&A);
        renderer2D->Draw({ 400.0f, 400.0f, 50.0f, 50.0f });
        renderer2D->PopShader();

        renderer2D.RenderPresent();
    }
    return 0;
}

Я бы хотел как-то изменить его на работу, как я описал, но я не знаю, как (если это вообще возможно) сделать это.

...