首先,让我澄清一下“分组模型”的含义。我实际上不确定这是什么标准术语。为了减少渲染调用的数量,我将多个模型组合成一个模型,并通过对 glDrawElements 的一次调用(使用 VBO)来渲染整个事物。在我的代码中,我称之为 ModelGroup。我将它用于各种事物,尤其是用于大组几何简单的对象(如城市中的建筑物或粒子)。
这个问题最近出现在我的模型组渲染非常缓慢的地方。我通过在它周围放置一个计时器,将减速与对 glDrawElements 的实际调用隔离开来。例如,我的粒子曾经在大约 2ms 左右渲染大约 10k 粒子(没有实例化)。我不记得确切的数字,但我们只能说渲染绝对不是目前的瓶颈。到目前为止,对具有 10k 个粒子的 glDrawElements 的一次调用大约需要 256 毫秒。这种性能仅比单独调用 glDrawElements 渲染对象好一点。因此,出于某种原因,GPU 显然承受着巨大的负担。
我的引擎发生了什么变化:我最近更新了 XCode,并从使用 EAGLView 更改为使用 GLKViewController。在这两种截然不同的性能状态之间,我的代码中没有任何其他更改。我会说,为了迁移到使用 GLKViewController,我完全重新创建了我的项目并添加了我所有的源文件。然后我重写了我的游戏循环以通过 GLKViewController 的更新功能进行更新。然而,这是一个非常小的变化。
为了完全清楚我的 ModelGroup 类的作用,我将发布将添加的模型编译到呈现的显示模型中的函数。
-(bool) compileDisplayModelWithNormals:(bool)updateNormals withTexcoords:(bool)updateTexcoords withColor:(bool)updateColor withElements:(bool)updateElements
{
modelCompiled = YES;
bool initd = displayModel->positions;
// set properties
if( !initd )
{
displayModel->primType = GL_UNSIGNED_SHORT;
displayModel->elementType = GL_TRIANGLES;
displayModel->positionType = GL_FLOAT;
displayModel->texcoordType = GL_FLOAT;
displayModel->colorType = GL_FLOAT;
displayModel->normalType = GL_FLOAT;
displayModel->positionSize = 3;
displayModel->normalSize = 3;
displayModel->texcoordSize = 2;
displayModel->colorSize = 4;
// initialize to zero
displayModel->numVertices = 0;
displayModel->numElements = 0;
displayModel->positionArraySize = 0;
displayModel->texcoordArraySize = 0;
displayModel->normalArraySize = 0;
displayModel->elementArraySize = 0;
displayModel->colorArraySize = 0;
// sum the sizes
for( NSObject<RenderedItem> *ri in items )
{
GLModel *model = ri.modelAsset.model.displayModel;
displayModel->numVertices += model->numVertices;
displayModel->numElements += model->numElements;
displayModel->positionArraySize += model->positionArraySize;
displayModel->texcoordArraySize += model->texcoordArraySize;
displayModel->normalArraySize += model->normalArraySize;
displayModel->elementArraySize += model->elementArraySize;
displayModel->colorArraySize += model->colorArraySize;
}
displayModel->positions = (GLfloat *)malloc( displayModel->positionArraySize );
displayModel->texcoords = (GLfloat *)malloc( displayModel->texcoordArraySize );
displayModel->normals = (GLfloat *)malloc( displayModel->normalArraySize );
displayModel->elements = (GLushort *)malloc( displayModel->elementArraySize );
displayModel->colors = (GLfloat *)malloc( displayModel->colorArraySize );
}
// update the data
int vertexOffset = 0;
int elementOffset = 0;
for( int j = 0; j < [items count]; j++ )
{
NSObject<RenderedItem> *ri = (GameItem *)[items objectAtIndex:j];
GLModel *model = ri.modelAsset.model.displayModel;
if( !ri.geometryUpdate )
{
vertexOffset += model->numVertices;
continue;
}
// reset the update flag
ri.geometryUpdate = NO;
// get GameItem transform data
rpVec3 pos = [ri getPosition];
rpMat3 rot = [ri orientation];
int NoV = model->numVertices;
int NoE = model->numElements;
for( int i = 0; i < NoV; i++ )
{
// positions
rpVec3 r = rpVec3( model->positions, model->positionSize * i );
// scale
rpVec3 s = ri.scale;
r.swizzleLocal( s );
// rotate
r = rot * r;
// translate
r.addLocal( pos );
int start = model->positionSize * (vertexOffset + i);
for( int k = 0; k < model->positionSize; k++ )
displayModel->positions[start + k] = r[k];
if( updateTexcoords )
{
// texcoords
start = model->texcoordSize * (vertexOffset + i);
if( model->texcoords )
for( int k = 0; k < model->texcoordSize; k++ )
displayModel->texcoords[start + k] = model->texcoords[model->texcoordSize * i + k];
}
if( updateNormals )
{
// normals (need to be rotated)
if( model->normals )
{
for( int k = 0; k < model->normalSize; k++ )
{
rpVec3 vn = rpVec3( model->normals, model->normalSize * i );
rpVec3 vnRot = rot * vn;
start = model->normalSize * (vertexOffset + i);
displayModel->normals[start + k] = vnRot[k];
}
}
}
if( updateColor )
{
if( model->colors )
{
start = model->colorSize * (vertexOffset + i);
displayModel->colors[start] = ri.color.r;
displayModel->colors[start + 1] = ri.color.g;
displayModel->colors[start + 2] = ri.color.b;
displayModel->colors[start + 3] = ri.color.a;
}
}
}
if( updateElements )
{
for( int i = 0; i < NoE; i++ )
{
// elements
displayModel->elements[elementOffset + i] = model->elements[i] + vertexOffset;
}
}
vertexOffset += NoV;
elementOffset += NoE;
}
return YES;
}
为了完整起见,这里是我渲染粒子的方式。粒子场绘制函数内部:
glBindVertexArray( modelGroup.displayModel->modelID );
glBindTexture( GL_TEXTURE_2D, textureID );
// set shader program
if( changeShader ) glUseProgram( shader.programID );
[modelViewStack push];
mtxMultiply( modelViewProjectionMatrix.m, [projectionStack top].m, [modelViewStack top].m );
glUniformMatrix4fv( shader.modelViewProjectionMatrixID, 1, GL_FALSE, modelViewProjectionMatrix.m );
[DebugTimer check:@"particle main start"];
glDrawElements( GL_TRIANGLES, modelGroup.displayModel->numElements, GL_UNSIGNED_SHORT, 0 );
[DebugTimer check:@"particle main end"];
[modelViewStack pop];
夹在 glDrawElements 语句之间的两个语句是我用来测量事件之间时间的计时器。
另外,我只想补充一点,我已经在设备和 iPad 模拟器 6.1 上运行,结果相同。模拟器在执行多个绘图调用时较慢,但在为 ModelGroup 调用 glDrawElements 时两者同样慢。就硬件加速而言,我已经检查以确保这种性能影响不会因为缺乏加速而产生一些副作用。我渲染了一个从包含 1024 个立方体(类似于城市的模型组)的文件中读取的模型,该模型可以毫无问题地渲染(没有 20 毫秒的延迟,就像模型组中的 1000 个立方体一样)。