OpenGL игровой движок

Я создаю игровой движок C ++ для развлечения и использую OpenGL для рендеринга моих вещей. Я сделал пакетный рендерер, который смог отображать 50K спрайтов с 300 FPS (нетекстурированный). Моя старая установка, которая могла работать с 300 FPS, была следующей:

У меня был класс под названием Renderable2D, который содержал все:

class Renderable2D
{
protected:
    vec3f m_Position;
    vec2f m_Size;
    vec4f m_Color;
    const Texture2D * m_Texture;
    std::vector<vec2f> m_TexCoords;

protected:
    Renderable2D() { }

public:
    Renderable2D(vec3f position, vec2f size, vec4f color)
        : m_Position(position), m_Size(size), m_Color(color), m_Texture(nullptr)
    {
        m_TexCoords.push_back(vec2f(0, 0));
        m_TexCoords.push_back(vec2f(0, 1));
        m_TexCoords.push_back(vec2f(1, 1));
        m_TexCoords.push_back(vec2f(1, 0));
    }

    virtual ~Renderable2D()
    { }

    inline virtual void Submit(Renderer2D * renderer) const
    {
        renderer->Submit(*this);
    }

    inline const vec3f& GetPosition() const { return m_Position; }
    inline const vec2f& GetSize() const { return m_Size; }
    inline const vec4f& GetColor() const { return m_Color; }
    inline const std::vector<vec2f>& GetTexCoords() const { return m_TexCoords; }

    inline const GLuint GetTextureID() const { return (m_Texture == nullptr ? 0 : m_Texture->GetTextureID()); }
};

В принципе, это был класс, в котором было все: координаты текстуры (если бы они были), цвет, положение и размер. У моего обработчика партии был метод для рисования любого Renderable2D на основе этого класса:

void BatchRenderer::Submit(const Renderable2D& renderable)
{
    const vec3f& position = renderable.GetPosition();
    const vec2f& size = renderable.GetSize();
    const unsigned int color = renderable.GetColor();
    const std::vector<vec2f>& texCoords = renderable.GetTexCoords();
    const GLuint tid = renderable.GetTextureID();

    float ts = 0.0f;
    if (tid > 0)
    {
        bool found = false;
        for (unsigned int i = 0; i < m_TextureSlots.size(); i++)
        {
            if (m_TextureSlots[i] == tid)
            {
                ts = (float)(i + 1);
                found = true;
                break;
            }
        }
        if (!found)
        {
            if (m_TextureSlots.size() >= 32)
            {
                End();
                Flush();
                Begin();
            }

        m_TextureSlots.push_back(tid);
        ts = (float)(m_TextureSlots.size());
        }
    }

    Maths::vec3f _tpos = *m_TransformationBack * position;

    m_Buffer->Position = _tpos;
    m_Buffer->TexCoord = texCoords[0];
    m_Buffer->TexID = ts;
    m_Buffer->Color = color;
    m_Buffer++;

    _tpos.y += size.y;

    m_Buffer->Position = _tpos;
    m_Buffer->TexCoord = texCoords[1];
    m_Buffer->TexID = ts;
    m_Buffer->Color = color;
    m_Buffer++;

    _tpos.x += size.x;

    m_Buffer->Position = _tpos;
    m_Buffer->TexCoord = texCoords[2];
    m_Buffer->TexID = ts;
    m_Buffer->Color = color;
    m_Buffer++;

    _tpos.y -= size.y;

    m_Buffer->Position = _tpos;
    m_Buffer->TexCoord = texCoords[3];
    m_Buffer->TexID = ts;
    m_Buffer->Color = color;
    m_Buffer++;

    m_IndexCount += 6;
}

Я хотел сделать это немного лучше и быстрее, поэтому я создал подклассы для renderable2D. Renderable2D имел только его положение и размер. Rectangle (унаследованный от Renderable2D) имел цвет тоже, и у Sprite были текстурные и текстурные коорды. Я думаю, что это немного более категоризировано и делает простой цветной прямоугольник менее тяжелым (без текстурных коордов). Затем я перепробовал метод отправки Renderable2D как для прямоугольника, так и для спрайта и создал (который, как я думал), оптимизировал методы отправки для моего визуализатор. Я сохранил исходный, но перегрузил метод, поэтому у меня было:

void BatchRenderer::Submit(const vec2f& position, const vec2f& size, unsinged int color);
void BatchRenderer::Submit(const vec2f& position, const vec2f& size, GLuint textureID, const std::vector<vec2f>& textureCoords);

Таким образом мне не нужно было назначать все для m_Buffer, и я мог бы исключить if (if (tid > 0)), потому что теперь я знал, когда renderable использовал текстуру. Я думал, что это ускорит код, но это замедлит его примерно до половины его скорости (условия тестирования были одинаковыми для неоптимизированного и «оптимизированного» кода). Почему этот рефактор (который, я думаю, должен ускорить код) замедлить его? Меньше назначений, один меньше if, меньшие структуры данных.

  • m_Buffer: на самом деле это объект буфера вершин, он называется просто буфером, потому что он не только хранит вершины, но и цвета, текстурные координаты и идентификаторы текстур. Метод Submit в основном записывает сопоставленные данные в этот буфер.

  • ts: он обозначает слот для текстур, да, это действительно плохое имя, я должен переименовать его. Это float, потому что OpenGL на самом деле любит плавать больше в буфере с плавающей точкой, чем целое число.

  • m_IndexCount: размер буфера индекса. (Индекс вершин)

  • Renderable2D: У меня есть метод Submit, который просто передает себя рендерингу. Это виртуально по назначению. Вы не отправляете спрайты непосредственно в средство визуализации, потому что есть Rendereable2D s Group s. Они переопределяют метод Submit и отправляют свои дети вместо этого.

  • m_Texture: Renderable2D устанавливает это как nullptr. Существует подкласс под названием Sprite, который в основном является конструктором, который устанавливает текстуру в переданный параметр. И мой план состоит в том, чтобы очистить Renderable2D от того, что ему не нужно, как текстуры, потому что спрайту это действительно нужно.

11 голосов | спросил CodezMe 12 J000000Sunday15 2015, 13:51:40

2 ответа


11

Несколько вещей, которые я бы сделал по-другому, плюс несколько других советов:

  • Renderable2D Кажется, я представляю один спрайт, каждый спрайт является четырехсторонним. В этом случае вам действительно не нужно переменное количество координат текстуры, а просто 4. Нет необходимости использовать std::vector в этот случай и принудительное распределение кучи. Просто используйте простой массив vec2f или std::array , если вы открыты для использования C ++ 11.

  • Вам не нужно добавлять inline, когда метод определен непосредственно внутри тела класса в файле заголовка. Это не имеет практической цели и делает код более подробным.

  • Всегда отдавайте предпочтение стилям в стиле C ++. static_cast и друзья обеспечат лучшую диагностику компилятора, если вы попытаетесь выполнить некоторые небезопасные действия.

Два бита личного стиля:

  • Это общепринятое соглашение для определения пользовательских типов с помощью PascalCase, как и вы, и переменные и функции с camelCase, чтобы различать оба типа вещей. На самом деле, идея состоит в том, чтобы назвать все, что может иметь адрес своей памяти с помощью camelCase и зарезервировать первую букву, заглавную для типов.

  • Я считаю, что лучше разместить общедоступный интерфейс класса сначала в файле заголовка, чтобы придать ему больше внимания. Это обычно та часть, которую вы и пользователи вашего кода будете смотреть чаще. защищенные и частные - это детали реализации, поэтому не стоит выделяться так много.

ответил glampert 13 J000000Monday15 2015, 00:40:45
7

Я согласен с предложениями @ glampert. У меня есть несколько моих собственных вопросов.

Нейминг

Какова цель метода Submit()? Что он представляет? (Например, я не вижу никаких вызовов в OpenGL для отправки данных атрибутов вершин.) Кроме того, он, как представляется, обновляет значение внутреннего указателя, m_Buffer. Что это за буфер? Его имя должно отражать то, что оно представляет. Это вершинные данные? Если это так, назовите его m_VertexBuffer или m_SpriteBuffer или что бы это ни было приемлемо.

Типы

В методе Submit() определена цель ts? Это float, но вы в конечном итоге назначаете mBuffer->TexId к значению ts. Но идентификаторы текстур GLuints. Кроме того, он, по-видимому, является индексом в массиве слотов текстур, а не фактическим идентификатором текстуры, который знает OpenGL. Если на самом деле это не идентификатор, возвращаемый glGenTextures() или какой-то эквивалент, я бы не назвал его TexID. Возможно, вместо этого TextureIndex? И это маловероятно float, поэтому make ts будет правильным, будь то GLuint или просто unsigned int

Повторения

У вас почти идентичный код, написанный 4 раза:

m_Buffer->Position = _tpos;
m_Buffer->TexCoord = texCoords[0];
m_Buffer->TexID = ts;
m_Buffer->Color = color;
m_Buffer++;

Почему бы не сделать это в функции и называть его 4 раза с соответствующими аргументами, а не записывать его вручную 4 раза?

Разное

Что делает последняя строка Submit()?

m_IndexCount += 6;

В приведенном выше коде добавляется 4 записи в список m_Buffers. Почему индекс увеличивается на 6? Какова его цель?

В Renderable2D у вас есть Submit(), который вы передаете Renderer2D to. Он просто вызывает renderer->Submit(*this);. Это кажется бессмысленным методом. Почему вызывающий абонент просто не звонит renderer->Submit(*renderable); и сохраняет 1 шаг?

Память

Как установить Renderable2D::m_Texture? Я вижу, как его получить, но он не устанавливается в конструкторе, это protected, и у него нет доступа для его установки. (Он также никогда не освобождается, что может быть ОК, если ему принадлежит другой объект. Если это так, вы можете использовать std::shared_ptr, если вы в C ++ 11.)

ответил user1118321 13 J000000Monday15 2015, 03:17:33

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132