现代 OpenGL 中的球体网格
Sphere mesh in modern OpenGL
我正在尝试创建球面的三角形网格并使用 OpenGL 4.1 绘制它。
这是我目前使用的代码,从 question 的第二个答案中获得,顶点布局是 [x, y, z, r, b, g, a] 这就是为什么每个顶点有 7 个浮点数:
std::vector<float> vertices;
std::vector<unsigned int> indices;
const float dLambda = 2 * glm::pi<float>() / meridianNumber;
const float dPhi = glm::pi<float>() / parallelNumber;
unsigned int lastVertex = 0;
for (int i = 0; i < parallelNumber; ++i) {
for (int j = 0; j < meridianNumber; ++j) {
std::cout << "lot: " << glm::degrees(j * dLambda);
std::cout << "\tlat: " << glm::degrees(i * dPhi);
std::cout << std::endl;
float lambda1 = j * dLambda;
float phi1 = i * dPhi;
float lambda2 = j+1 == parallelNumber ? 2 * glm::pi<float>()
: (j+1) * dLambda;
float phi2 = i+1 == meridianNumber ? glm::pi<float>()
: (i+1) * dPhi;
// vertex 1
vertices.emplace_back(cosf(lambda1) * sinf(phi1) * radius);
vertices.emplace_back(cosf(phi1) * radius);
vertices.emplace_back(sinf(lambda1) * sinf(phi1) * radius);
vertices.emplace_back(0.5f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
// vertex 2
vertices.emplace_back(cosf(lambda1) * sinf(phi2) * radius);
vertices.emplace_back(cosf(phi2) * radius);
vertices.emplace_back(sinf(lambda1) * sinf(phi2) * radius);
vertices.emplace_back(0.5f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
// vertex 3
vertices.emplace_back(cosf(lambda2) * sinf(phi1) * radius);
vertices.emplace_back(cosf(phi1) * radius);
vertices.emplace_back(sinf(lambda2) * sinf(phi1) * radius);
vertices.emplace_back(0.5f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
// vertex 4
vertices.emplace_back(cosf(lambda2) * sinf(phi2) * radius);
vertices.emplace_back(cosf(phi2) * radius);
vertices.emplace_back(sinf(lambda2) * sinf(phi2) * radius);
vertices.emplace_back(0.5f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
indices.emplace_back(lastVertex);
indices.emplace_back(lastVertex+1);
indices.emplace_back(lastVertex+2);
indices.emplace_back(lastVertex+1);
indices.emplace_back(lastVertex+3);
indices.emplace_back(lastVertex+2);
lastVertex += 4;
}
但我做错了什么,因为那是我画的:
我用来绘制的代码是:
GLCall(glDrawElements(
GL_TRIANGLES,
indicesNumber,
GL_UNSIGNED_INT,
(const void*) 0
));
编辑 1:
VAO 设置非常复杂,因为我在 opengl 上写了一点抽象层......
我有一个名为 VertexBuffer 的 class,它创建、保持活动并销毁 OpenGL 数组缓冲区。
另一个class IndexBuffer 和前面那个管理Element数组缓冲区的非常相似。
这两个 classes 使用起来非常简单,它们可以构造、绑定、解除绑定和销毁,仅此而已。
第三个 class 表示 OpenGL 顶点缓冲区中单个顶点的布局;这个 class 称为 VertexLayout 包含调用 glVertexAttribPointer 所需的所有数据。
hpp:
class VertexLayout {
private:
struct Element {
unsigned int type;
unsigned int count;
unsigned char normalized;
size_t typeSize;
Element(
unsigned int type, unsigned int count, unsigned char normalized,
size_t typeSize
);
};
std::vector<Element> elements;
unsigned int stride;
public:
VertexLayout();
template<typename T>
VertexLayout &push(unsigned int count, unsigned char normalized = GL_FALSE){
std::fputs(
"this function has to be implemented for desired type",
stderr
);
assert(false);
return *this;
}
const std::vector<Element> &getElements() const;
unsigned int getStride() const;
};
cpp:
template<>
VertexLayout &VertexLayout::push<unsigned int>(
unsigned int count, unsigned char normalized
) {
elements.emplace_back(
GL_UNSIGNED_INT, count, normalized, sizeof(unsigned int)
);
stride += count * sizeof(unsigned int);
return *this;
};
template<>
VertexLayout &VertexLayout::push<unsigned char>(
unsigned int count, unsigned char normalized
) {
elements.emplace_back(
GL_UNSIGNED_BYTE, count, normalized, sizeof(unsigned char)
);
stride += count * sizeof(unsigned char);
return *this;
};
template<>
VertexLayout &VertexLayout::push<float>(unsigned int count, unsigned char normalized){
elements.emplace_back(GL_FLOAT, count, normalized, sizeof(float));
stride += count * sizeof(float);
return *this;
}
VertexLayout::Element::Element(
unsigned int type, unsigned int count,
unsigned char normalized, size_t typeSize
) : type(type), count(count), normalized(normalized), typeSize(typeSize) {}
const std::vector<VertexLayout::Element> &VertexLayout::getElements() const {
return elements;
}
unsigned int VertexLayout::getStride() const {
return stride;
}
VertexLayout::VertexLayout() : stride(0) {}
因此应该为每个 VertexBuffer 对象创建一个 VertexLayout 实例,并且应该为每个 opengl 属性创建一个 push<type>(numberOfElementOfThatType)
。
第四个也是最后一个 class 是表示 VAO 的 VertexArray class:最后一个 class 跟踪所有连接到 vao 的 VertexBuffer 和 IndexBuffer 对象,使用以下方法在添加 VertexBuffer 时设置调用 glVertexAttribPointer 的布局:
void VertexArray::addBuffer(
const VertexBuffer &buffer, const VertexLayout &layout
) {
GLCall(glBindVertexArray(id));
buffer.bind();
const auto &elements = layout.getElements();
size_t offset = 0;
for (unsigned int i = 0; i < elements.size(); ++i) {
const auto &element = elements[i];
GLCall(glEnableVertexAttribArray(i));
GLCall(glVertexAttribPointer(
i, element.count, element.type, element.normalized,
layout.getStride(), (const void *)offset
));
offset += element.count * element.typeSize;
}
vertexBuffers.emplace_back(buffer);
}
GLCall 是一个宏,在发布时不执行任何操作,而在调试时清除 OpenGL 错误并打印新错误。
编辑 2:
这是代表一个 VBO 的 class VertexBuffer:
hpp
class VertexBuffer {
private: // static
static std::map<unsigned int, unsigned int> references;
private: // member
unsigned int rendererID;
public:
VertexBuffer();
VertexBuffer(
const void *data, unsigned long size,
unsigned int usage = GL_STATIC_DRAW
);
VertexBuffer(const VertexBuffer &oth);
VertexBuffer &operator=(const VertexBuffer &rhs);
~VertexBuffer();
void bind() const;
void unbind() const;
};
cpp:
std::map<unsigned int, unsigned int> VertexBuffer::references;
VertexBuffer::VertexBuffer(
const void *data,
unsigned long size,
unsigned int usage
) {
GLCall(glGenBuffers(1, &rendererID));
GLCall(glBindBuffer(GL_ARRAY_BUFFER, rendererID));
GLCall(glBufferData(GL_ARRAY_BUFFER, size, data, usage));
references.insert_or_assign(rendererID, 1);
}
VertexBuffer::VertexBuffer(const VertexBuffer &oth) {
if (oth.rendererID != 0){
auto ref = references.find(oth.rendererID);
assert(ref != references.end());
ref->second++;
}
rendererID = oth.rendererID;
}
VertexBuffer &VertexBuffer::operator=(const VertexBuffer &rhs) {
if (rendererID != 0) {
auto refs = references.find(rendererID);
assert(refs != references.end());
if (--refs->second == 0) {
GLCall(glDeleteBuffers(1, &rendererID));
references.erase(refs);
}
}
if (rhs.rendererID != 0){
auto ref = references.find(rhs.rendererID);
assert(ref != references.end());
ref->second++;
}
rendererID = rhs.rendererID;
return *this;
}
VertexBuffer::VertexBuffer() : rendererID(0) {}
VertexBuffer::~VertexBuffer() {
if (rendererID != 0) {
auto ref = references.find(rendererID);
assert(ref != references.end());
if (--ref->second == 0) {
GLCall(glDeleteBuffers(1, &rendererID));
references.erase(ref);
}
}
}
void VertexBuffer::bind() const {
GLCall(glBindBuffer(GL_ARRAY_BUFFER, rendererID));
}
void VertexBuffer::unbind() const {
GLCall(glBindBuffer(GL_ARRAY_BUFFER, 0));
}
在球体中,我只有一个包含位置和颜色的大缓冲区。
我找到了解决方案。这是一个非常愚蠢的错误:VertexBuffer class 的构造函数需要以字节为单位的缓冲区大小,但是当我调用它时,我只传递了 std::vector 的大小,即元素的数量。
我正在尝试创建球面的三角形网格并使用 OpenGL 4.1 绘制它。
这是我目前使用的代码,从 question 的第二个答案中获得,顶点布局是 [x, y, z, r, b, g, a] 这就是为什么每个顶点有 7 个浮点数:
std::vector<float> vertices;
std::vector<unsigned int> indices;
const float dLambda = 2 * glm::pi<float>() / meridianNumber;
const float dPhi = glm::pi<float>() / parallelNumber;
unsigned int lastVertex = 0;
for (int i = 0; i < parallelNumber; ++i) {
for (int j = 0; j < meridianNumber; ++j) {
std::cout << "lot: " << glm::degrees(j * dLambda);
std::cout << "\tlat: " << glm::degrees(i * dPhi);
std::cout << std::endl;
float lambda1 = j * dLambda;
float phi1 = i * dPhi;
float lambda2 = j+1 == parallelNumber ? 2 * glm::pi<float>()
: (j+1) * dLambda;
float phi2 = i+1 == meridianNumber ? glm::pi<float>()
: (i+1) * dPhi;
// vertex 1
vertices.emplace_back(cosf(lambda1) * sinf(phi1) * radius);
vertices.emplace_back(cosf(phi1) * radius);
vertices.emplace_back(sinf(lambda1) * sinf(phi1) * radius);
vertices.emplace_back(0.5f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
// vertex 2
vertices.emplace_back(cosf(lambda1) * sinf(phi2) * radius);
vertices.emplace_back(cosf(phi2) * radius);
vertices.emplace_back(sinf(lambda1) * sinf(phi2) * radius);
vertices.emplace_back(0.5f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
// vertex 3
vertices.emplace_back(cosf(lambda2) * sinf(phi1) * radius);
vertices.emplace_back(cosf(phi1) * radius);
vertices.emplace_back(sinf(lambda2) * sinf(phi1) * radius);
vertices.emplace_back(0.5f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
// vertex 4
vertices.emplace_back(cosf(lambda2) * sinf(phi2) * radius);
vertices.emplace_back(cosf(phi2) * radius);
vertices.emplace_back(sinf(lambda2) * sinf(phi2) * radius);
vertices.emplace_back(0.5f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
vertices.emplace_back(1.0f);
indices.emplace_back(lastVertex);
indices.emplace_back(lastVertex+1);
indices.emplace_back(lastVertex+2);
indices.emplace_back(lastVertex+1);
indices.emplace_back(lastVertex+3);
indices.emplace_back(lastVertex+2);
lastVertex += 4;
}
但我做错了什么,因为那是我画的:
我用来绘制的代码是:
GLCall(glDrawElements(
GL_TRIANGLES,
indicesNumber,
GL_UNSIGNED_INT,
(const void*) 0
));
编辑 1: VAO 设置非常复杂,因为我在 opengl 上写了一点抽象层...... 我有一个名为 VertexBuffer 的 class,它创建、保持活动并销毁 OpenGL 数组缓冲区。 另一个class IndexBuffer 和前面那个管理Element数组缓冲区的非常相似。 这两个 classes 使用起来非常简单,它们可以构造、绑定、解除绑定和销毁,仅此而已。 第三个 class 表示 OpenGL 顶点缓冲区中单个顶点的布局;这个 class 称为 VertexLayout 包含调用 glVertexAttribPointer 所需的所有数据。
hpp:
class VertexLayout {
private:
struct Element {
unsigned int type;
unsigned int count;
unsigned char normalized;
size_t typeSize;
Element(
unsigned int type, unsigned int count, unsigned char normalized,
size_t typeSize
);
};
std::vector<Element> elements;
unsigned int stride;
public:
VertexLayout();
template<typename T>
VertexLayout &push(unsigned int count, unsigned char normalized = GL_FALSE){
std::fputs(
"this function has to be implemented for desired type",
stderr
);
assert(false);
return *this;
}
const std::vector<Element> &getElements() const;
unsigned int getStride() const;
};
cpp:
template<>
VertexLayout &VertexLayout::push<unsigned int>(
unsigned int count, unsigned char normalized
) {
elements.emplace_back(
GL_UNSIGNED_INT, count, normalized, sizeof(unsigned int)
);
stride += count * sizeof(unsigned int);
return *this;
};
template<>
VertexLayout &VertexLayout::push<unsigned char>(
unsigned int count, unsigned char normalized
) {
elements.emplace_back(
GL_UNSIGNED_BYTE, count, normalized, sizeof(unsigned char)
);
stride += count * sizeof(unsigned char);
return *this;
};
template<>
VertexLayout &VertexLayout::push<float>(unsigned int count, unsigned char normalized){
elements.emplace_back(GL_FLOAT, count, normalized, sizeof(float));
stride += count * sizeof(float);
return *this;
}
VertexLayout::Element::Element(
unsigned int type, unsigned int count,
unsigned char normalized, size_t typeSize
) : type(type), count(count), normalized(normalized), typeSize(typeSize) {}
const std::vector<VertexLayout::Element> &VertexLayout::getElements() const {
return elements;
}
unsigned int VertexLayout::getStride() const {
return stride;
}
VertexLayout::VertexLayout() : stride(0) {}
因此应该为每个 VertexBuffer 对象创建一个 VertexLayout 实例,并且应该为每个 opengl 属性创建一个 push<type>(numberOfElementOfThatType)
。
第四个也是最后一个 class 是表示 VAO 的 VertexArray class:最后一个 class 跟踪所有连接到 vao 的 VertexBuffer 和 IndexBuffer 对象,使用以下方法在添加 VertexBuffer 时设置调用 glVertexAttribPointer 的布局:
void VertexArray::addBuffer(
const VertexBuffer &buffer, const VertexLayout &layout
) {
GLCall(glBindVertexArray(id));
buffer.bind();
const auto &elements = layout.getElements();
size_t offset = 0;
for (unsigned int i = 0; i < elements.size(); ++i) {
const auto &element = elements[i];
GLCall(glEnableVertexAttribArray(i));
GLCall(glVertexAttribPointer(
i, element.count, element.type, element.normalized,
layout.getStride(), (const void *)offset
));
offset += element.count * element.typeSize;
}
vertexBuffers.emplace_back(buffer);
}
GLCall 是一个宏,在发布时不执行任何操作,而在调试时清除 OpenGL 错误并打印新错误。
编辑 2: 这是代表一个 VBO 的 class VertexBuffer:
hpp
class VertexBuffer {
private: // static
static std::map<unsigned int, unsigned int> references;
private: // member
unsigned int rendererID;
public:
VertexBuffer();
VertexBuffer(
const void *data, unsigned long size,
unsigned int usage = GL_STATIC_DRAW
);
VertexBuffer(const VertexBuffer &oth);
VertexBuffer &operator=(const VertexBuffer &rhs);
~VertexBuffer();
void bind() const;
void unbind() const;
};
cpp:
std::map<unsigned int, unsigned int> VertexBuffer::references;
VertexBuffer::VertexBuffer(
const void *data,
unsigned long size,
unsigned int usage
) {
GLCall(glGenBuffers(1, &rendererID));
GLCall(glBindBuffer(GL_ARRAY_BUFFER, rendererID));
GLCall(glBufferData(GL_ARRAY_BUFFER, size, data, usage));
references.insert_or_assign(rendererID, 1);
}
VertexBuffer::VertexBuffer(const VertexBuffer &oth) {
if (oth.rendererID != 0){
auto ref = references.find(oth.rendererID);
assert(ref != references.end());
ref->second++;
}
rendererID = oth.rendererID;
}
VertexBuffer &VertexBuffer::operator=(const VertexBuffer &rhs) {
if (rendererID != 0) {
auto refs = references.find(rendererID);
assert(refs != references.end());
if (--refs->second == 0) {
GLCall(glDeleteBuffers(1, &rendererID));
references.erase(refs);
}
}
if (rhs.rendererID != 0){
auto ref = references.find(rhs.rendererID);
assert(ref != references.end());
ref->second++;
}
rendererID = rhs.rendererID;
return *this;
}
VertexBuffer::VertexBuffer() : rendererID(0) {}
VertexBuffer::~VertexBuffer() {
if (rendererID != 0) {
auto ref = references.find(rendererID);
assert(ref != references.end());
if (--ref->second == 0) {
GLCall(glDeleteBuffers(1, &rendererID));
references.erase(ref);
}
}
}
void VertexBuffer::bind() const {
GLCall(glBindBuffer(GL_ARRAY_BUFFER, rendererID));
}
void VertexBuffer::unbind() const {
GLCall(glBindBuffer(GL_ARRAY_BUFFER, 0));
}
在球体中,我只有一个包含位置和颜色的大缓冲区。
我找到了解决方案。这是一个非常愚蠢的错误:VertexBuffer class 的构造函数需要以字节为单位的缓冲区大小,但是当我调用它时,我只传递了 std::vector 的大小,即元素的数量。