在完成了大部分关于https://learnopengl.com/的教程之后,我开始将代码封装到它们各自的类中,并开始编写一个网格类和一个多边形类,作为我的第一个原始几何对象来计算代码的结构。关于我将在结尾添加的代码有几个问题,除了这些问题,我还在寻找可能的改进。最后,我想得到一个2D精灵类和一个tilemap类,以便高效地绘制更大的地图。目前,所有的代码都是内联在头文件,我最终会把它分成翻译单位,一旦我相信我的设计。
/// Describes a custom vertex format structure that contains position, color,
/// and one set of texture coordinates.
struct FVertex final {
/// XYZ position.
glm::vec3 Position;
/// The vertex color.
glm::vec3 Color;
/// UV texture coordinates.
glm::vec2 TextureCoordinate;
};/// A mesh is a collection of vertices that define the geometry of an object.
class FMesh final {
public:
/// Creates a new FMesh.
explicit FMesh() { initialize(); };
/// Default destructor.
~FMesh() {
if (!IsInitialized) {
return;
}
glDeleteVertexArrays(1, &VertexArrayHandle);
glDeleteBuffers(1, &VertexBufferHandle);
IsInitialized = false;
}
/// Deleted copy-constructor.
FMesh(const FMesh &) = delete;
/// Deleted move-constructor.
FMesh(FMesh &&) = delete;
/// Deleted copy-assignment operator.
auto operator=(const FMesh &) -> FMesh & = delete;
/// Deleted move-assignment operator.
auto operator=(FMesh &&) -> FMesh & = delete;
/// Draws all vertices of this FMesh.
/// @param Mode Specifies what kind of primitives to render. Can be one of @p
/// GL_POINTS, @p GL_LINES, @p GL_LINE_STRIP, @p GL_LINE_LOOP, @p
/// GL_TRIANGLES, @p GL_TRIANGLE_STRIP or @p GL_TRIANGLE_FAN.
auto draw(const GLenum Mode) const -> void {
glBindVertexArray(VertexArrayHandle);
glDrawArrays(Mode, 0, static_cast<GLint>(Vertices.size()));
}
/// Sets the vertex with the specified index of this FMesh.
/// @param Index Index of the vertex to set.
/// @param Vertex Vertex data to store at the specified index.
/// @remark Before using this method you must call setVertexCount() first to
/// initialize this FMesh.
auto setVertex(const std::size_t Index, const FVertex &Vertex) -> void {
assert(Vertices.size() >= 2 &&
"The FMesh hasn't been initialized with setVertexCount.");
Vertices.at(Index) = Vertex;
update();
}
/// Sets the number of vertices of this FMesh.
/// @param Count Number of vertices of this FMesh.
auto setVertexCount(const std::size_t Count) -> void {
assert(Count >= 2 && "The FMesh must have at least two vertices.");
Vertices.resize(Count);
update();
}
private:
/// Initializes the OpenGL objects required for this FMesh.
auto initialize() -> void {
if (IsInitialized) {
return;
}
glGenVertexArrays(1, &VertexArrayHandle);
glGenBuffers(1, &VertexBufferHandle);
glBindVertexArray(VertexArrayHandle);
glBindBuffer(GL_ARRAY_BUFFER, VertexBufferHandle);
glBufferData(GL_ARRAY_BUFFER, Vertices.size() * sizeof(FVertex),
Vertices.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, decltype(FVertex::Position)::length(), GL_FLOAT,
GL_FALSE, sizeof(FVertex),
getOffsetPtrOf(&FVertex::Position));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, decltype(FVertex::Color)::length(), GL_FLOAT,
GL_FALSE, sizeof(FVertex),
getOffsetPtrOf(&FVertex::Color));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, decltype(FVertex::TextureCoordinate)::length(),
GL_FLOAT, GL_FALSE, sizeof(FVertex),
getOffsetPtrOf(&FVertex::TextureCoordinate));
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
IsInitialized = true;
}
/// Recompute the internal geometry of the FMesh. This function must be called
/// every time the vertices change.
auto update() const -> void {
assert(IsInitialized == true &&
"Cannot update FMesh that is not initialized.");
glBindBuffer(GL_ARRAY_BUFFER, VertexBufferHandle);
glBufferData(GL_ARRAY_BUFFER, Vertices.size() * sizeof(FVertex),
Vertices.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
/// Boolean value indicating whether this FMesh has been intialized.
bool IsInitialized;
/// OpenGL handle to the vertex buffer object of this FMesh.
FHandle VertexBufferHandle;
/// OpenGL handle to the vertex array object of this FMesh.
FHandle VertexArrayHandle;
/// List of FVertex objects describing the geometry of this FMesh.
std::vector<FVertex> Vertices;
};/// A primitive shape with a finite number of straight line segments that form a
/// closed chain.
class FPolygon final {
public:
/// Creates a new FPolygon.
explicit FPolygon() = default;
/// Default destructor.
~FPolygon() = default;
/// Deleted copy-constructor.
FPolygon(const FPolygon &) = delete;
/// Deleted move-constructor.
FPolygon(FPolygon &&) = delete;
/// Deleted copy-assignment operator.
auto operator=(const FPolygon &) -> FPolygon & = delete;
/// Deleted move-assignment operator.
auto operator=(FPolygon &&) -> FPolygon & = delete;
/// Draws this FPolygon.
auto draw() const -> void { Mesh.draw(GL_TRIANGLE_FAN); }
/// Gets a reference to the underlying FMesh of this FPolygon.
/// @return Reference to the underlying FMesh of this FPolygon.
[[nodiscard]] auto getMesh() -> FMesh & { return Mesh; }
private:
/// FMesh describing the geometry of this FPolygon.
FMesh Mesh;
};FPolygon类的使用和初始化现在如下所示:
Polygon.getMesh().setVertexCount(3);
Polygon.getMesh().setVertex(
0, FVertex{glm::vec3{0, 0.5, 0}, glm::vec3{1, 0, 0}, glm::vec2{1}});
Polygon.getMesh().setVertex(
1, FVertex{glm::vec3{0.5, -0.5, 0}, glm::vec3{0, 1, 0}, glm::vec2{1}});
Polygon.getMesh().setVertex(
2, FVertex{glm::vec3{-0.5, -0.5, 0}, glm::vec3{0, 0, 1}, glm::vec2{1}});FVertex结构已经有当前未使用的纹理坐标,稍后我将添加处理。FVertex的数据布局在它们之间是相同的。使用共享顶点数组对象是常见的,还是复制它们的开销可以忽略不计?FMesh继承还是将它作为成员变量添加比较好。对于这个决定,我有什么要考虑的吗?发布于 2021-04-16 17:48:13
现在,所有网格都有自己的顶点数组对象,尽管基于FVertex的数据布局在它们之间是相同的。使用共享顶点数组对象是常见的,还是复制它们的开销可以忽略不计?
如果网格表示的只是一个简单的瓷砖,其几何形状总是相同的,那么是的,使用多个VBOS是个坏主意。通常,使用openGL,glX调用越少越好。您应该使用单个VBO并使用实例渲染,其中基本上告诉openGL多次绘制相同的内容,但使用不同的输入参数,在您的示例中,偏移量或2D转换和纹理偏移量指向正确的瓷砖纹理,这是较大纹理的一部分。
但我不清楚Mesh类的用途是什么,它是任何网格的数据的通用容器,还是特定于瓦状渲染?
目前,您有一个只能有一种顶点的网格,并且只能用这3个属性绘制,所以它非常适合您的问题。无法将其扩展到,例如,渲染具有2组纹理坐标、法线或其他任何内容的东西。顶点类也有颜色和纹理坐标,这没有多大意义,除非你想用打开的GL执行一些奇怪的效果,你似乎也有3D坐标,但你也想用它渲染2D精灵。如果您希望能够扩展和重用网格类,那么它必须是更通用的。如果需要的话,您可能希望使用Vertex类或者直接使用Mesh类来描述顶点的格式,而不是包含数据本身。例如,它可以包含一个Attribute列表,该列表描述不同类型的属性及其属性(大步、大小、类型等)把数据存储在其他地方。然后,您可以遍历顶点描述符并相应地将数据加载到OGL中。
您应该分离OpenGL特定的代码和网格数据。理想情况下,Mesh类不应该依赖于OpenGL。
有一个“可渲染对象”类包含一个网格和描述额外的信息(例如,在世界上的位置(变换),纹理,着色器和其他“每个对象”属性,以便你可以循环相同的网格与不同的位置和纹理。通常,像这样的对象包含一个子程序列表,其中的位置相对于父对象的转换。
让另一个模块/类处理openGL的细节。它应该获取对象的列表,并以最好的方式绘制它们。例如,所有共享相同网格和纹理且仅通过位置不同的对象都应该作为实例一起呈现。渲染器本身应该只加载到OpenGL中,每个vbo的一个副本和每个纹理的一个副本。构建您需要的任何数据结构来有效地完成这一任务。
Polygon类似乎没有必要,它的目的是什么?它只指定要用GL_TRIANGLE_FAN绘制该网格,但该信息应该已经在网格本身中。
https://codereview.stackexchange.com/questions/259542
复制相似问题