这份代码的原始例子比较不好参考,创建一个三角形还比较好参考,创建多个三角形或者元素就有点懵。所以我做了个更通用的例子(即创建多个mesh)作为使用这个gltf sdk的样例。
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include <GLTFSDK/GLTF.h>
#include <GLTFSDK/BufferBuilder.h>
#include <GLTFSDK/GLTFResourceWriter.h>
#include <GLTFSDK/GLBResourceWriter.h>
#include <GLTFSDK/IStreamWriter.h>
#include <GLTFSDK/Serialize.h>
// Replace this with <filesystem> (and use std::filesystem rather than
// std::experimental::filesystem) if your toolchain fully supports C++17
#include <experimental/filesystem>
#include <fstream>
#include <sstream>
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <map>
using namespace std;
using namespace Microsoft::glTF;
namespace
{
// The glTF SDK is decoupled from all file I/O by the IStreamWriter (and IStreamReader)
// interface(s) and the C++ stream-based I/O library. This allows the glTF SDK to be used in
// sandboxed environments, such as WebAssembly modules and UWP apps, where any file I/O code
// must be platform or use-case specific.
class StreamWriter : public IStreamWriter
{
public:
StreamWriter(std::experimental::filesystem::path pathBase) : m_pathBase(std::move(pathBase))
{
assert(m_pathBase.has_root_path());
}
// Resolves the relative URIs of any external resources declared in the glTF manifest
std::shared_ptr<std::ostream> GetOutputStream(const std::string& filename) const override
{
// In order to construct a valid stream:
// 1. The filename argument will be encoded as UTF-8 so use filesystem::u8path to
// correctly construct a path instance.
// 2. Generate an absolute path by concatenating m_pathBase with the specified filename
// path. The filesystem::operator/ uses the platform's preferred directory separator
// if appropriate.
// 3. Always open the file stream in binary mode. The glTF SDK will handle any text
// encoding issues for us.
auto streamPath = m_pathBase / std::experimental::filesystem::u8path(filename);
auto stream = std::make_shared<std::ofstream>(streamPath, std::ios_base::binary);
// Check if the stream has no errors and is ready for I/O operations
if (!stream || !(*stream))
{
throw std::runtime_error("Unable to create a valid output stream for uri: " + filename);
}
return stream;
}
private:
std::experimental::filesystem::path m_pathBase;
};
std::string AddMaterial(Document& document, Color4& color4) {
// Construct a Material
Material material;
material.metallicRoughness.baseColorFactor = color4;
material.metallicRoughness.metallicFactor = 0.2f;
material.metallicRoughness.roughnessFactor = 0.4f;
material.doubleSided = true;
// Add it to the Document and store the generated ID
auto materialId = document.materials.Append(std::move(material), AppendIdPolicy::GenerateOnEmpty).id;
return materialId;
}
//添加顶点索引Accessor
string AddIndexAccessor(BufferBuilder& bufferBuilder, const vector<uint16_t> &indices) {
// 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);
// Add an Accessor for the indices
// Copy the Accessor's id - subsequent calls to AddAccessor may invalidate the returned reference
return bufferBuilder.AddAccessor(indices, { TYPE_SCALAR, COMPONENT_UNSIGNED_SHORT }).id;
}
//添加顶点Accessor
string AddPositionsAccessor(BufferBuilder& bufferBuilder,const vector<float> &positions) {
// Create a BufferView with target ARRAY_BUFFER (as it will reference vertex attribute data)
bufferBuilder.AddBufferView(BufferViewTarget::ARRAY_BUFFER);
// Add an Accessor for the positions
//std::vector<float> positions = {
// 0.0f, 0.0f, 0.0f, // Vertex 0
// 1.0f, 0.0f, 0.0f, // Vertex 1
// 0.0f, 1.0f, 0.0f // Vertex 2
//};
std::vector<float> minValues(3U, std::numeric_limits<float>::max());
std::vector<float> maxValues(3U, std::numeric_limits<float>::lowest());
const size_t positionCount = positions.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(positions[i], minValues[j]);
maxValues[j] = std::max(positions[i], maxValues[j]);
}
return bufferBuilder.AddAccessor(positions, { TYPE_VEC3, COMPONENT_FLOAT, false, std::move(minValues), std::move(maxValues) }).id;
}
void AddNode(Document& document, string &meshId, Scene &scene) {
Node node;
node.meshId = meshId;
// Add it to the Document and store the generated ID
auto nodeId = document.nodes.Append(std::move(node), AppendIdPolicy::GenerateOnEmpty).id;
scene.nodes.push_back(nodeId);
}
void AddMesh(Document& document,string &materialId,string &accessorIdIndices,string &accessorIdPositions, Scene &scene) {
MeshPrimitive meshPrimitive;
meshPrimitive.materialId = materialId;
meshPrimitive.indicesAccessorId = accessorIdIndices;
meshPrimitive.attributes[ACCESSOR_POSITION] = accessorIdPositions;
// 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;
AddNode(document, meshId, scene);
}
void CreateTrianglesResources(Document& document, BufferBuilder& bufferBuilder)
{
std::string accessorIdIndices;
std::string accessorIdPositions;
std::string accessorIdIndices2;
std::string accessorIdPositions2;
// 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);
#pragma region 添加索引Accessor
std::vector<uint16_t> indices = {
0U, 1U, 2U
};
accessorIdIndices = AddIndexAccessor(bufferBuilder,indices);
#pragma endregion
#pragma region 添加顶点Accessor
// Add an Accessor for the positions
std::vector<float> positions = {
0.0f, 0.0f, 0.0f, // Vertex 0
1.0f, 0.0f, 0.0f, // Vertex 1
0.0f, 1.0f, 0.0f // Vertex 2
};
accessorIdPositions = AddPositionsAccessor(bufferBuilder, positions);
#pragma endregion
#pragma region 添加索引Accessor
std::vector<uint16_t> indices2 = {
0U, 1U, 2U
};
accessorIdIndices2 = AddIndexAccessor(bufferBuilder, indices2);
#pragma endregion
#pragma region 添加顶点Accessor
// Add an Accessor for the positions
std::vector<float> positions2 = {
1.0f, 0.0f, 0.0f, // Vertex 0
2.0f, 0.0f, 0.0f, // Vertex 1
1.0f, 1.0f, 0.0f // Vertex 2
};
accessorIdPositions2 = AddPositionsAccessor(bufferBuilder, positions2);
#pragma endregion
// Add all of the Buffers, BufferViews and Accessors that were created using BufferBuilder to
// the Document. Note that after this point, no further calls should be made to BufferBuilder
bufferBuilder.Output(document);
// Construct a Material
Color4 color(1.0f, 0.0f, 0.0f, 1.0f);
auto materialId = AddMaterial(document, color);
Color4 color2(0.0f, 1.0f, 0.0f, 1.0f);
auto materialId2 = AddMaterial(document, color2);
// 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.
Scene scene;
AddMesh(document, materialId, accessorIdIndices, accessorIdPositions, scene);
AddMesh(document, materialId2, accessorIdIndices2, accessorIdPositions2, scene);
// 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);
}
void SerializeTriangle(const std::experimental::filesystem::path& path)
{
// Pass the absolute path, without the filename, to the stream writer
auto streamWriter = std::make_unique<StreamWriter>(path.parent_path());
std::experimental::filesystem::path pathFile = path.filename();
std::experimental::filesystem::path pathFileExt = pathFile.extension();
auto MakePathExt = [](const std::string& ext)
{
return "." + ext;
};
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");
}
// 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));
CreateTrianglesResources(document, bufferBuilder);
//CreateTriangle2Entities(document, accessorIdIndices, accessorIdPositions);
//CreateTriangle2Resources(document, bufferBuilder, accessorIdIndices, accessorIdPositions);
//CreateTriangle2Entities(document, accessorIdIndices, accessorIdPositions);
std::string manifest;
try
{
// Serialize the glTF Document into a JSON manifest
manifest = Serialize(document, SerializeFlags::Pretty);
}
catch (const GLTFException& ex)
{
std::stringstream ss;
ss << "Microsoft::glTF::Serialize failed: ";
ss << ex.what();
throw std::runtime_error(ss.str());
}
auto& gltfResourceWriter = bufferBuilder.GetResourceWriter();
if (auto glbResourceWriter = dynamic_cast<GLBResourceWriter*>(&gltfResourceWriter))
{
glbResourceWriter->Flush(manifest, pathFile.u8string()); // A GLB container isn't created until the GLBResourceWriter::Flush member function is called
}
else
{
gltfResourceWriter.WriteExternal(pathFile.u8string(), manifest); // Binary resources have already been written, just need to write the manifest
}
}
}
int main()
{
const char* argv[1] = {"D:\\test.gltf" };
try
{
std::experimental::filesystem::path path = argv[0U];
if (path.is_relative())
{
auto pathCurrent = std::experimental::filesystem::current_path();
// Convert the relative path into an absolute path by appending the command line argument to the current path
pathCurrent /= path;
pathCurrent.swap(path);
}
if (!path.has_filename())
{
throw std::runtime_error("Command line argument path has no filename");
}
if (!path.has_extension())
{
throw std::runtime_error("Command line argument path has no filename extension");
}
SerializeTriangle(path);
}
catch (const std::runtime_error& ex)
{
std::cerr << "Error! - ";
std::cerr << ex.what() << "\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}