我已经学习了几天OpenGL,通过一些教程并编写了我自己的一些实验。但有一件事我真的不明白是阻止我继续下去的。我一直在谷歌上搜索几个小时,但我的问题还没有找到答案。
我应该在哪里为每个顶点指定每个单独的颜色值和纹理坐标?这些属性总是是否应该与顶点位置在同一个数组(struct)中列出?像这样:
const Vertex Vertices[] = {
// Front
{{1, -1, 0}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
{{1, 1, 0}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
{{-1, 1, 0}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
{{-1, -1, 0}, {0, 0, 0, 1}, {0, 0}},
...
或者有没有办法将颜色值和纹理坐标放在单独的数组中?但问题出现了:如何使用单独的数组调用glDrawElements
?
如果你想知道我为什么要分离这些值:我现在正在obj-c中创建自己的.obj解析器,我想知道:如果加载没有纹理的模型并且只想显示一个物体上的颜色?或者:如果你想加载一个只有一个纹理映射到它但每个顶点没有单独颜色的模型怎么办?并且:没有使用过多的数据将Vertex结构的颜色值和纹理坐标放大。
答案 0 :(得分:21)
实际上,通常使用多个数组/缓冲区将顶点数据分成位置,颜色等。
上次我接触ES 2.0是在WebGL的背景下(其规格略有不同,但最终基于ES 2.0)。
基本上做的是使用
将数据写入单独的缓冲区glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, 12 * sizeof(float), positions, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(float), colors, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
...
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(ushort), indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
在这种情况下,位置和颜色是包含顶点数据的float数组和包含索引的索引作为unsigned short。
要渲染此数据,您需要使用指向着色器的缓冲区和属性指针:
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glVertexAttribPointer(vertexPositionAttribute, 3, GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
glVertexAttribPointer(vertexColorAttribute, 4, GL_FLOAT, false, 0, 0);
最后绑定索引缓冲区:
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
并渲染:
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, 0);
获取属性:
glUseProgram(shaderProgram);
vertexPositionAttribute= glGetAttribLocation(shaderProgram, "vertexPosition");
glEnableVertexAttribArray(vertexPositionAttribute);
vertexColorAttribute = glGetAttribLocation(shaderProgram, "vertexColor");
glEnableVertexAttribArray(vertexColorAttribute );
...
如果您没有自定义着色器(使用固定功能),您可以使用
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glVertexPointer(3, GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
glColorPointer(4, GL_FLOAT, false, 0, 0);
代替。不过,我建议不要这样做,因为它已经过时了(如果在ES 2.0中可用)。 如果您仍想使用它,则可以完全跳过整个缓冲区业务并使用
glVertexPointer(3, GL_FLOAT, false, 0, positions);
glColorPointer(4, GL_FLOAT, false, 0, colors);
与
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, indices);
我希望这不会太令人困惑并且有点帮助。为了进一步阅读,虽然针对OpenGL,我建议使用Nehe tutorials。
答案 1 :(得分:2)
您当然可以将数据放在不同的缓冲区中。请记住,glVertexAttribPointer
调用确定了属性的来源。因此,要为属性使用不同的缓冲区,只需在调用GL_ARRAY_BUFFER
之前绑定不同的glVertexAttribPointer
。 glDrawElements
与它没有任何关系:
glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
glVertexAttribPointer(0, ...);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
glVertexAttribPointer(1, ...);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glDrawElements(...);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
当不使用VBO时(我不确定ES 2.0中是否可行),只需为每个属性调用glVertexAttribPointer
不同的数组:
glVertexAttribPointer(0, ..., positionArray);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, ..., colorArray);
glEnableVertexAttribArray(1);
glDrawElements(..., indexArray);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
但是将单个顶点的属性保持在一起通常比在示例中更高效,因为这对缓存更友好。如果几乎所有对象都使用所有数组,那么将它们保持在一起并且不为少数不使用它的对象启用属性可能是一个更好的主意。但是如果使用的属性在不同对象之间确实不同,则单独的缓冲区解决方案可能是更好的主意。您还可以在一个VBO中一个接一个地存储所有单独的属性数组,并在glVertexAttribPointer
调用中使用相应的缓冲区偏移量,因此每个对象仍然只需要一个VBO。
但是当然,每个对象只能使用一个索引数组,而位置和颜色不能使用不同的索引数组。这可能需要您稍微对从OBJ文件读取的数据进行后处理,因为OBJ文件确实可以使用不同的位置,法线和texCoords索引。
答案 2 :(得分:1)
你可以将它们分成子缓冲区,但是如果你使用它们,那么你必须为所有顶点使用它们,如果你使用索引缓冲区,那么你必须为所有使用一个索引缓冲区(位置,颜色,texcoord)等)。以下是我的代码摘录:
分配
glBindBuffer(GL_ARRAY_BUFFER, mId);
glBufferData(GL_ARRAY_BUFFER,
mMaxNumberOfVertices * (mVertexBlockSize + mNormalBlockSize + mColorBlockSize + mTexCoordBlockSize),
0,
mDrawMode);
填写
glBufferSubData(GL_ARRAY_BUFFER, mVertexOffset, numberOfVertsToStore * mVertexBlockSize, vertices);
glBufferSubData(GL_ARRAY_BUFFER, mNormalOffset, numberOfVertsToStore * mNormalBlockSize, normals);
glBufferSubData(GL_ARRAY_BUFFER, mColorOffset, numberOfVertsToStore * mColorBlockSize, colors);
glBufferSubData(GL_ARRAY_BUFFER, mTexCoordOffset, numberOfVertsToStore * mTexCoordBlockSize, texCoords);
并使用它(我不认为不断切换clientStates是最好的做法)
void Vbo::draw(GLenum primMode)
{
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(mVertexComponents, GL_FLOAT, 0, (void*)mVertexOffset);
if(mNormalBlockSize){
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT, 0, (void*)mNormalOffset);
}
if(mColorBlockSize){
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(mColorComponents, GL_FLOAT, 0, (void*)mColorOffset);
}
if(mTexCoordBlockSize){
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(mTexCoordComponents, GL_FLOAT, 0, (void*)mTexCoordOffset);
}
if (mAttachedIndexBuffer)
{
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mAttachedIndexBuffer);
glDrawElements(primMode,
mAttachedIndexBuffer->getNumberOfStoredIndices(),
mAttachedIndexBuffer->getDataType(),
0);
}
else
{
glDrawArrays(primMode, 0, mNumberOfStoredVertices);
}
if(mTexCoordBlockSize)
{
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
if(mColorBlockSize)
{
glDisableClientState(GL_COLOR_ARRAY);
}
if(mNormalBlockSize)
{
glDisableClientState(GL_NORMAL_ARRAY);
}
glDisableClientState(GL_VERTEX_ARRAY);
}