从零开始:手把手教你将SKP高效转换为GLTF | 附全套源码

深度解析:如何高效实现 SKP 到 GLTF 的转换——附带完整源代码

在现代 3D 图形处理中,GLTF 格式因其高效的传输和渲染性能而备受青睐,其格式是公开的。而 SketchUp 的 SKP 格式是封闭的。如何将 SKP 转换为 GLTF 是许多开发者面临的挑战。本文将深入解析如何高效实现 SKP 到 GLTF 的转换,并附带完整的源代码示例。

项目概述

本项目旨在实现一个工具,将 SketchUp 的 SKP 文件转换为 GLTF 格式。项目使用 C++ 编写,并结合了多种第三方库,如 SketchUp SDK、Draco、TinyGLTF 等,以确保转换的高效性和准确性。

项目结构

项目的主要结构如下:

  • CMakeLists.txt : 项目的构建配置文件,定义了项目的编译选项、依赖库和生成目标。

  • include/ : 包含项目的头文件,定义了项目的接口和数据结构。

  • exportgltf/ : GLTF 导出相关头文件。

  • gltflib/ : GLTF 库头文件。

  • skp2xml/ : SKP 到 XML 转换头文件。

  • src/ : 包含项目的源代码,实现了项目的核心功能。

  • exportgltf/ : 实现 GLTF 导出功能。

  • gltflib/ : 实现 GLTF 库功能。

  • skp2xml/ : 实现 SKP 到 XML 转换功能。

  • main.cpp : 项目的主程序入口。

  • thirdparty/ : 包含项目使用的第三方库。

  • SketchUpAPI/ : SketchUp SDK。

  • draco/ : Draco 压缩库。

  • tinygltf/ : GLTF 解析库。

  • 其他 JSON 和 XML 解析库。

  • lib/ : 包含项目依赖的库文件。

项目实现逻辑

±-----------------+
| main.cpp |
| (程序入口) |
±-------±--------+
|
v
±-------±--------+
| skp2xml |
| (SKP -> XML) |
±-------±--------+
|
v
±-------±--------+
| exportgltf |
| (XML -> GLTF) |
±-------±--------+
|
v
±-------±--------+

关键技术点

1. 使用 SketchUp SDK 解析 SKP 文件

SketchUp SDK 提供了丰富的 API 用于解析 SKP 文件。通过这些 API,我们可以提取模型的几何信息、材质信息等。

2. 使用 TinyGLTF 生成 GLTF 文件

TinyGLTF 是一个轻量级的 GLTF 解析和生成库。我们使用它来构建 GLTF 文件的结构,并将压缩后的几何数据写入其中。

代码实现

以下是项目的核心代码片段:

// ... existing code ...
void CXmlExporter::exportToGltfImpl(const std::string &gltfName) {
    // 处理文件名后缀
    std::string outputPath = gltfName;
    if (outputPath.length() < 5 || outputPath.substr(outputPath.length() - 5) != ".gltf") {
        outputPath += ".gltf";
    }
    
    // 初始化 GLTF 模型
    tinygltf::Model model;
    model.asset.version = "2.0";
    model.asset.generator = "zhuzhaoyun";
    tinygltf::Scene scene;
    model.scenes.push_back(scene);
    model.defaultScene = 0;
    
    // 添加一个默认buffer用于存储所有数据
    model.buffers.push_back(tinygltf::Buffer());
    
    // 处理几何数据
    for (auto &item : facetMap) {
        tinygltf::Mesh mesh;
        tinygltf::Primitive primitive;
        primitive.mode = 4;  // triangles
        
        // 收集顶点和索引数据
        std::vector<float> positions;
        std::vector<float> uvs;
        std::vector<unsigned int> indices;

        // 添加用于计算边界的变量
        float posMin[3] = {FLT_MAX, FLT_MAX, FLT_MAX};
        float posMax[3] = {-FLT_MAX, -FLT_MAX, -FLT_MAX};
        
        std::vector<cFacet> &facetVec = item.second;
        size_t vertexOffset = 0;
        
        for (size_t i = 0; i < facetVec.size(); i++) {
            for (int j = 0; j < 3; j++) {
                float x = static_cast<float>(facetVec[i].vertex[j].x);
                float y = static_cast<float>(facetVec[i].vertex[j].y);
                float z = static_cast<float>(facetVec[i].vertex[j].z);
                
                // 更新边界值
                posMin[0] = (std::min)(posMin[0], x);
                posMin[1] = (std::min)(posMin[1], y);
                posMin[2] = (std::min)(posMin[2], z);
                posMax[0] = (std::max)(posMax[0], x);
                posMax[1] = (std::max)(posMax[1], y);
                posMax[2] = (std::max)(posMax[2], z);
                
                positions.push_back(x);
                positions.push_back(y);
                positions.push_back(z);

                if (!facetVec.empty()) {
                    uvs.push_back(static_cast<float>(facetVec[i].uv[j].x));
                    uvs.push_back(static_cast<float>(-facetVec[i].uv[j].y));
                }
                
                indices.push_back(vertexOffset++);
            }
        }

        // 创建并添加顶点位置buffer
        {
            tinygltf::BufferView bufferView;
            bufferView.buffer = 0;
            bufferView.byteOffset = model.buffers[0].data.size();
            bufferView.byteLength = positions.size() * sizeof(float);
            bufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
            
            size_t bufferOffset = model.buffers[0].data.size();
            model.buffers[0].data.resize(bufferOffset + bufferView.byteLength);
            memcpy(model.buffers[0].data.data() + bufferOffset, positions.data(), bufferView.byteLength);
            
            int positionBufferViewIndex = model.bufferViews.size();
            model.bufferViews.push_back(bufferView);
            
            tinygltf::Accessor accessor;
            accessor.bufferView = positionBufferViewIndex;
            accessor.byteOffset = 0;
            accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
            accessor.count = positions.size() / 3;
            accessor.type = TINYGLTF_TYPE_VEC3;

            // 设置边界值
            accessor.minValues = {posMin[0], posMin[1], posMin[2]};
            accessor.maxValues = {posMax[0], posMax[1], posMax[2]};

            int positionAccessorIndex = model.accessors.size();
            model.accessors.push_back(accessor);
            
            primitive.attributes["POSITION"] = positionAccessorIndex;
        }
        
        // 创建并添加UV buffer (如果有UV数据)
        if (!uvs.empty() && !item.first.imageUri.empty()) {
            tinygltf::BufferView bufferView;
            bufferView.buffer = 0;
            bufferView.byteOffset = model.buffers[0].data.size();
            bufferView.byteLength = uvs.size() * sizeof(float);
            bufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER;
            
            size_t bufferOffset = model.buffers[0].data.size();
            model.buffers[0].data.resize(bufferOffset + bufferView.byteLength);
            memcpy(model.buffers[0].data.data() + bufferOffset, uvs.data(), bufferView.byteLength);
            
            int uvBufferViewIndex = model.bufferViews.size();
            model.bufferViews.push_back(bufferView);
            
            tinygltf::Accessor accessor;
            accessor.bufferView = uvBufferViewIndex;
            accessor.byteOffset = 0;
            accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT;
            accessor.count = uvs.size() / 2;
            accessor.type = TINYGLTF_TYPE_VEC2;
            
            int uvAccessorIndex = model.accessors.size();
            model.accessors.push_back(accessor);
            
            primitive.attributes["TEXCOORD_0"] = uvAccessorIndex;
        }
        
        // 创建并添加索引buffer
        {
            tinygltf::BufferView bufferView;
            bufferView.buffer = 0;
            bufferView.byteOffset = model.buffers[0].data.size();
            bufferView.byteLength = indices.size() * sizeof(unsigned int);
            bufferView.target = TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER;
            
            size_t bufferOffset = model.buffers[0].data.size();
            model.buffers[0].data.resize(bufferOffset + bufferView.byteLength);
            memcpy(model.buffers[0].data.data() + bufferOffset, indices.data(), bufferView.byteLength);
            
            int indexBufferViewIndex = model.bufferViews.size();
            model.bufferViews.push_back(bufferView);
            
            tinygltf::Accessor accessor;
            accessor.bufferView = indexBufferViewIndex;
            accessor.byteOffset = 0;
            accessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT;
            accessor.count = indices.size();
            accessor.type = TINYGLTF_TYPE_SCALAR;
            
            primitive.indices = model.accessors.size();
            model.accessors.push_back(accessor);
        }
        
        // 设置材质
        tinygltf::Material material;
        material.pbrMetallicRoughness.baseColorFactor = {
            item.first.r, item.first.g, item.first.b, item.first.a
        };
        material.name = item.first.name;
        material.pbrMetallicRoughness.metallicFactor = 0.0;
        material.pbrMetallicRoughness.roughnessFactor = 1.0;
        
        if (!item.first.imageUri.empty()) {
            std::string processedTexturePath = ProcessTexture(item.first.imageUri);
            
            tinygltf::Image gltfImage;
            gltfImage.uri = processedTexturePath;
            int imageIndex = model.images.size();
            model.images.push_back(gltfImage);
            
            tinygltf::Texture gltfTexture;
            gltfTexture.source = imageIndex;
            int textureIndex = model.textures.size();
            model.textures.push_back(gltfTexture);
            
            material.pbrMetallicRoughness.baseColorTexture.index = textureIndex;
        }
        
        primitive.material = model.materials.size();
        model.materials.push_back(material);
        
        mesh.primitives.push_back(primitive);
        model.meshes.push_back(mesh);
        
        tinygltf::Node node;
        node.mesh = model.meshes.size() - 1;
        model.nodes.push_back(node);
        model.scenes[0].nodes.push_back(model.nodes.size() - 1);
    }
    
    // 写入 GLTF 文件
    tinygltf::TinyGLTF gltf;
    bool ret = gltf.WriteGltfSceneToFile(&model, outputPath,
                                        true,  // embedImages
                                        true,  // embedBuffers
                                        true,  // prettyPrint
                                        false); // writeBinary
    
    return ret ? 0 : 1;
}
// ... existing code ...

构建和运行

可参照项目地址
转换的文件显示如下

在这里插入图片描述

总结

当前解析完成的gltf 的大小是原 skp 文件的2~3倍。目前 draco 压缩还没有引入,后续引入 draco 压缩,解析的速度上会有一些影响,但 gltf 的大小会大大降低。


skp2gltf 源码地址,点击获取,也可以在github项目找到交流联系方式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值