Simple2D-22(重构)纹理池

Posted 为了邮箱5

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Simple2D-22(重构)纹理池相关的知识,希望对你有一定的参考价值。

  以前 Simple2D 使用 TextureManager,现在将它改为 TexturePool (纹理池)。主要是负责加载和管理纹理,这次为 TexturePool 添加纹理集的功能,纹理集就是将大量的图片拼合成一张纹理。

 

  纹理集的制作

  你可以使用软件 TexturePacher 来创建纹理集:

 

  将图片文件拖曳到左边的窗口,然后将 Output 的 DataFormat 设置为 cocos2d,最后选择 Data File 和 Texture File 的输出路径,点击工具栏的 Publish 按钮后得到两个文件 xxx.plist 和 xxx.png,再将这两个文件放置在 Assert 文件夹即可。

 

  解析 Plist 文件

  由于 Plist 文件时 xml 格式的,所以可以使用 Tinyxml 库来解析,其中只需要读取 plist 文件的三个信息即可:

 

  1、纹理文件名,与小图相关联的标签

  2、小图的大小和小图在大图中的位置偏移,用来计算纹理坐标

  3、小图是否旋转,TexturePacher 在合并小图时为了合理分配空间位置,必要时会对小图旋转 90o,计算纹理坐标时要进行旋转

 

  OpenGL 为每个纹理分配一个唯一的 ID,而纹理集的多张小图都来自于一张纹理,为了管理这些纹理,需要两个结构:TextureUnique 和 Texture2D。TextureUnique 对于着一张纹理,而 Texture2D 则对应小图:

    struct Texture2D
    {
        TextureUnique* textureUnique;

        int     width;
        int     height;
        Vec2 uv[4];
    };

  Texture2D 保存图片的大小(该大小是小图的大小,不是纹理的大小)、纹理坐标和 TextureUnique 对象。

  TexturePool 使用 ParsePlistFile( ) 函数来解析 plist 文件:

bool TexturePool::ParsePlistFile(const std::string& filename, std::vector<PlistParseData>& ppd_list)
    {
        tinyxml2::XMLDocument doc;

        auto path = PathHelper::fullPath(filename);
        if ( doc.LoadFile(path.c_str()) != tinyxml2::XML_NO_ERROR ) {
            LOG_WRITE_DEBUG("不存在 plist 文件:%s", filename.c_str());
            return false;
        }

        tinyxml2::XMLElement* frame_ele = nullptr;
        tinyxml2::XMLElement* context_ele = nullptr;

        tinyxml2::XMLNode* plist_node = doc.RootElement();

        plist_node = plist_node->FirstChildElement();
        frame_ele = plist_node->FirstChildElement();

        tinyxml2::XMLElement* begin_node = frame_ele->NextSiblingElement()->FirstChildElement();

        std::string left, right;

        while ( begin_node ) {
            PlistParseData ppd;

            ppd.filename = begin_node->GetText();
            context_ele = begin_node->NextSiblingElement();

            context_ele = context_ele->FirstChildElement("string");
            std::string size = context_ele->GetText();

            /* {{xx, xx},{xx, xx}} */
            left = size.substr(2, size.find_first_of("}") - 2);
            right = left.substr(left.find_first_of(",") + 1, left.size() - left.find_first_of(","));
            left = left.substr(0, left.find_first_of(","));

            ppd.offsetx = atoi(left.c_str());
            ppd.offsety = atoi(right.c_str());

            right = size.substr(size.find_last_of("{") + 1, size.size() - size.find_last_of("{") - 3);
            left = right.substr(0, right.find_first_of(","));
            right = right.substr(right.find_first_of(",") + 1, right.size() - right.find_first_of(","));

            ppd.width = atoi(left.c_str());
            ppd.height = atoi(right.c_str());

            context_ele = context_ele->NextSiblingElement();
            context_ele = context_ele->NextSiblingElement();
            context_ele = context_ele->NextSiblingElement();
            context_ele = context_ele->NextSiblingElement();

            std::string rotate = context_ele->Name();
            ppd.rotate = (rotate.compare("true") == 0);

            begin_node = begin_node->NextSiblingElement();
            begin_node = begin_node->NextSiblingElement();

            ppd_list.push_back(ppd);
        }
        /* 获取图像文件名 */
        frame_ele = frame_ele->NextSiblingElement();
        frame_ele = frame_ele->NextSiblingElement();
        frame_ele = frame_ele->NextSiblingElement();

        tinyxml2::XMLElement* metadata = frame_ele->FirstChildElement("string");
        std::string texture_name = metadata->GetText();

        metadata = metadata->NextSiblingElement("string");
        std::string texture_size = metadata->GetText();

        int dot = texture_size.find_first_of(",");

        PlistParseData ppd;
        ppd.filename = texture_name;
        ppd.width    = atoi(texture_size.substr(1, dot - 1).c_str());
        ppd.height   = atoi(texture_size.substr(dot + 1, texture_size.size() - dot - 2).c_str());

        ppd_list.push_back(ppd);
        return true;
    }

  将解析得到的小图数据保存到 PlistParseData 结构中,然后得到一个 PlistParseData 数组:

        struct PlistParseData
        {
            std::string filename;
            bool rotate;

            int offsetx;
            int offsety;

            int width;
            int height;
        };

  通过 PlistParseData 数组就可以创建 TextureUnique 和 Texture2D 对象了:

bool TexturePool::LoadFileFromPlist(const std::string& filename)
    {
        std::vector<PlistParseData> ppd_list;
        if ( ParsePlistFile(filename, ppd_list) == false ) {
            LOG_WRITE("解析文件 %s 失败!", filename.c_str());
            return false;
        }

        TextureUnique* texture_unique = new TextureUnique(ppd_list.back().filename.c_str());

        for ( int i = 0; i < ppd_list.size() - 1; i++ ) {
            PlistParseData& ppd = ppd_list[i];

            Texture2D* texture_2d = new Texture2D;
            texture_2d->textureUnique = texture_unique;
            texture_2d->width = ppd.width;
            texture_2d->height = ppd.height;

            /* 计算纹理坐标 */
            Vec2 p1, p2;
            if ( ppd.rotate ) {
                p1.x = ( float ) ppd.offsetx / texture_unique->width;
                p1.y = 1 - ( float ) (ppd.offsety + ppd.width) / texture_unique->height;

                p2.x = ( float ) (ppd.offsetx + ppd.height) / texture_unique->width;
                p2.y = 1 - ( float ) ppd.offsety / texture_unique->height;

                texture_2d->uv[3].set(p1.x, p1.y);
                texture_2d->uv[0].set(p1.x, p2.y);
                texture_2d->uv[1].set(p2.x, p2.y);
                texture_2d->uv[2].set(p2.x, p1.y);
            }
            else {
                p1.x = ( float ) ppd.offsetx / texture_unique->width;
                p1.y = 1- ( float ) (ppd.offsety + ppd.height) / texture_unique->height;

                p2.x = ( float ) (ppd.offsetx + ppd.width) / texture_unique->width;
                p2.y = 1- ( float ) ppd.offsety / texture_unique->height;

                texture_2d->uv[0].set(p1.x, p1.y);
                texture_2d->uv[1].set(p1.x, p2.y);
                texture_2d->uv[2].set(p2.x, p2.y);
                texture_2d->uv[3].set(p2.x, p1.y);
            }
            vTextureMap.insert(std::make_pair(ppd.filename, texture_2d));
        }
        return true;
    }

  将得到的 Texture2D 对象保存到一个数组中,最后通过 TexturePool 提供的函数 Texture2D* GetTexture(const std::string& filename) 获取 Texture2D 对象。而 TextureUnique 则用于纹理的删除,但 TexturePool 并没有提供纹理的删除操作,也就是你无法再不需要纹理时删除纹理,只能在程序结束后删除。

  Texture2D 是 Sprite、Painter 和 ImGui 使用的图片渲染对象,而 TextureUnique 只是在 TexturePool 内部使用。

 

  源码下载:Simple2D-20.rar

以上是关于Simple2D-22(重构)纹理池的主要内容,如果未能解决你的问题,请参考以下文章

在片段着色器中丢失纹理定义

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

片段着色器中未使用纹理数据 - OpenGL

GLSL:无法从 FBO 读取纹理并使用片段着色器渲染到另一个 FBO

OpenGL、GLSL 片段着色器无法读取 Sampler2D 纹理

片段着色器究竟如何用于纹理?