【OSG学习笔记】Day 38: TextureVisitor(纹理访问器)

张开发
2026/4/18 9:28:01 15 分钟阅读

分享文章

【OSG学习笔记】Day 38: TextureVisitor(纹理访问器)
TextureVisitor纹理访问器在 OSG 三维渲染开发中纹理访问器TextureVisitor是专门用于遍历、提取、修改、管理场景模型纹理贴图的核心工具。它基于 OSG 最经典的访问者设计模式实现能够在不修改场景节点源码、不破坏引擎结构的前提下自动遍历整棵场景树批量处理所有模型的纹理、图片、材质状态。本文结合你提供的完整可运行代码从原理、继承关系、代码实现、执行流程、应用场景五个维度彻底讲透纹理访问器。纹理访问器是什么解决什么问题OSG 场景是一棵树Root → Group → Geode → Drawable → StateSet → Texture → Image纹理贴图图片藏在最底层的StateSet渲染状态集中。如果没有纹理访问器你想要提取模型所有贴图替换所有纹理统一修改纹理参数清理无效纹理只能手写递归遍历所有节点代码冗余、容易出错、扩展性极差。纹理访问器的价值自动遍历引擎自动递归整棵场景树非侵入式不修改 Node/Geode/Texture 源码集中处理所有纹理逻辑写在一个类里高度复用一套访问器支持所有模型核心原理纹理访问器的底层原理和顶点访问器完全一致都是 OSG 访问者模式的标准实现1. 两大角色被访问者osg::Node、osg::Geode、osg::StateSet访问者TextureVisitor你自定义的类2. 执行流程灵魂node-accept(visitor) // 节点允许访问 ↓ visitor.apply(node) // 访问器处理节点 ↓ visitor.apply(geode) // 处理模型 ↓ visitor.apply(stateSet) // 处理纹理状态核心 ↓ traverse(node) // 继续遍历子节点3. 双重分发运行时自动识别节点类型调用对应的apply函数遇到 Node → 调用apply(Node)遇到 Geode → 调用apply(Geode)遇到 StateSet → 调用apply(StateSet)这是 OSG 能实现自动类型匹配、自动遍历的核心秘密。类继承关系必须掌握1. 访问器继承链osg::Referenced ↓ osg::NodeVisitor -- 所有访问器的基类 ↓ TextureVisitor -- 你实现的纹理访问器2. 节点与纹理继承链osg::Node ↓ osg::Geode // 模型节点 ↓ osg::Drawable // 几何体 ↓ osg::StateSet // 渲染状态存放纹理 ↓ osg::Texture2D // 2D 纹理 ↓ osg::Image // 真正的图片数据像素3. 关键类作用NodeVisitor提供遍历框架StateSet管理纹理、材质、着色器Texture2D2D 纹理对象Image内存中的图像可保存为文件完整代码实现深度解析下面结合可直接编译运行的代码逐模块讲解纹理访问器实现。/******************************************************** * Write by FlySky * zzuxp163.com http://www.OsgChina.org ********************************************************/#includeosgViewer/Viewer#includeosg/Node#includeosg/Geode#includeosg/Group#includeosg/NodeVisitor#includeosgDB/ReadFile#includeosgDB/WriteFile#includeosgUtil/Optimizer#includeiostream#includestring#includemap#includecstdio// 节点纹理访问器class TextureVisitor:public osg::NodeVisitor{public:TextureVisitor():osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN){}// 处理 Nodevirtualvoidapply(osg::Nodenode){if(node.getStateSet()){// 修复传指针不是引用apply(node.getStateSet());}traverse(node);}// 处理 Geodevirtualvoidapply(osg::Geodegeode){if(geode.getStateSet()){// 修复传指针apply(geode.getStateSet());}unsignedintcntgeode.getNumDrawables();for(unsignedinti0;icnt;i){osg::Drawable*drawablegeode.getDrawable(i);if(drawabledrawable-getStateSet()){// 修复传指针这行就是报错位置apply(drawable-getStateSet());}}traverse(geode);}// 核心处理 StateSet指针形式voidapply(osg::StateSet*state){if(!state)return;osg::StateSet::TextureAttributeListtexAttribListstate-getTextureAttributeList();for(unsignedinti0;itexAttribList.size();i){osg::Texture*texdynamic_castosg::Texture*(state-getTextureAttribute(i,osg::StateAttribute::TEXTURE));if(tex){osg::Texture2D*tex2Ddynamic_castosg::Texture2D*(tex);if(tex2D){osg::Image*imagetex2D-getImage();if(image){std::string nameimage-getFileName();if(name.empty()){charuuid[36];sprintf(uuid,Unknown_Texture_%p,image);namestd::string(uuid);}_imageList.insert(std::make_pair(name,image));}}}}}std::mapstd::string,osg::ref_ptrosg::ImagegetImages(){return_imageList;}protected:std::mapstd::string,osg::ref_ptrosg::Image_imageList;};intmain(){osg::ref_ptrosgViewer::Viewerviewernew osgViewer::Viewer();osg::ref_ptrosg::Grouprootnew osg::Group();osg::ref_ptrosg::NodenodeosgDB::readNodeFile(cow.osg);if(!node){std::cerr错误无法加载模型文件 cow.osgstd::endl;return-1;}TextureVisitor textureTV;node-accept(textureTV);autoimageListtextureTV.getImages();autoiterimageList.begin();unsignedintcnt0;charbuffer[2000];std::cout检测到 imageList.size() 张贴图。正在导出...std::endl;for(;iter!imageList.end();iter){sprintf(buffer,Texture_%d.jpg,cnt);bool resultosgDB::writeImageFile(*(iter-second.get()),buffer);if(result)std::cout成功导出: bufferstd::endl;elsestd::cerr导出失败: bufferstd::endl;}root-addChild(node.get());osgUtil::Optimizer optimizer;optimizer.optimize(root.get());viewer-setSceneData(root.get());viewer-realize();returnviewer-run();}1. 类定义与构造classTextureVisitor:publicosg::NodeVisitor{public:// 遍历所有子节点TextureVisitor():osg::NodeVisitor(TRAVERSE_ALL_CHILDREN){}必须继承osg::NodeVisitorTRAVERSE_ALL_CHILDREN自动遍历所有子节点2. 处理普通节点voidapply(osg::Nodenode)override{if(node.getStateSet())apply(node.getStateSet());traverse(node);// 继续遍历}3. 处理模型节点Geodevoidapply(osg::Geodegeode)override{if(geode.getStateSet())apply(geode.getStateSet());// 遍历所有几何体for(unsignedi0;igeode.getNumDrawables();i){autodgeode.getDrawable(i);if(dd-getStateSet())apply(d-getStateSet());}traverse(geode);}4. 核心提取纹理与图片最关键voidapply(osg::StateSet*state){if(!state)return;// 遍历所有纹理单元autotexListstate-getTextureAttributeList();for(unsignedi0;itexList.size();i){// 取出纹理osg::Texture*texdynamic_castosg::Texture*(state-getTextureAttribute(i,osg::StateAttribute::TEXTURE));// 转为 2D 纹理osg::Texture2D*tex2ddynamic_castosg::Texture2D*(tex);if(tex2dtex2d-getImage()){// 保存图片到 map_imageList[tex2d-getImage()-getFileName()]tex2d-getImage();}}}5. 主函数使用TextureVisitor tv;node-accept(tv);// 启动访问autoimagestv.getImages();for(autopair:images){osgDB::writeImageFile(*pair.second,Texture.jpg);}纹理访问器执行流程极简总结1. 调用 node-accept(tv) 2. 访问器进入节点 3. 自动调用 apply(Node) / apply(Geode) 4. 取出 StateSet 5. 遍历纹理 → 转 Texture2D → 取 Image 6. 存入 map 7. traverse 继续遍历子节点 8. 遍历完成 → 得到所有纹理图片纹理访问器的应用场景超级实用纹理访问器是OSG 工程化开发必备工具覆盖 90% 纹理相关需求1. 批量提取模型贴图最常用导出模型所有纹理到本地资源解包、查看模型材质你现在运行的代码就是这个功能2. 批量替换纹理一键换肤场景中所有模型统一换贴图模拟昼夜、季节、皮肤切换3. 纹理压缩与优化自动压缩贴图大小降低内存占用提升渲染性能4. 清理无效纹理删除没有使用的贴图修复模型加载异常5. 批量修改纹理参数统一开启/关闭纹理过滤统一设置 Wrap 模式统一修改纹理单元6. 纹理统计与检查统计场景贴图数量、尺寸、格式检查超大贴图、异常纹理总结纹理访问器 自动遍历 纹理批量操作基类osg::NodeVisitor核心方法apply(StateSet) → 提取 Texture2D → 取 Image执行流程accept → apply → traverse应用提取、换肤、压缩、清理、统计

更多文章