首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >OpenGL游戏引擎

OpenGL游戏引擎
EN

Code Review用户
提问于 2015-07-12 10:51:40
回答 2查看 492关注 0票数 12

我正在为娱乐制作一个C++游戏引擎,并使用OpenGL渲染我的东西。我制作了一个批量渲染器,它可以用300 FPS (无纹理)渲染50K的精灵。我使用300 FPS运行的旧设置如下:

我有一个名为Renderable2D的类,它包含所有内容:

代码语言:javascript
复制
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

代码语言:javascript
复制
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遗传而来)也有颜色,雪碧也有纹理和纹理。我认为这是一个更多的分类,并使一个简单的彩色矩形重量较轻(没有纹理和弦)。然后,我重写了Renderable2D的矩形和精灵的S提交方法,并为我的渲染器创建了(我认为是)优化的提交方法。我保留了原来的提交方法,但是重载了该方法,所以我有:

代码语言:javascript
复制
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)),因为现在我知道可渲染何时使用纹理。我认为这会加快代码的速度,但它将其速度降低到了大约一半(对于未优化和“优化”的代码,测试条件是一样的)。为什么这个重构(我认为应该加速代码)减慢了它的速度?少分配,少一个if语句,更小的数据结构。

  • m_Buffer:它实际上是一个顶点缓冲区对象,命名简单,因为它不仅存储顶点,而且还存储颜色、纹理坐标和纹理ID。Submit方法基本上将映射的数据写入此缓冲区。
  • ts:它代表纹理槽,是的,这是一个非常糟糕的命名,我应该重命名它。它是一个浮点数,因为OpenGL实际上更喜欢浮动缓冲区中的浮点数,而不是整数。
  • m_IndexCount:索引缓冲区的大小。(顶点索引)
  • Renderable2D:我有一个提交方法,它只是将自己传递给呈现器。是故意虚拟的。你不能直接把精灵送到渲染器上,因为有Rendereable2Ds叫Groups,他们凌驾于Submit方法之上,而是把他们的孩子送去。
  • m_TextureRenderable2D现在将其设置为nullptr。有一个名为Sprite的子类,它基本上是一个构造函数,它将纹理设置为传递的参数。我的计划是把Renderable2D从它不需要的东西中清理出来,比如纹理,因为雪碧真的需要它。
EN

回答 2

Code Review用户

发布于 2015-07-12 21:40:45

我会做一些不同的事情,再加上一些更广泛的建议:

  • 在我看来,Renderable2D代表的是一个精灵,每个精灵都是四边形的。在这种情况下,您实际上不需要可变数量的纹理坐标,而是只需要4。在这种情况下,不需要使用std::vector并强制进行堆分配。如果您开放使用vec2fstd::array,只需使用C++11的普通数组即可。
  • 当方法直接定义在头文件中的类主体中时,不需要添加inline。这不符合实际目的,只会使代码更加冗长。
  • 总是喜欢C++风格的类型。如果您尝试一些不安全的强制转换,static_cast和朋友将提供更好的编译器诊断。

两种个人风格:

  • 使用PascalCase命名用户定义的类型(就像您已经做过的那样)和用camelCase命名变量和函数是一种常见的约定,以区分这两种情况。实际上,这样做的目的是将任何可以在camelCase中获取其内存地址的名称命名,并为类型保留第一个大写字母。
  • 我发现最好先将类的公共接口放在头文件中,以便给予它更多的强调。这通常是您和您的代码用户将更经常看到的部分。受保护和私有是实现的细节,所以不需要那么突出。
票数 12
EN

Code Review用户

发布于 2015-07-13 00:17:33

我同意“魅力”的建议。我也有几个问题要问。

命名

Submit()方法的目的是什么?它提交了什么?(例如,我没有看到对OpenGL的任何调用来提交顶点属性数据。)此外,它似乎更新了内部指针m_Buffer的值。它是什么缓冲器?它的名称应反映它所代表的内容。是顶点数据吗?如果是这样的话,将其命名为m_VertexBufferm_SpriteBuffer或任何合适的名称。

类型

Submit()方法中,ts的目的是什么?它是一个float,但最终将mBuffer->TexId分配给ts的值。但是纹理ID是GLuints。而且,它似乎是纹理槽数组中的索引,而不是OpenGL所知道的实际纹理ID。如果它实际上不是glGenTextures()返回的ID或其他类似的ID,我不会将它命名为TexID。也许是TextureIndex?它不太可能是float,所以让ts成为正确的类型,不管它是GLuint还是unsigned int

重复

您有几乎相同的代码编写了4次:

代码语言:javascript
复制
m_Buffer->Position = _tpos;
m_Buffer->TexCoord = texCoords[0];
m_Buffer->TexID = ts;
m_Buffer->Color = color;
m_Buffer++;

为什么不把它变成一个函数,用适当的参数调用它4次,而不是用手写4次呢?

Miscellaneous

Submit()的最后一行是做什么的?

代码语言:javascript
复制
m_IndexCount += 6;

上面的代码似乎正在向m_Buffers列表中添加4个条目。为什么指数会增加6?它的目的是什么?

Renderable2D中,您有一个传递Renderer2DSubmit()方法。它只是简单地调用renderer->Submit(*this);。这似乎是一种毫无意义的方法。为什么调用方不直接调用renderer->Submit(*renderable);并保存1步呢?

存储器

如何设置Renderable2D::m_Texture?我知道如何获得它,但是它没有在构造函数中设置,它是protected,并且没有访问器来设置它。(它也从未被释放过,如果另一个对象拥有它,这可能是可以的。)如果是这样的话,如果您使用的是std::shared_ptr,则可以考虑使用C++11。)

票数 8
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/96659

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档