微软glTF-SDK代码示例及源码研究

21 篇文章 9 订阅
18 篇文章 2 订阅

上一篇:GLtf读写库的初步研究

1.glTF-SDK现状及问题

1、接口使用较为方便;有简单示例;
2、研究源码及示例,仅支持连续的addAccessor(),即连续的add绑定同一bufferView的Accessors,
比如很多个mesh,
(1)只能统一获取所有的indices(positions、uvs)再连续的addAccessories,绑定到一个indices的bufferView上;然后再绑定positions的,其次再绑定uvs...,这样流程不太 方便,占内存较大;
(2)对于每一个mesh的indices,都创建一个bufferView,这样也是官方示例所示,问题是会有很多的bufferView;
(3)结合上面两点:对于一部分meshes,创建一个bufferView,这样在上面两点之间做一个平衡;
(4)如果想实现第一点的效果,但是不想用哪种方式,那么可以在glTF-SDK的源码基础上新增一些接口和行为,来满足,具体的,行为上来说,可以交叉的add不同类型的(比如mesh0_indices,mesh0_positions,mesh0_uvs,mesh1_indices,mesh1_positions,mesh1_uvs...);在具体的,addAccessor()时不需要立即写入到stream,而是记录下来(将二进制数据和bufferView的offset等信息更新),最后统一(或自动的)写入stream;
目前在glTF-SDK基础是上做了个尝试,但是工作量有点大,还是暂时搁置;

2.glTF结构

建议要先看官方的specification:https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy

了解了格式之后,使用glTF-SDK会方便很多,

3.代码示例

其实glTF-SDK有序列化及反序列化gltf文件的简单示例(https://github.com/Microsoft/glTF-SDK),对于入门还好,但是对于实际使用还是不够的,现结合实际使用进行总结,

注意:结合官方示例看本文会理解的更好,本文也是基础官方示例,结合使用总结的,

resourceWriter:

std::unique_ptr<ResourceWriter> resourceWriter;

// If the file has a '.gltf' extension then create a GLTFResourceWriter
if (pathFileExt == MakePathExt(GLTF_EXTENSION))
{
    resourceWriter = std::make_unique<GLTFResourceWriter>(std::move(streamWriter));
}

// If the file has a '.glb' extension then create a GLBResourceWriter. This class derives
// from GLTFResourceWriter and adds support for writing manifests to a GLB container's
// JSON chunk and resource data to the binary chunk.
if (pathFileExt == MakePathExt(GLB_EXTENSION))
{
    resourceWriter = std::make_unique<GLBResourceWriter>(std::move(streamWriter));
}

if (!resourceWriter)
{
    throw std::runtime_error("Command line argument path filename extension must be .gltf or .glb");
}

Initialize Buffer & AddBuffer:

// The Document instance represents the glTF JSON manifest
    Document document;

// Use the BufferBuilder helper class to simplify the process of
// constructing valid glTF Buffer, BufferView and Accessor entities
BufferBuilder bufferBuilder(std::move(resourceWriter));

// Create all the resource data (e.g. triangle indices and
// vertex positions) that will be written to the binary buffer
const char* bufferId = nullptr;

// Specify the 'special' GLB buffer ID. This informs the GLBResourceWriter that it should use
// the GLB container's binary chunk (usually the desired buffer location when creating GLBs)
if (dynamic_cast<const GLBResourceWriter*>(&bufferBuilder.GetResourceWriter()))
{
    bufferId = GLB_BUFFER_ID;
}

// Create a Buffer - it will be the 'current' Buffer that all the BufferViews
// created by this BufferBuilder will automatically reference
bufferBuilder.AddBuffer(bufferId);

Prepare Data:

//  get data from mesh
std::vector<unsigned short> indexes;
std::vector<float> vertexes, normals, uvs;
GetMeshData(mesh, indexes, vertexes, normals, uvs);
std::vector<unsigned char> batchIds((unsigned char)(vertexes.size() / 3), batchId);

AddBufferView:

// Create a BufferView with target ARRAY_BUFFER (as it will reference vertex attribute data)
bufferBuilder.AddBufferView(BufferViewTarget::ARRAY_BUFFER);

std::vector<float> minValues(3U, (std::numeric_limits<float>::max)());
std::vector<float> maxValues(3U, std::numeric_limits<float>::lowest());

const size_t positionCount = vertexes.size();

// Accessor min/max properties must be set for vertex position data so calculate them here
for (size_t i = 0U, j = 0U; i < positionCount; ++i, j = (i % 3U))
{
    minValues[j] = (std::min)(vertexes[i], minValues[j]);
    maxValues[j] = (std::max)(vertexes[i], maxValues[j]);
}

accessorIdPositions = bufferBuilder.AddAccessor(vertexes, { TYPE_VEC3, COMPONENT_FLOAT, false, std::move(minValues), std::move(maxValues) }).id;

//  normals
bufferBuilder.AddBufferView(BufferViewTarget::ARRAY_BUFFER);
accessorIdNormals = bufferBuilder.AddAccessor(normals, { TYPE_VEC3, COMPONENT_FLOAT }).id;

//  uvs
bufferBuilder.AddBufferView(BufferViewTarget::ARRAY_BUFFER);
accessorIdUvs = bufferBuilder.AddAccessor(uvs, { TYPE_VEC2, COMPONENT_FLOAT }).id;

//  batchId
bufferBuilder.AddBufferView(BufferViewTarget::ELEMENT_ARRAY_BUFFER);
accessorIdBatchIds = bufferBuilder.AddAccessor(batchIds, { TYPE_SCALAR, COMPONENT_BYTE }).id;

// Create a BufferView with a target of ELEMENT_ARRAY_BUFFER (as it will reference index
// data) - it will be the 'current' BufferView that all the Accessors created by this
// BufferBuilder will automatically reference
bufferBuilder.AddBufferView(BufferViewTarget::ELEMENT_ARRAY_BUFFER);

// Copy the Accessor's id - subsequent calls to AddAccessor may invalidate the returned reference
accessorIdIndices = bufferBuilder.AddAccessor(indexes, { TYPE_SCALAR, COMPONENT_UNSIGNED_SHORT/*COMPONENT_UNSIGNED_SHORT*/ }).id;

node:

{
    "nodes": [
        {
            "name": "Car",
            "children": [1, 2, 3, 4]
        },
        {
            "name": "wheel_1"
        },
        {
            "name": "wheel_2"
        },
        {
            "name": "wheel_3"
        },
        {
            "name": "wheel_4"
        }        
    ]
}

支持如下两种transform形式,当然需要结合实际情况选用,博主用的就是第二种矩阵形式的,

需要注意矩阵存储是列优先的

{
    "nodes": [
        {
            "name": "Box",
            "rotation": [
                0,
                0,
                0,
                1
            ],
            "scale": [
                1,
                1,
                1
            ],
            "translation": [
                -17.7082,
                -11.4156,
                2.0922
            ]
        }
    ]
}
{
    "nodes": [
        {
            "name": "node-camera",
            "camera": 1,
            "matrix": [
                -0.99975,
                -0.00679829,
                0.0213218,
                0,
                0.00167596,
                0.927325,
                0.374254,
                0,
                -0.0223165,
                0.374196,
                -0.927081,
                0,
                -0.0115543,
                0.194711,
                -0.478297,
                1
            ]
        }
    ]
}

scene:

All nodes listed in scene.nodes array must be root nodes

//  scene & root node
Scene scene;
Node rootNode;
SetRootNodeTransform(rootNode.matrix);
rootNode.name = new char[5] { 'r', 'o', 'o', 't', '\0' };
......
//  element node
Node nodeElement;
nodeElement.name = itrItem->first;
......

//  mesh node
Node node;
node.meshId = meshId;
ConvertToMatrix(itrMesh->meshTrs, node.matrix);

// Add it to the Document and store the generated ID
auto nodeId = document.nodes.Append(std::move(node), AppendIdPolicy::GenerateOnEmpty).id;
nodeElement.children.push_back(nodeId);

auto elemNodeId = document.nodes.Append(std::move(nodeElement), AppendIdPolicy::GenerateOnEmpty).id;
rootNode.children.push_back(elemNodeId);

//  All nodes listed in scene.nodes array must be root nodes
auto rootNodeId = document.nodes.Append(std::move(rootNode), AppendIdPolicy::GenerateOnEmpty).id;
scene.nodes.push_back(rootNodeId);

// Add it to the Document, using a utility method that also sets the Scene as the Document's default
document.SetDefaultScene(std::move(scene), AppendIdPolicy::GenerateOnEmpty);

那么 meshId从何而来?

// Construct a MeshPrimitive. Unlike most types in glTF, MeshPrimitives are direct children
// of their parent Mesh entity rather than being children of the Document. This is why they
// don't have an ID member.
MeshPrimitive meshPrimitive;
meshPrimitive.materialId = materialId;
meshPrimitive.indicesAccessorId = itrMesh->accessorIdIndices;
meshPrimitive.attributes[ACCESSOR_POSITION] = itrMesh->accessorIdPositions;
meshPrimitive.attributes[ACCESSOR_NORMAL] = itrMesh->accessorIdNormals;
meshPrimitive.attributes["_BATCHID"] = itrMesh->accessorIdBatchIds;
meshPrimitive.attributes[ACCESSOR_TEXCOORD_0] = itrMesh->accessorIdUvs;
meshPrimitive.mode = MESH_TRIANGLES;

// Construct a Mesh and add the MeshPrimitive as a child
Mesh mesh;
mesh.primitives.push_back(std::move(meshPrimitive));

// Add it to the Document and store the generated ID
auto meshId = document.meshes.Append(std::move(mesh), AppendIdPolicy::GenerateOnEmpty).id;

 

material & texture:

//  initialize material
Material material;

//  image used by texture
Image image;
image.uri = fileName.c_str();
auto imageId = document.images.Append(image, AppendIdPolicy::GenerateOnEmpty).id;

//  sampler used by texture
Sampler sampler;
sampler.magFilter = MagFilterMode::MagFilter_LINEAR;
sampler.minFilter = MinFilterMode::MinFilter_LINEAR_MIPMAP_LINEAR;
sampler.wrapS = WrapMode::Wrap_REPEAT;
sampler.wrapT = WrapMode::Wrap_REPEAT;
auto samplerId = document.samplers.Append(sampler, AppendIdPolicy::GenerateOnEmpty).id;

//  texture
Texture texture;
texture.imageId = imageId;
texture.samplerId = samplerId;
auto textureId = document.textures.Append(texture, AppendIdPolicy::GenerateOnEmpty).id;
TextureInfo textureInfo;
textureInfo.texCoord = 0;
textureInfo.textureId = textureId;

//  texture transform
static string strKHRTextureTrs = "KHR_texture_transform";
if (document.extensionsUsed.find(strKHRTextureTrs) == document.extensionsUsed.end())
    document.extensionsUsed.insert(strKHRTextureTrs);
if (document.extensionsRequired.find(strKHRTextureTrs) == document.extensionsRequired.end())
    document.extensionsRequired.insert(strKHRTextureTrs);

......

string textureTrsContent;
GetTextureTransformExtension(textureData, textureTrsContent);
textureInfo.extensions["KHR_texture_transform"] = textureTrsContent;
material.metallicRoughness.baseColorTexture = textureInfo;

material.metallicRoughness.metallicFactor = 0.4f;
material.metallicRoughness.roughnessFactor = 0.3015f;
material.alphaMode = abs(itrMesh->material->getTransparency()) < 1e-5 ? ALPHA_OPAQUE : ALPHA_BLEND/*ALPHA_OPAQUE*/;
material.doubleSided = false;
//material.alphaCutoff = 1.0f - itrMesh->material->getTransparency();

// Add it to the Document and store the generated ID
auto materialId = document.materials.Append(std::move(material), AppendIdPolicy::GenerateOnEmpty).id;

看不懂?没关系,结合数据格式继续看~

 

这部分最好详细了解gltf2.0关于材质部分的格式之后再看代码,理解的更深,

上面表示texture transform用到了extension数据,glTF格式预定义了一些扩展格式:

https://github.com/KhronosGroup/glTF/blob/master/extensions/README.md

4.示例文件

{
	"asset": {
		"version": "2.0"
	},
	"accessors": [
		{
			"bufferView": 0,
			"componentType": 5126,
			"count": 180,
			"type": "VEC3",
			"max": [
				-0.8864412307739258,
				3.537320137023926,
				26.694475173950197
			],
			"min": [
				-55.02029800415039,
				-49.61228561401367,
				20.26502227783203
			]
		},
		{
			"bufferView": 1,
			"componentType": 5126,
			"count": 180,
			"type": "VEC3"
		},
		{
			"bufferView": 2,
			"componentType": 5126,
			"count": 180,
			"type": "VEC2"
		},
		{
			"bufferView": 3,
			"componentType": 5120,
			"count": 180,
			"type": "SCALAR"
		},
		{
			"bufferView": 4,
			"componentType": 5123,
			"count": 348,
			"type": "SCALAR"
		}
	],
	"bufferViews": [
		{
			"buffer": 0,
			"byteOffset": 0,
			"byteLength": 2160,
			"target": 34962
		},
		{
			"buffer": 0,
			"byteOffset": 2160,
			"byteLength": 2160,
			"target": 34962
		},
		{
			"buffer": 0,
			"byteOffset": 4320,
			"byteLength": 1440,
			"target": 34962
		},
		{
			"buffer": 0,
			"byteOffset": 5760,
			"byteLength": 180,
			"target": 34963
		},
		{
			"buffer": 0,
			"byteOffset": 5940,
			"byteLength": 696,
			"target": 34963
		}
	],
	"buffers": [
		{
			"byteLength": 6636
		}
	],
	"images": [
		{
			"uri": "Thermal_Moisture.Roof.Tiles.Spanish.png"
		}
	],
	"materials": [
		{
			"pbrMetallicRoughness": {
				"baseColorTexture": {
					"index": 0,
					"extensions": {
						"KHR_texture_transform": {
							"offset": [
								0.0,
								0.0
							],
							"rotation": 0.0,
							"scale": [
								0.6000000238418579,
								0.6000000238418579
							],
							"texCoord": 0
						}
					}
				},
				"metallicFactor": 0.4000000059604645,
				"roughnessFactor": 0.30149999260902407
			}
		}
	],
	"meshes": [
		{
			"primitives": [
				{
					"attributes": {
						"NORMAL": 1,
						"POSITION": 0,
						"_BATCHID": 3,
						"TEXCOORD_0": 2
					},
					"indices": 4,
					"material": 0
				}
			]
		}
	],
	"nodes": [
		{
			"mesh": 0
		},
		{
			"children": [
				0
			],
			"name": "356468"
		},
		{
			"children": [
				1
			],
			"matrix": [
				1.0,
				0.0,
				0.0,
				0.0,
				0.0,
				0.0,
				-1.0,
				0.0,
				0.0,
				1.0,
				0.0,
				0.0,
				0.0,
				0.0,
				0.0,
				1.0
			],
			"name": "root"
		}
	],
	"samplers": [
		{
			"magFilter": 9729,
			"minFilter": 9987
		}
	],
	"scenes": [
		{
			"nodes": [
				2
			]
		}
	],
	"textures": [
		{
			"sampler": 0,
			"source": 0
		}
	],
	"scene": 0,
	"extensionsUsed": [
		"KHR_texture_transform"
	],
	"extensionsRequired": [
		"KHR_texture_transform"
	]
}

5.总结

 

 

 

 

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值