带有纹理的卡通着色器。这可以优化吗?

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 教程包含有用的信息,但现在非常过时; glVertexglBegin 和家人现在已经很少使用了。

【讨论】:

内容丰富,谢谢!另一位海报向我推荐了同样的事情!我将考虑使用顶点缓冲区对象来实现它...关于第二个功能(3DS 模型)有什么严重的错误吗?因为当我只启用那个而不是第一个(MD2)时,帧速率似乎更低...... 我已经设法重新调用函数以删除 glBegin/End 立即绘图并使用 sdt::vector 容器和 glDrawArrays 重新完成......但事实证明它更慢...... .你能看看我在上面发布的新功能,看看我是否正确实现了它?感谢您的热心帮助! 在我们继续之前,让我确认一下向量是否不是每帧都重新分配?它们是否在渲染循环之外定义,以便可以重用内存缓冲区?也就是说,注释掉所有glDrawArrays() 后性能有何变化? (这应该告诉我们是 CPU 还是 GPU 是限制)

以上是关于带有纹理的卡通着色器。这可以优化吗?的主要内容,如果未能解决你的问题,请参考以下文章

8个纹理单元的opengl es多纹理着色器的思考

纹理中的 OpenGL 片段着色器

如何在片段着色器中平铺部分纹理

材质着色器和纹理

材质着色器和纹理

材质着色器和纹理