带有纹理的卡通着色器。这可以优化吗?
Posted
技术标签:
【中文标题】带有纹理的卡通着色器。这可以优化吗?【英文标题】:Toon shader with Texture. Can this be optimized? 【发布时间】:2011-11-22 13:50:41 【问题描述】:我设法将Nehe's Cel-Shading 渲染与我的模型加载器集成在一起,并让它们同时使用卡通阴影和轮廓以及它们的原始纹理进行绘制。结果实际上是模型纹理的一个非常漂亮的 Cel Shading 效果,但是它使程序的速度减半,即使屏幕上只有 3 个模型也非常慢...
由于结果有点被破解,我在想也许我正在执行一些可能不需要的额外步骤或额外渲染任务,并且正在减慢游戏速度? 也许你们能发现一些不必要的东西?
MD2 和 3DS 加载器都有一个 InitToon() 函数在创建时调用以加载着色器
initToon()
int i; // Looping Variable ( NEW )
char Line[255]; // Storage For 255 Characters ( NEW )
float shaderData[32][3]; // Storate For The 96 Shader Values ( NEW )
FILE *In = fopen ("Shader.txt", "r"); // Open The Shader File ( NEW )
if (In) // Check To See If The File Opened ( NEW )
for (i = 0; i < 32; i++) // Loop Though The 32 Greyscale Values ( NEW )
if (feof (In)) // Check For The End Of The File ( NEW )
break;
fgets (Line, 255, In); // Get The Current Line ( NEW )
shaderData[i][0] = shaderData[i][1] = shaderData[i][2] = float(atof (Line)); // Copy Over The Value ( NEW )
fclose (In); // Close The File ( NEW )
else
return false; // It Went Horribly Horribly Wrong ( NEW )
glGenTextures (1, &shaderTexture[0]); // Get A Free Texture ID ( NEW )
glBindTexture (GL_TEXTURE_1D, shaderTexture[0]); // Bind This Texture. From Now On It Will Be 1D ( NEW )
// For Crying Out Loud Don't Let OpenGL Use Bi/Trilinear Filtering! ( NEW )
glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage1D (GL_TEXTURE_1D, 0, GL_RGB, 32, 0, GL_RGB , GL_FLOAT, shaderData); // Upload ( NEW )
这是动画 MD2 模型的绘图:
void MD2Model::drawToon()
float outlineWidth = 3.0f; // Width Of The Lines ( NEW )
float outlineColor[3] = 0.0f, 0.0f, 0.0f ; // Color Of The Lines ( NEW )
// ORIGINAL PART OF THE FUNCTION
//Figure out the two frames between which we are interpolating
int frameIndex1 = (int)(time * (endFrame - startFrame + 1)) + startFrame;
if (frameIndex1 > endFrame)
frameIndex1 = startFrame;
int frameIndex2;
if (frameIndex1 < endFrame)
frameIndex2 = frameIndex1 + 1;
else
frameIndex2 = startFrame;
MD2Frame* frame1 = frames + frameIndex1;
MD2Frame* frame2 = frames + frameIndex2;
//Figure out the fraction that we are between the two frames
float frac =
(time - (float)(frameIndex1 - startFrame) /
(float)(endFrame - startFrame + 1)) * (endFrame - startFrame + 1);
// I ADDED THESE FROM NEHE'S TUTORIAL FOR FIRST PASS (TOON SHADE)
glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); // Use The Good Calculations ( NEW )
glEnable (GL_LINE_SMOOTH);
// Cel-Shading Code //
glEnable (GL_TEXTURE_1D); // Enable 1D Texturing ( NEW )
glBindTexture (GL_TEXTURE_1D, shaderTexture[0]); // Bind Our Texture ( NEW )
glColor3f (1.0f, 1.0f, 1.0f); // Set The Color Of The Model ( NEW )
// ORIGINAL DRAWING CODE
//Draw the model as an interpolation between the two frames
glBegin(GL_TRIANGLES);
for(int i = 0; i < numTriangles; i++)
MD2Triangle* triangle = triangles + i;
for(int j = 0; j < 3; j++)
MD2Vertex* v1 = frame1->vertices + triangle->vertices[j];
MD2Vertex* v2 = frame2->vertices + triangle->vertices[j];
Vec3f pos = v1->pos * (1 - frac) + v2->pos * frac;
Vec3f normal = v1->normal * (1 - frac) + v2->normal * frac;
if (normal[0] == 0 && normal[1] == 0 && normal[2] == 0)
normal = Vec3f(0, 0, 1);
glNormal3f(normal[0], normal[1], normal[2]);
MD2TexCoord* texCoord = texCoords + triangle->texCoords[j];
glTexCoord2f(texCoord->texCoordX, texCoord->texCoordY);
glVertex3f(pos[0], pos[1], pos[2]);
glEnd();
// ADDED THESE FROM NEHE'S FOR SECOND PASS (OUTLINE)
glDisable (GL_TEXTURE_1D); // Disable 1D Textures ( NEW )
glEnable (GL_BLEND); // Enable Blending ( NEW )
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); // Set The Blend Mode ( NEW )
glPolygonMode (GL_BACK, GL_LINE); // Draw Backfacing Polygons As Wireframes ( NEW )
glLineWidth (outlineWidth); // Set The Line Width ( NEW )
glCullFace (GL_FRONT); // Don't Draw Any Front-Facing Polygons ( NEW )
glDepthFunc (GL_LEQUAL); // Change The Depth Mode ( NEW )
glColor3fv (&outlineColor[0]); // Set The Outline Color ( NEW )
// HERE I AM PARSING THE VERTICES AGAIN (NOT IN THE ORIGINAL FUNCTION) FOR THE OUTLINE AS PER NEHE'S TUT
glBegin (GL_TRIANGLES); // Tell OpenGL What We Want To Draw
for(int i = 0; i < numTriangles; i++)
MD2Triangle* triangle = triangles + i;
for(int j = 0; j < 3; j++)
MD2Vertex* v1 = frame1->vertices + triangle->vertices[j];
MD2Vertex* v2 = frame2->vertices + triangle->vertices[j];
Vec3f pos = v1->pos * (1 - frac) + v2->pos * frac;
Vec3f normal = v1->normal * (1 - frac) + v2->normal * frac;
if (normal[0] == 0 && normal[1] == 0 && normal[2] == 0)
normal = Vec3f(0, 0, 1);
glNormal3f(normal[0], normal[1], normal[2]);
MD2TexCoord* texCoord = texCoords + triangle->texCoords[j];
glTexCoord2f(texCoord->texCoordX, texCoord->texCoordY);
glVertex3f(pos[0], pos[1], pos[2]);
glEnd (); // Tell OpenGL We've Finished
glDepthFunc (GL_LESS); // Reset The Depth-Testing Mode ( NEW )
glCullFace (GL_BACK); // Reset The Face To Be Culled ( NEW )
glPolygonMode (GL_BACK, GL_FILL); // Reset Back-Facing Polygon Drawing Mode ( NEW )
glDisable (GL_BLEND);
这是 3DS 加载器中的 drawToon 函数
void Model_3DS::drawToon()
float outlineWidth = 3.0f; // Width Of The Lines ( NEW )
float outlineColor[3] = 0.0f, 0.0f, 0.0f ; // Color Of The Lines ( NEW )
//ORIGINAL CODE
if (visible)
glPushMatrix();
// Move the model
glTranslatef(pos.x, pos.y, pos.z);
// Rotate the model
glRotatef(rot.x, 1.0f, 0.0f, 0.0f);
glRotatef(rot.y, 0.0f, 1.0f, 0.0f);
glRotatef(rot.z, 0.0f, 0.0f, 1.0f);
glScalef(scale, scale, scale);
// Loop through the objects
for (int i = 0; i < numObjects; i++)
// Enable texture coordiantes, normals, and vertices arrays
if (Objects[i].textured)
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
if (lit)
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
// Point them to the objects arrays
if (Objects[i].textured)
glTexCoordPointer(2, GL_FLOAT, 0, Objects[i].TexCoords);
if (lit)
glNormalPointer(GL_FLOAT, 0, Objects[i].Normals);
glVertexPointer(3, GL_FLOAT, 0, Objects[i].Vertexes);
// Loop through the faces as sorted by material and draw them
for (int j = 0; j < Objects[i].numMatFaces; j ++)
// Use the material's texture
Materials[Objects[i].MatFaces[j].MatIndex].tex.Use();
// AFTER THE TEXTURE IS APPLIED I INSERT THE TOON FUNCTIONS HERE (FIRST PASS)
glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); // Use The Good Calculations ( NEW )
glEnable (GL_LINE_SMOOTH);
// Cel-Shading Code //
glEnable (GL_TEXTURE_1D); // Enable 1D Texturing ( NEW )
glBindTexture (GL_TEXTURE_1D, shaderTexture[0]); // Bind Our Texture ( NEW )
glColor3f (1.0f, 1.0f, 1.0f); // Set The Color Of The Model ( NEW )
glPushMatrix();
// Move the model
glTranslatef(Objects[i].pos.x, Objects[i].pos.y, Objects[i].pos.z);
// Rotate the model
glRotatef(Objects[i].rot.z, 0.0f, 0.0f, 1.0f);
glRotatef(Objects[i].rot.y, 0.0f, 1.0f, 0.0f);
glRotatef(Objects[i].rot.x, 1.0f, 0.0f, 0.0f);
// Draw the faces using an index to the vertex array
glDrawElements(GL_TRIANGLES, Objects[i].MatFaces[j].numSubFaces, GL_UNSIGNED_SHORT, Objects[i].MatFaces[j].subFaces);
glPopMatrix();
glDisable (GL_TEXTURE_1D); // Disable 1D Textures ( NEW )
// THIS IS AN ADDED SECOND PASS AT THE VERTICES FOR THE OUTLINE
glEnable (GL_BLEND); // Enable Blending ( NEW )
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); // Set The Blend Mode ( NEW )
glPolygonMode (GL_BACK, GL_LINE); // Draw Backfacing Polygons As Wireframes ( NEW )
glLineWidth (outlineWidth); // Set The Line Width ( NEW )
glCullFace (GL_FRONT); // Don't Draw Any Front-Facing Polygons ( NEW )
glDepthFunc (GL_LEQUAL); // Change The Depth Mode ( NEW )
glColor3fv (&outlineColor[0]); // Set The Outline Color ( NEW )
for (int j = 0; j < Objects[i].numMatFaces; j ++)
glPushMatrix();
// Move the model
glTranslatef(Objects[i].pos.x, Objects[i].pos.y, Objects[i].pos.z);
// Rotate the model
glRotatef(Objects[i].rot.z, 0.0f, 0.0f, 1.0f);
glRotatef(Objects[i].rot.y, 0.0f, 1.0f, 0.0f);
glRotatef(Objects[i].rot.x, 1.0f, 0.0f, 0.0f);
// Draw the faces using an index to the vertex array
glDrawElements(GL_TRIANGLES, Objects[i].MatFaces[j].numSubFaces, GL_UNSIGNED_SHORT, Objects[i].MatFaces[j].subFaces);
glPopMatrix();
glDepthFunc (GL_LESS); // Reset The Depth-Testing Mode ( NEW )
glCullFace (GL_BACK); // Reset The Face To Be Culled ( NEW )
glPolygonMode (GL_BACK, GL_FILL); // Reset Back-Facing Polygon Drawing Mode ( NEW )
glDisable (GL_BLEND);
glPopMatrix();
最后,这是加载 BMP 纹理并以某种方式与卡通着色完美融合的 tex.Use() 函数
void GLTexture::Use()
glEnable(GL_TEXTURE_2D); // Enable texture mapping
glBindTexture(GL_TEXTURE_2D, texture[0]); // Bind the texture as the current one
编辑------------------------------ ------------------------------
谢谢大家的建议。按照 Kos 的建议,我重新分解了函数,以便不使用 glBegin/End .... 所以我在计算出顶点位置后将顶点存储在 std::vectors 中,然后使用 glDrawArrays 从向量中读取。 ....这在理论上意味着更快,但它给我的帧速率比以前低得多...这是否正确实施?
for(int i = 0; i < numTriangles; i++)
MD2Triangle* triangle = triangles + i;
for(int j = 0; j < 3; j++)
MD2Vertex* v1 = frame1->vertices + triangle->vertices[j];
MD2Vertex* v2 = frame2->vertices + triangle->vertices[j];
Vec3f pos = v1->pos * (1 - frac) + v2->pos * frac;
Vec3f normal = v1->normal * (1 - frac) + v2->normal * frac;
if (normal[0] == 0 && normal[1] == 0 && normal[2] == 0)
normal = Vec3f(0, 0, 1);
normals.push_back(normal[0]);
normals.push_back(normal[1]);
normals.push_back(normal[2]);
MD2TexCoord* texCoord = texCoords + triangle->texCoords[j];
textCoords.push_back(texCoord->texCoordX);
textCoords.push_back(texCoord->texCoordY);
vertices.push_back(pos[0]);
vertices.push_back(pos[1]);
vertices.push_back(pos[2]);
glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_VERTEX_ARRAY);
glNormalPointer(GL_FLOAT, 0, &normals[0]);
glTexCoordPointer(2, GL_FLOAT, 0, &textCoords[0]);
glVertexPointer(3, GL_FLOAT, 0, &vertices[0]);
glDrawArrays(GL_TRIANGLES, 0, vertices.size()/3);
glDisableClientState(GL_VERTEX_ARRAY); // disable vertex arrays
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
vertices.clear();
textCoords.clear();
normals.clear();
【问题讨论】:
您应该访问codereview.stackexchange.com。这个网站是针对特定编程问题的。 【参考方案1】:这是非常低效的。仅使用您的代码中提供的信息:
MD2Frame* frame1 = frames + frameIndex1;
MD2Frame* frame2 = frames + frameIndex2;
这 2 行取决于 MD2Frame 的工作方式,要么计算 2 个关键帧 CPU 端(非常非常慢),要么偏移到 2 个预先计算的关键帧(更有可能)。让我担心的是,所有可用信息都表明这是顶点动画,例如 24 fps 将为您提供 24 * num vertices * size of per vertex data bytes of per vertex data bytes per second of animation。您应该做的是使用骨骼和硬件皮肤为您的模型制作动画。
编辑:注意到它实际上是指针算术,所以它 99% 确定它是预先计算的情况。
这里最容易实现的是使用 VBO,您可以在其中填充计算出的顶点信息。更进一步,创建 2 个 VBO,每个帧一个,将它们都绑定到您的顶点程序,然后在顶点程序中进行混合。或者,如果您要构建对这个代码库有一定要求的任何东西,那么请改用骨骼动画,这会更快,内存效率更高。
编辑:澄清,当顶点混合有意义时,有一些用例,所以在不知道你想要做什么的情况下,我只能说:看看每个顶点动画是否真的是你问题的最佳解决方案.
【讨论】:
感谢您的意见。对于这个编程任务,我需要使用关键帧动画,而 MD2 是最简单的格式之一。该模型在没有卡通代码的情况下以非常快的速度制作动画! 小于 2000,MD2 格式有顶点限制,所以它们的多边形非常低....但并非所有模型都是动画的,3DS 模型是静态的...动画方面到目前为止,这还不是问题,只是这种渲染效果减慢了速度,因为我在所有顶点上循环的次数是我想象的两倍...... 感谢您的进一步信息,似乎每个人都同意 VBO 是要走的路,您建议有两个独立的,一个用于 Toon 纹理渲染,另一个用于轮廓边缘渲染似乎就像一个非常聪明的方法......我会尝试看看我是否可以用我的经验来实现它们并得到结果! 第二个使用VBO的函数似乎很慢,难道是因为两个glDrawElement调用使用的是同一个缓冲区对象? 很可能不会,尽管我只能推测。最简单的方法是使用分析器(如果您在 Windows 上,谷歌非常困,您将在几秒钟内启动并运行)和 nvperfhud / gdebugger。如果您没有看到 VBO 有太多好处,那么您更有可能受 GPU 限制。【参考方案2】:看看这里:
glBegin(GL_TRIANGLES);
for(int i = 0; i < numTriangles; i++)
// ...
glNormal3f(normal[0], normal[1], normal[2]);
// ...
glVertex3f(pos[0], pos[1], pos[2]);
glEnd();
这部分效率非常低,因为您对 GPU 驱动程序对模型的每个顶点都进行了多次调用。正确的方法是使用一个绘制调用和顶点数组将其全部发送,或者 - 甚至更好 - 最好使用顶点缓冲区对象将几何图形存储在 GPU 上。
NeHe 教程包含有用的信息,但现在非常过时; glVertex
和 glBegin
和家人现在已经很少使用了。
【讨论】:
内容丰富,谢谢!另一位海报向我推荐了同样的事情!我将考虑使用顶点缓冲区对象来实现它...关于第二个功能(3DS 模型)有什么严重的错误吗?因为当我只启用那个而不是第一个(MD2)时,帧速率似乎更低...... 我已经设法重新调用函数以删除 glBegin/End 立即绘图并使用 sdt::vector 容器和 glDrawArrays 重新完成......但事实证明它更慢...... .你能看看我在上面发布的新功能,看看我是否正确实现了它?感谢您的热心帮助! 在我们继续之前,让我确认一下向量是否不是每帧都重新分配?它们是否在渲染循环之外定义,以便可以重用内存缓冲区?也就是说,注释掉所有glDrawArrays()
后性能有何变化? (这应该告诉我们是 CPU 还是 GPU 是限制)以上是关于带有纹理的卡通着色器。这可以优化吗?的主要内容,如果未能解决你的问题,请参考以下文章