我正在为娱乐制作一个C++游戏引擎,并使用OpenGL渲染我的东西。我制作了一个批量渲染器,它可以用300 FPS (无纹理)渲染50K的精灵。我使用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遗传而来)也有颜色,雪碧也有纹理和纹理。我认为这是一个更多的分类,并使一个简单的彩色矩形重量较轻(没有纹理和弦)。然后,我重写了Renderable2D的矩形和精灵的S提交方法,并为我的渲染器创建了(我认为是)优化的提交方法。我保留了原来的提交方法,但是重载了该方法,所以我有:
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_Texture:Renderable2D现在将其设置为nullptr。有一个名为Sprite的子类,它基本上是一个构造函数,它将纹理设置为传递的参数。我的计划是把Renderable2D从它不需要的东西中清理出来,比如纹理,因为雪碧真的需要它。发布于 2015-07-12 21:40:45
我会做一些不同的事情,再加上一些更广泛的建议:
Renderable2D代表的是一个精灵,每个精灵都是四边形的。在这种情况下,您实际上不需要可变数量的纹理坐标,而是只需要4。在这种情况下,不需要使用std::vector并强制进行堆分配。如果您开放使用vec2f或std::array,只需使用C++11的普通数组即可。inline。这不符合实际目的,只会使代码更加冗长。static_cast和朋友将提供更好的编译器诊断。两种个人风格:
PascalCase命名用户定义的类型(就像您已经做过的那样)和用camelCase命名变量和函数是一种常见的约定,以区分这两种情况。实际上,这样做的目的是将任何可以在camelCase中获取其内存地址的名称命名,并为类型保留第一个大写字母。发布于 2015-07-13 00:17:33
我同意“魅力”的建议。我也有几个问题要问。
Submit()方法的目的是什么?它提交了什么?(例如,我没有看到对OpenGL的任何调用来提交顶点属性数据。)此外,它似乎更新了内部指针m_Buffer的值。它是什么缓冲器?它的名称应反映它所代表的内容。是顶点数据吗?如果是这样的话,将其命名为m_VertexBuffer或m_SpriteBuffer或任何合适的名称。
在Submit()方法中,ts的目的是什么?它是一个float,但最终将mBuffer->TexId分配给ts的值。但是纹理ID是GLuints。而且,它似乎是纹理槽数组中的索引,而不是OpenGL所知道的实际纹理ID。如果它实际上不是glGenTextures()返回的ID或其他类似的ID,我不会将它命名为TexID。也许是TextureIndex?它不太可能是float,所以让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;上面的代码似乎正在向m_Buffers列表中添加4个条目。为什么指数会增加6?它的目的是什么?
在Renderable2D中,您有一个传递Renderer2D的Submit()方法。它只是简单地调用renderer->Submit(*this);。这似乎是一种毫无意义的方法。为什么调用方不直接调用renderer->Submit(*renderable);并保存1步呢?
如何设置Renderable2D::m_Texture?我知道如何获得它,但是它没有在构造函数中设置,它是protected,并且没有访问器来设置它。(它也从未被释放过,如果另一个对象拥有它,这可能是可以的。)如果是这样的话,如果您使用的是std::shared_ptr,则可以考虑使用C++11。)
https://codereview.stackexchange.com/questions/96659
复制相似问题