如何基于tinygltf将自定义三角网格序列化为glTF/GLB格式?
用tinygltf批量导出程序生成三角网格的正确姿势
先给结论:单个三角形单独建缓冲区/访问器不可取
可行是可行,但完全没必要——glTF是为高效传输设计的,每个三角形单独搞一套缓冲区、访问器,会让文件里塞满重复的元数据,体积暴涨,加载时引擎也要反复处理冗余结构,性能拉胯,属于典型的反模式。正确做法是把所有三角形的顶点数据合并到同一个缓冲区,用一套访问器搞定;如果有不同材质,再按材质分组处理。
泛化处理批量三角网格的步骤
假设你已经有批量三角数据:比如一个std::vector<float>,按x1,y1,z1,x2,y2,z2,x3,y3,z3,...的顺序存储所有三角形的顶点坐标。
1. 合并所有顶点数据到单个缓冲区
不用给每个三角形单独分配缓冲区,把所有顶点数据拼进一个大数组:
std::vector<float> all_vertices; // 遍历所有三角点组,将每个点的x/y/z依次存入数组 for (const auto& triangle : all_triangles) { all_vertices.push_back(triangle.p1.x); all_vertices.push_back(triangle.p1.y); all_vertices.push_back(triangle.p1.z); all_vertices.push_back(triangle.p2.x); all_vertices.push_back(triangle.p2.y); all_vertices.push_back(triangle.p2.z); all_vertices.push_back(triangle.p3.x); all_vertices.push_back(triangle.p3.y); all_vertices.push_back(triangle.p3.z); }
2. 创建单个缓冲区、BufferView和访问器
将合并后的顶点数据写入tinygltf的缓冲区,再用BufferView和访问器描述数据格式:
tinygltf::Model model; // 创建缓冲区,写入顶点数据 tinygltf::Buffer& buffer = model.buffers.emplace_back(); buffer.data.resize(all_vertices.size() * sizeof(float)); std::memcpy(buffer.data.data(), all_vertices.data(), buffer.data.size()); buffer.name = "vertex_buffer"; // 创建BufferView,连接缓冲区与访问器 tinygltf::BufferView& bufferView = model.bufferViews.emplace_back(); bufferView.buffer = 0; bufferView.byteOffset = 0; bufferView.byteLength = buffer.data.size(); bufferView.target = TINYGLTF_TARGET_ARRAY_BUFFER; bufferView.name = "vertex_buffer_view"; // 创建访问器,定义顶点数据的类型、数量等 tinygltf::Accessor& accessor = model.accessors.emplace_back(); accessor.bufferView = 0; accessor.byteOffset = 0; accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; accessor.count = all_vertices.size() / 3; // 总顶点数(每个顶点3个float) accessor.type = TINYGLTF_TYPE_VEC3; // 可选:计算顶点包围盒,填入min/max,方便引擎做优化 accessor.minValues = {std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max()}; accessor.maxValues = {std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest()}; for (size_t i = 0; i < all_vertices.size(); i += 3) { accessor.minValues[0] = std::min(accessor.minValues[0], all_vertices[i]); accessor.minValues[1] = std::min(accessor.minValues[1], all_vertices[i+1]); accessor.minValues[2] = std::min(accessor.minValues[2], all_vertices[i+2]); accessor.maxValues[0] = std::max(accessor.maxValues[0], all_vertices[i]); accessor.maxValues[1] = std::max(accessor.maxValues[1], all_vertices[i+1]); accessor.maxValues[2] = std::max(accessor.maxValues[2], all_vertices[i+2]); } accessor.name = "vertex_accessor";
3. 创建网格与图元
所有三角形共用同一批顶点数据,只需创建一个图元,模式设为TINYGLTF_MODE_TRIANGLES,引擎会自动按每3个顶点组成一个三角形:
tinygltf::Primitive primitive; primitive.attributes["POSITION"] = 0; // 指向上面创建的访问器索引 primitive.mode = TINYGLTF_MODE_TRIANGLES; tinygltf::Mesh mesh; mesh.primitives.push_back(primitive); mesh.name = "batch_triangles_mesh"; model.meshes.push_back(mesh);
4. 添加节点并导出
把网格挂到节点上,导出为GLB(二进制格式更紧凑)或glTF:
tinygltf::Node node; node.mesh = 0; // 指向上面创建的网格索引 model.nodes.push_back(node); model.scenes[0].nodes.push_back(0); // 将节点加入默认场景 // 导出GLB tinygltf::Writer writer; writer.WriteGLB("output.glb", model, true, true); // 若要导出glTF+bin格式,用下面的代码 // writer.WriteSceneToFile(&model, "output.gltf", tinygltf::WriterOptions());
按材质分组的特殊情况
如果批量三角形有不同材质,就把同材质的三角形顶点合并到同一个缓冲区,每个材质对应一个Primitive,再将这些Primitive放到同一个Mesh中(或不同Mesh,按需选择),既保证效率,又能区分材质渲染。
内容的提问来源于stack exchange,提问作者Lorah Attkins
相关产品推荐
相关产品推荐

