一、前言
网上的教程均为搭配opengl使用,如果单纯想读取模型数据,资料就比较少了。先放出相关链接:
1、gltf规范文档:glTF™ 2.0 Specification (khronos.org)
2、gltf在线模型查看器 :glTF Viewer (donmccurdy.com)
3、tinygltf:GitHub - syoyo/tinygltf: Header only C++11 tiny glTF 2.0 library
4、vulkan使用tinygltf官方示例:GitHub - SaschaWillems/Vulkan-glTF-PBR: Physical based rendering with Vulkan using glTF 2.0 models
二、使用
1、初始化
首先引入头文件:
#define TINYGLTF_IMPLEMENTATION
#include <tiny_gltf.h>再加载文件,注意glb格式和gltf格式调用的函数不同:
tinygltf::Model model;
tinygltf::TinyGLTF loader;
std::string err;
std::string warn;
bool load_ok;
if(is_glb)
	load_ok = loader.LoadBinaryFromFile(&model, &err, &warn, std::string{ path_name });
else
	load_ok = loader.LoadASCIIFromFile(&model, &err, &warn, std::string{ path_name });
if (!warn.empty()) 
	log_warn("{}", warn);
if (!err.empty())
	log_err("{}", err);
if (!load_ok)
{
	log_err("解析gltf格式失败:{}", err);
	return {};
}二、读取材质
其中 mo_material是我们自定义的结构,用于接收读取到的数据。由于之前的光照模型是 Blinn-Phong,而现在更通用的是pbr,以下代码可能不适用你的需求。
之所以我将纹理导出、因为我要进行二次处理,生成自定义的模型格式。你可以不保存到文件,而是直接使用。
通过查看 tinygltf::PbrMetallicRoughness类型的定义,可以知道提供哪些纹理和材质数据。
//------------------获取材质-------------------
mo_material.resize(model.materials.size());
for (size_t i = 0; i < model.materials.size(); ++i)
{
	tinygltf::Material& m = model.materials[i];
	log_info("材质:{},{}", i, m.name);
	tinygltf::PbrMetallicRoughness& pbr = m.pbrMetallicRoughness;
	ModelObjectMaterial& material = mo_material[i];
	material._materialName = m.name;
	material._material._diffuseAlbedo = { 
		(float)pbr.baseColorFactor[0],
		(float)pbr.baseColorFactor[1],
		(float)pbr.baseColorFactor[2], 1.0f };
	material._material._fresnelR0 = { 
		(float)pbr.metallicFactor, (float)pbr.metallicFactor, (float)pbr.metallicFactor };
	material._material._roughness = (float)pbr.roughnessFactor;
	// 纹理导出
	auto fn_texture = [&](int index, std::string_view tag) {
		if (index == -1)
			return std::string{};
		tinygltf::Image& img = model.images[index];
		if (!img.uri.empty())
			return img.uri;
		std::string path_name = fmt::format("{}{}{}#{}.png",
			File::PathTemp(), res_name, tag, i);
		stbi_write_png(CodeCvt::Utf8ToMultiByte(path_name).c_str(),
			img.width, img.height,
			4, img.image.data(), 0);
		return path_name;
		};
	material._pathTexDiffuse = fn_texture(pbr.baseColorTexture.index, "");
	material._pathTexNormal = fn_texture(m.normalTexture.index, "_normal");
}我的做法是按材质合并网格(不知道会不会影响骨骼动画,后面再说),你也可以简单处理,按 tinygltf::Primitive为单位为绘制。prim里不直接存数据,而是通过 Accessor访问,然后通过 Accessor取得 BufferView,然后通过 BufferView取得真正的顶点数据。
注意数据首地址为 buf.data.data() + buf_view.byteOffset + accessor.byteOffset,单个顶点偏移为 int byte_stride = accessor.ByteStride(buf_view)。
注意我这里没有过多的 assert的检查,这里的代码还需要尝试更多模型,才能具有可靠性。
		for (tinygltf::Mesh& mesh : model.meshes)
		{
			for (tinygltf::Primitive& prim : mesh.primitives)
			{
				PrimData& prim_data = map_material[prim.material];
				size_t vertex_offset = prim_data._allVertex.size();
				// 索引
				{
					tinygltf::Accessor accessor = model.accessors[prim.indices];
					tinygltf::BufferView buf_view = model.bufferViews[accessor.bufferView];
					tinygltf::Buffer& buf = model.buffers[buf_view.buffer];
					int byte_stride = accessor.ByteStride(buf_view);
					unsigned char* p = buf.data.data() + buf_view.byteOffset + accessor.byteOffset;
					assert(byte_stride == 4);
					for (size_t i = 0; i < accessor.count; ++i)
					{
						uint32_t index = *(uint32_t*)p; 
						prim_data._allIndex.push_back(vertex_offset + index);
						p += byte_stride;
					}
				}
				// 位置
				auto iter_pos = prim.attributes.find("POSITION");
				if(iter_pos != prim.attributes.end())
				{
					tinygltf::Accessor accessor = model.accessors[iter_pos->second];
					tinygltf::BufferView buf_view = model.bufferViews[accessor.bufferView];
					tinygltf::Buffer& buf = model.buffers[buf_view.buffer];
					prim_data._allVertex.resize(vertex_offset + accessor.count);
					int byte_stride = accessor.ByteStride(buf_view);
					unsigned char* p = buf.data.data() + buf_view.byteOffset + accessor.byteOffset;
					for (size_t i = 0; i < accessor.count; ++i)
					{
						Vertex& v = prim_data._allVertex[vertex_offset + i];
						v._pos = *(Position3*)p;
						p += byte_stride;
					}
				}
				// uv
				auto iter_uv = prim.attributes.find("TEXCOORD_0");
				if (iter_uv != prim.attributes.end())
				{
					tinygltf::Accessor accessor = model.accessors[iter_uv->second];
					tinygltf::BufferView buf_view = model.bufferViews[accessor.bufferView];
					tinygltf::Buffer& buf = model.buffers[buf_view.buffer];
					int byte_stride = accessor.ByteStride(buf_view);
					unsigned char* p = buf.data.data() + buf_view.byteOffset + accessor.byteOffset;
					for (size_t i = 0; i < accessor.count; ++i)
					{
						Vertex& v = prim_data._allVertex[vertex_offset + i];
						v._uv = *(Position2*)p;
						p += byte_stride;
					}
				}
				// normal
				auto iter_normal = prim.attributes.find("NORMAL");
				if (iter_normal != prim.attributes.end())
				{
					tinygltf::Accessor accessor = model.accessors[iter_normal->second];
					tinygltf::BufferView buf_view = model.bufferViews[accessor.bufferView];
					tinygltf::Buffer& buf = model.buffers[buf_view.buffer];
					int byte_stride = accessor.ByteStride(buf_view);
					unsigned char* p = buf.data.data() + buf_view.byteOffset + accessor.byteOffset;
					for (size_t i = 0; i < accessor.count; ++i)
					{
						Vertex& v = prim_data._allVertex[vertex_offset + i];
						v._normal = *(Position3*)p;
						p += byte_stride;
					}
				}
				// TANGENT
				auto iter_tangent = prim.attributes.find("TANGENT");
				if (iter_tangent != prim.attributes.end())
				{
					tinygltf::Accessor accessor = model.accessors[iter_tangent->second];
					tinygltf::BufferView buf_view = model.bufferViews[accessor.bufferView];
					tinygltf::Buffer& buf = model.buffers[buf_view.buffer];
					int byte_stride = accessor.ByteStride(buf_view);
					unsigned char* p = buf.data.data() + buf_view.byteOffset + accessor.byteOffset;
					for (size_t i = 0; i < accessor.count; ++i)
					{
						Vertex& v = prim_data._allVertex[vertex_offset + i];
						v._tangent = *(Position3*)p;
						p += byte_stride;
					}
				}
			}
		}三、截图
这里没有处理骨骼动画和光照,不过对于学习 tinygltf的基本用法,应该是足够了!

项目地址:dl: C++ drawing library (gitee.com)


















