源码地址:点击跳转吗
tinygltf
是一个轻量级的、用 C++ 编写的开源库,用于加载和保存 glTF 文件格式(JSON 和二进制格式)。glTF(GL Transmission Format)是一个开放的、专为高效传输和加载 3D 内容而设计的文件格式,广泛应用于网页、游戏、虚拟现实(VR)和增强现实(AR)等领域。
tinygltf
的主要特点:
-
轻量级和高效:
tinygltf
被设计为轻量级的库,提供了简单的 API 和高效的实现,适合需要快速加载 glTF 文件的应用。- 它支持 glTF 2.0 标准,能够处理包括模型、纹理、动画、材质等在内的完整 3D 内容。
-
支持格式:
tinygltf
支持加载和保存两种主要的 glTF 文件格式:JSON 格式(.gltf)和二进制格式(.glb)。其中,.glb
是将 glTF 文件内容和资源(如纹理)打包为一个二进制文件的格式。- 它还支持读取 glTF 文件中的网格、节点、材质、纹理、动画等数据。
-
跨平台:
- 作为一个 C++ 库,
tinygltf
支持多平台,包括 Windows、Linux 和 macOS。
- 作为一个 C++ 库,
-
易于集成:
tinygltf
是一个单头文件库,这使得它非常容易集成到其他项目中,无需复杂的构建配置。
-
无外部依赖:
tinygltf
不依赖于任何外部库,只有标准 C++ 库的支持,因此它的依赖非常少,简化了集成和使用的复杂性。
核心功能:
- 加载 glTF 文件:从磁盘加载
.gltf
或.glb
文件,解析其中的 3D 内容。 - 保存 glTF 文件:将 3D 内容保存为
.gltf
或.glb
格式。 - 处理 纹理、网格、动画:解析文件中的材质、纹理、网格、骨骼动画、相机和灯光等元素。
- 支持扩展:
tinygltf
支持 glTF 文件格式的扩展,允许开发者使用自定义的功能。
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include "glad/glad.h"
#include "tiny_gltf.h"
int main(int argc, char *argv[])
{
tinygltf::Model model;
tinygltf::TinyGLTF loader;
std::string err;
std::string warn;
bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, "zhiche.glb");
if (!warn.empty()) qWarning() << warn.c_str();
if (!err.empty()) qCritical() << err.c_str();
if (!ret) {
qCritical() << " load error!";
throw std::runtime_error("Failed to load glTF model!");
}
qDebug() << "meshes.size: " << model.meshes.size();
glm::vec3 translation(0.0);
glm::vec3 rotation(0.0, 0.0, 0.0);
glm::vec3 scale(1.0);
/** @note Assume one Node just one Mesh */
int nodeIndex = -1;
QMap<int, QString> s_meshNames;
QMap<int, QVector<glm::vec3>> s_vertices;
QMap<int, QVector<unsigned int>> s_indices;
QMap<int, QVector<glm::vec2>> s_texcoords;
QMap<int, std::tuple<glm::vec3, glm::vec3, glm::vec3>> s_transforms;
QMap<int, Material> s_materials;
QMap<int, QVector<glm::vec3>> s_normals;
for (const auto& node : model.nodes)
{// #Node
if (!node.translation.empty())
translation = glm::vec3(node.translation[0], node.translation[1], node.translation[2]);
if (!node.rotation.empty())
{
const auto& quaternion = glm::quat(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]);
rotation = glm::eulerAngles(quaternion);
}
if (!node.scale.empty())
scale = glm::vec3(node.scale[0], node.scale[1], node.scale[2]);
++nodeIndex;
// !Assume one Node just one Mesh
const auto& mesh = model.meshes[node.mesh];// #Mesh
int meshIndex = nodeIndex;
s_meshNames.insert(meshIndex, QString::fromStdString(mesh.name));
s_transforms.insert(meshIndex, std::make_tuple(translation, rotation, scale));
for (const auto& primitive : mesh.primitives)
{// #Primitive
if (primitive.attributes.find("POSITION") != primitive.attributes.end())
{// #Vertex
const auto& positionAccessor = model.accessors[primitive.attributes.at("POSITION")];
const auto& positionBufferView = model.bufferViews[positionAccessor.bufferView];
const auto& positionBuffer = model.buffers[positionBufferView.buffer];
QVector<glm::vec3> _vertices;
const auto* vertices = reinterpret_cast<const float*>(&positionBuffer.data[positionBufferView.byteOffset + positionAccessor.byteOffset]);
for (size_t i = 0; i < positionAccessor.count; ++i)
{
const auto& P = glm::vec3(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]);
_vertices << P;
}
s_vertices.insert(meshIndex, _vertices);
}
if (primitive.attributes.find("NORMAL") != primitive.attributes.end())
{// #Normal
const auto& normalAccessor = model.accessors[primitive.attributes.at("NORMAL")];
const auto& normalBufferView = model.bufferViews[normalAccessor.bufferView];
const auto& normalBuffer = model.buffers[normalBufferView.buffer];
QVector<glm::vec3> _normals;
const float* normals = reinterpret_cast<const float*>(&normalBuffer.data[normalBufferView.byteOffset + normalAccessor.byteOffset]);
for (size_t i = 0; i < normalAccessor.count; ++i)
_normals << glm::vec3(normals[i * 3], normals[i * 3 + 1], normals[i * 3 + 2]);
s_normals.insert(meshIndex, _normals);
}
if (primitive.attributes.find("TEXCOORD_0") != primitive.attributes.end())
{// #Texcoord
const auto& texcoordAccessor = model.accessors[primitive.attributes.at("TEXCOORD_0")];
const auto& texcoordBufferView = model.bufferViews[texcoordAccessor.bufferView];
const auto& texcoordBuffer = model.buffers[texcoordBufferView.buffer];
QVector<glm::vec2> _texcoords;
const float* texcoords = reinterpret_cast<const float*>(&texcoordBuffer.data[texcoordBufferView.byteOffset + texcoordAccessor.byteOffset]);
for (size_t i = 0; i < texcoordAccessor.count; ++i)
_texcoords << glm::vec2(texcoords[i * 2], texcoords[i * 2 + 1]);
s_texcoords.insert(meshIndex, _texcoords);
}
if (primitive.indices >= 0)
{// #Index
const auto& indexAccessor = model.accessors[primitive.indices];
const auto& indexBufferView = model.bufferViews[indexAccessor.bufferView];
const auto& indexBuffer = model.buffers[indexBufferView.buffer];
QVector<GLuint> _indices;
if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT)
{
const unsigned short* indices = reinterpret_cast<const unsigned short*>(&indexBuffer.data[indexBufferView.byteOffset + indexAccessor.byteOffset]);
for (size_t i = 0; i < indexAccessor.count; ++i)
_indices << indices[i];
} else if (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT)
{
const unsigned int* indices = reinterpret_cast<const unsigned int*>(&indexBuffer.data[indexBufferView.byteOffset + indexAccessor.byteOffset]);
for (size_t i = 0; i < indexAccessor.count; ++i)
_indices << indices[i];
}
s_indices.insert(meshIndex, _indices);
}
if (primitive.material >= 0 && primitive.material < model.materials.size())
{
Material _material;
const auto& material = model.materials[primitive.material];
if (material.pbrMetallicRoughness.baseColorFactor.size() == 4)
{// #Material (if size is not 4, it means bad datas in glTF file)
float r = material.pbrMetallicRoughness.baseColorFactor[0];
float g = material.pbrMetallicRoughness.baseColorFactor[1];
float b = material.pbrMetallicRoughness.baseColorFactor[2];
float a = material.pbrMetallicRoughness.baseColorFactor[3];
_material.setBaseColor(QColor::fromRgbF(r, g, b, a));
}
if (material.pbrMetallicRoughness.metallicFactor > 0.0f)// [0, 1]
_material.setMetallic(material.pbrMetallicRoughness.metallicFactor);
else
_material.setMetallic(0);// no metallic
if (material.pbrMetallicRoughness.roughnessFactor > 0.0f)// [0, 1]
_material.setRoughness(material.pbrMetallicRoughness.roughnessFactor);
else
_material.setRoughness(0);// no roughness
s_materials.insert(meshIndex, _material);
}
else
qInfo() << "read glTF no Material: define Default. # modelType";
}
}// end for #Node
return 0;
}
demo:点击跳转
觉得有帮助的话,打赏一下呗。。
需要商务合作(定制程序)的欢迎私信!!