As i promised before, i've extracted majority gltf loader code from my engine.
First is the mesh loader.
Like i said before, gltf models may not have tangents so we'll use this library
https://github.com/mlimper/tgen.
In tgen.h file, we need to change some code from
Code: Select all
typedef std::size_t VIndexT;
typedef double RealT;
into
Code: Select all
typedef std::uint32_t VIndexT;
typedef float RealT;
Now we are ready.
This is how i create mesh from scratch:
Code: Select all
Ogre::IndexBufferPacked* CreateManualIndexBuffer(Ogre::IndexBufferPacked::IndexType indexType, const void* data, int indexCount)
{
auto typeSize = indexType == Ogre::IndexBufferPacked::IT_16BIT ? sizeof(std::uint16_t) : sizeof(std::uint32_t);
auto simdBuffer = OGRE_MALLOC_SIMD(indexCount * typeSize, Ogre::MEMCATEGORY_GEOMETRY);
std::memcpy(simdBuffer, data, indexCount * typeSize);
auto indexBuffer = Ogre::Root::getSingleton().getRenderSystem()->getVaoManager()->createIndexBuffer(indexType, indexCount, Ogre::BT_IMMUTABLE, simdBuffer, false);
OGRE_FREE_SIMD(simdBuffer, Ogre::MEMCATEGORY_GEOMETRY);
return indexBuffer;
}
Ogre::VertexBufferPacked* CreateManualVertexBuffer(Ogre::VertexElement2Vec vertexElements, const void* data, int vertexCount)
{
auto vertexSize = Ogre::VaoManager::calculateVertexSize(vertexElements);
auto simdBuffer = OGRE_MALLOC_SIMD(vertexCount * vertexSize, Ogre::MEMCATEGORY_GEOMETRY);
std::memcpy(simdBuffer, data, vertexCount * vertexSize);
auto vertexBuffer = Ogre::Root::getSingleton().getRenderSystem()->getVaoManager()->createVertexBuffer(vertexElements, vertexCount, Ogre::BT_IMMUTABLE, simdBuffer, false);
OGRE_FREE_SIMD(simdBuffer, Ogre::MEMCATEGORY_GEOMETRY);
return vertexBuffer;
}
Ogre::SubMesh* CreateManualSubMesh(Ogre::MeshPtr mesh, const Ogre::VertexBufferPackedVec& vertexBufferList, Ogre::IndexBufferPacked* indexBuffer, Ogre::OperationType operationType)
{
auto subMesh = mesh->createSubMesh();
auto ogreVAO = Ogre::Root::getSingleton().getRenderSystem()->getVaoManager()->createVertexArrayObject(vertexBufferList, indexBuffer, operationType);
subMesh->mVao[Ogre::VpNormal].push_back(ogreVAO);
subMesh->mVao[Ogre::VpShadow].push_back(ogreVAO);
return subMesh;
}
Helper functions:
Code: Select all
Ogre::VertexElementType GetVertexElementTypeFromGLTF(int componentType, int componentCount)
{
switch (componentType) {
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: {
switch (componentCount) {
case 2: return Ogre::VET_USHORT2;
case 4: return Ogre::VET_USHORT4;
default:
OGRE_EXCEPT(Ogre::Exception::ERR_NOT_IMPLEMENTED, "", __FUNCTION__);
break;
}
break;
}
case TINYGLTF_COMPONENT_TYPE_FLOAT: {
switch (componentCount) {
case 2: return Ogre::VET_FLOAT2;
case 3: return Ogre::VET_FLOAT3;
case 4: return Ogre::VET_FLOAT4;
default:
OGRE_EXCEPT(Ogre::Exception::ERR_NOT_IMPLEMENTED, "", __FUNCTION__);
break;
}
break;
}
default:
OGRE_EXCEPT(Ogre::Exception::ERR_NOT_IMPLEMENTED, "", __FUNCTION__);
break;
}
}
Ogre::VertexElementSemantic GetVertexElementSemanticFromGLTF(std::string gltfSemanticName)
{
if (gltfSemanticName == "POSITION")
return Ogre::VES_POSITION;
else if (gltfSemanticName == "NORMAL")
return Ogre::VES_NORMAL;
else if (gltfSemanticName == "TANGENT")
return Ogre::VES_TANGENT;
else if (gltfSemanticName == "TEXCOORD_0")
return Ogre::VES_TEXTURE_COORDINATES;
else if (gltfSemanticName == "TEXCOORD_1")
return Ogre::VES_TEXTURE_COORDINATES;
else if (gltfSemanticName == "COLOR_0")
return Ogre::VES_DIFFUSE;
else if (gltfSemanticName == "JOINTS_0")
return Ogre::VES_BLEND_INDICES;
else if (gltfSemanticName == "WEIGHTS_0")
return Ogre::VES_BLEND_WEIGHTS;
else
OGRE_EXCEPT(Ogre::Exception::ERR_NOT_IMPLEMENTED, "", __FUNCTION__);
}
The mesh loader function:
Code: Select all
Ogre::MeshPtr CreateMeshFromGLTF(const std::string& meshName, const tinygltf::Model& gltfModel, const tinygltf::Mesh& gltfMesh, bool needVertexNormal)
{
auto mesh = Ogre::MeshManager::getSingleton().getByName(meshName);
if (mesh != nullptr)
return mesh;
mesh = Ogre::MeshManager::getSingletonPtr()->createManual(meshName, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
Ogre::Aabb boundingBox;
for (auto primitiveIndex = 0; primitiveIndex < gltfMesh.primitives.size(); ++primitiveIndex) {
auto& gltfPrimitive = gltfMesh.primitives[primitiveIndex];
auto vertexCount = 0;
Ogre::VertexElement2Vec vertexElements;
for (auto& [gltfSemanticName, accessorIndex] : gltfPrimitive.attributes) {
auto& gltfAccessor = gltfModel.accessors[accessorIndex];
auto type = GetVertexElementTypeFromGLTF(gltfAccessor.componentType, tinygltf::GetNumComponentsInType(gltfAccessor.type));
auto semantic = GetVertexElementSemanticFromGLTF(gltfSemanticName);
vertexElements.emplace_back(Ogre::VertexElement2(type, semantic));
vertexCount = gltfAccessor.count;
}
bool needGenerateNormals = needVertexNormal;
bool needGenerateTangents = true;
std::vector<std::uint32_t> triangleIndices;
std::vector<std::uint16_t> joints;
std::vector<float> weights;
auto HasSemantic = [&](Ogre::VertexElementSemantic semantic) {
for (auto&& element : vertexElements)
if (element.mSemantic == semantic)
return true;
return false;
};
needGenerateNormals &= !HasSemantic(Ogre::VES_NORMAL);
if (needGenerateNormals)
vertexElements.emplace_back(Ogre::VertexElement2(Ogre::VET_FLOAT3, Ogre::VES_NORMAL));
needGenerateTangents &= HasSemantic(Ogre::VES_POSITION);
needGenerateTangents &= HasSemantic(Ogre::VES_TEXTURE_COORDINATES);
needGenerateTangents &= !HasSemantic(Ogre::VES_TANGENT);
if (needGenerateTangents)
vertexElements.emplace_back(Ogre::VertexElement2(Ogre::VET_FLOAT3, Ogre::VES_TANGENT));
Ogre::IndexBufferPacked* indexBuffer = nullptr;
if (gltfPrimitive.indices >= 0) {
auto& gltfAccessor = gltfModel.accessors[gltfPrimitive.indices];
auto& gltfBufferView = gltfModel.bufferViews[gltfAccessor.bufferView];
auto& gltfBuffer = gltfModel.buffers[gltfBufferView.buffer];
auto byteStride = gltfAccessor.ByteStride(gltfBufferView);
auto indexCount = gltfAccessor.count;
auto sourceOffset = gltfBuffer.data.data() + gltfBufferView.byteOffset + gltfAccessor.byteOffset;
switch (gltfAccessor.componentType) {
case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: {
std::vector<std::uint16_t> buffer(indexCount);
for (auto i = 0; i < indexCount; ++i)
buffer[i] = *reinterpret_cast<const std::uint8_t*>(sourceOffset + i * byteStride);
indexBuffer = CreateManualIndexBuffer(Ogre::IndexBufferPacked::IT_16BIT, buffer.data(), indexCount);
if (needGenerateNormals || needGenerateTangents) {
triangleIndices.resize(indexCount);
std::copy(std::begin(buffer), std::end(buffer), std::begin(triangleIndices));
}
break;
}
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: {
std::vector<std::uint16_t> buffer(indexCount);
std::memcpy(buffer.data(), sourceOffset, indexCount * byteStride);
indexBuffer = CreateManualIndexBuffer(Ogre::IndexBufferPacked::IT_16BIT, buffer.data(), indexCount);
if (needGenerateNormals || needGenerateTangents) {
triangleIndices.resize(indexCount);
std::copy(std::begin(buffer), std::end(buffer), std::begin(triangleIndices));
}
break;
}
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: {
std::vector<std::uint32_t> buffer(indexCount);
std::memcpy(buffer.data(), sourceOffset, indexCount * byteStride);
indexBuffer = CreateManualIndexBuffer(Ogre::IndexBufferPacked::IT_32BIT, buffer.data(), indexCount);
if (needGenerateNormals || needGenerateTangents) {
triangleIndices.resize(indexCount);
std::copy(std::begin(buffer), std::end(buffer), std::begin(triangleIndices));
}
break;
}
default:
OGRE_EXCEPT(Ogre::Exception::ERR_NOT_IMPLEMENTED, "", __FUNCTION__);
break;
}
} else {
if (vertexCount < std::numeric_limits<std::uint16_t>::max()) {
std::vector<std::uint16_t> buffer(vertexCount);
for (auto i = 0; i < vertexCount; ++i)
buffer[i] = i;
indexBuffer = CreateManualIndexBuffer(Ogre::IndexBufferPacked::IT_16BIT, buffer.data(), vertexCount);
} else {
std::vector<std::uint32_t> buffer(vertexCount);
for (auto i = 0; i < vertexCount; ++i)
buffer[i] = i;
indexBuffer = CreateManualIndexBuffer(Ogre::IndexBufferPacked::IT_32BIT, buffer.data(), vertexCount);
}
if (needGenerateNormals || needGenerateTangents) {
triangleIndices.resize(vertexCount);
for (auto i = 0; i < vertexCount; ++i)
triangleIndices[i] = i;
}
}
auto elementStride = 0;
auto vertexSize = Ogre::VaoManager::calculateVertexSize(vertexElements);
std::vector<char> buffer(vertexCount* vertexSize);
std::vector<float> positions(vertexCount * 3);
std::vector<float> uvs(vertexCount * 2);
std::vector<float> normals(vertexCount * 3);
for (auto& [gltfSemanticName, accessorIndex] : gltfPrimitive.attributes) {
auto& gltfAccessor = gltfModel.accessors[accessorIndex];
auto& gltfBufferView = gltfModel.bufferViews[gltfAccessor.bufferView];
auto& gltfBuffer = gltfModel.buffers[gltfBufferView.buffer];
auto byteStride = gltfAccessor.ByteStride(gltfBufferView);
auto componentSize = tinygltf::GetComponentSizeInBytes(gltfAccessor.componentType);
auto componentCount = tinygltf::GetNumComponentsInType(gltfAccessor.type);
auto attributeSize = componentSize * componentCount;
auto destinationOffset = buffer.data() + elementStride;
auto sourceOffset = gltfBuffer.data.data() + gltfBufferView.byteOffset + gltfAccessor.byteOffset;
for (auto i = 0; i < vertexCount; ++i)
std::memcpy(destinationOffset + i * vertexSize, sourceOffset + i * byteStride, attributeSize);
if (gltfAccessor.sparse.isSparse) {
std::vector<std::uint32_t> sparseIndices;
{
auto& gltfIndicesBufferView = gltfModel.bufferViews[gltfAccessor.sparse.indices.bufferView];
auto& gltfIndicesBuffer = gltfModel.buffers[gltfIndicesBufferView.buffer];
auto indicesByteStride = tinygltf::GetComponentSizeInBytes(gltfAccessor.sparse.indices.componentType);
auto indicesSourceOffset = gltfIndicesBuffer.data.data() + gltfIndicesBufferView.byteOffset + gltfAccessor.sparse.indices.byteOffset;
sparseIndices.resize(gltfAccessor.sparse.count);
switch (gltfAccessor.sparse.indices.componentType) {
case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE:
for (auto i = 0; i < gltfAccessor.sparse.count; ++i)
sparseIndices[i] = *reinterpret_cast<const std::uint8_t*>(indicesSourceOffset + i * indicesByteStride);
break;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
for (auto i = 0; i < gltfAccessor.sparse.count; ++i)
sparseIndices[i] = *reinterpret_cast<const std::uint16_t*>(indicesSourceOffset + i * indicesByteStride);
break;
case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
std::memcpy(sparseIndices.data(), sourceOffset, gltfAccessor.sparse.count * indicesByteStride);
break;
default:
OGRE_EXCEPT(Ogre::Exception::ERR_NOT_IMPLEMENTED, "", __FUNCTION__);
break;
}
}
// Replace vertices
{
auto& gltfValuesBufferView = gltfModel.bufferViews[gltfAccessor.sparse.values.bufferView];
auto& gltfValuesBuffer = gltfModel.buffers[gltfValuesBufferView.buffer];
auto valuesByteStride = gltfAccessor.ByteStride(gltfValuesBufferView);
auto valuesSourceOffset = gltfValuesBuffer.data.data() + gltfValuesBufferView.byteOffset + gltfAccessor.sparse.values.byteOffset;
for (auto i = 0; i < gltfAccessor.sparse.count; ++i)
std::memcpy(destinationOffset + sparseIndices[i] * vertexSize, valuesSourceOffset + i * valuesByteStride, attributeSize);
}
}
if (gltfSemanticName == "POSITION") {
Ogre::Vector3 minimumPoint(gltfAccessor.minValues[0], gltfAccessor.minValues[1], gltfAccessor.minValues[2]);
Ogre::Vector3 maximumPoint(gltfAccessor.maxValues[0], gltfAccessor.maxValues[1], gltfAccessor.maxValues[2]);
if (primitiveIndex == 0)
boundingBox = Ogre::Aabb::newFromExtents(minimumPoint, maximumPoint);
else
boundingBox.merge(Ogre::Aabb::newFromExtents(minimumPoint, maximumPoint));
if (needGenerateNormals || needGenerateTangents)
for (auto i = 0; i < vertexCount; ++i)
std::memcpy(positions.data() + i * 3, destinationOffset + i * vertexSize, sizeof(float) * 3);
} else if (gltfSemanticName == "NORMAL") {
if (needGenerateTangents)
for (auto i = 0; i < vertexCount; ++i)
std::memcpy(normals.data() + i * 3, destinationOffset + i * vertexSize, sizeof(float) * 3);
} else if (gltfSemanticName == "TEXCOORD_0") {
if (needGenerateTangents)
for (auto i = 0; i < vertexCount; ++i)
std::memcpy(uvs.data() + i * 2, destinationOffset + i * vertexSize, sizeof(float) * 2);
} else if (gltfSemanticName == "JOINTS_0") {
joints.resize(vertexCount* componentCount);
for (auto i = 0; i < vertexCount; ++i)
std::memcpy(joints.data() + i * componentCount, destinationOffset + i * vertexSize, attributeSize);
} else if (gltfSemanticName == "WEIGHTS_0") {
weights.resize(vertexCount* componentCount);
for (auto i = 0; i < vertexCount; ++i)
std::memcpy(weights.data() + i * componentCount, destinationOffset + i * vertexSize, attributeSize);
}
elementStride += attributeSize;
}
if (needGenerateNormals) {
for (auto i = 0; i < triangleIndices.size() / 3; ++i) {
auto i0 = triangleIndices[i * 3 + 0] * 3;
auto i1 = triangleIndices[i * 3 + 1] * 3;
auto i2 = triangleIndices[i * 3 + 2] * 3;
auto p0 = &positions[i0];
auto p1 = &positions[i1];
auto p2 = &positions[i2];
auto v0 = Ogre::Vector3(p0[0], p0[1], p0[2]);
auto v1 = Ogre::Vector3(p1[0], p1[1], p1[2]);
auto v2 = Ogre::Vector3(p2[0], p2[1], p2[2]);
auto n = (v1 - v0).crossProduct(v2 - v0).normalisedCopy();
std::memcpy(&normals[i0], &n, sizeof(float) * 3);
std::memcpy(&normals[i1], &n, sizeof(float) * 3);
std::memcpy(&normals[i2], &n, sizeof(float) * 3);
}
auto destinationOffset = buffer.data() + elementStride;
for (auto i = 0; i < vertexCount; ++i)
std::memcpy(destinationOffset + i * vertexSize, normals.data() + i * 3, sizeof(float) * 3);
elementStride += sizeof(float) * 3;
}
if (needGenerateTangents) {
std::vector<float> cornerTangents;
std::vector<float> cornerBitangents;
tgen::computeCornerTSpace(triangleIndices, triangleIndices, positions, uvs, cornerTangents, cornerBitangents);
std::vector<float> vertexTangents;
std::vector<float> vertexBitangents;
tgen::computeVertexTSpace(triangleIndices, cornerTangents, cornerBitangents, vertexCount, vertexTangents, vertexBitangents);
if (HasSemantic(Ogre::VES_NORMAL))
tgen::orthogonalizeTSpace(normals, vertexTangents, vertexBitangents);
auto destinationOffset = buffer.data() + elementStride;
for (auto i = 0; i < vertexCount; ++i)
std::memcpy(destinationOffset + i * vertexSize, vertexTangents.data() + i * 3, sizeof(float) * 3);
}
auto vertexBuffer = CreateManualVertexBuffer(vertexElements, buffer.data(), vertexCount);
Ogre::VertexBufferPackedVec vertexBufferList;
vertexBufferList.emplace_back(vertexBuffer);
auto subMesh = CreateManualSubMesh(mesh, vertexBufferList, indexBuffer, Ogre::OT_TRIANGLE_LIST);
if (HasSemantic(Ogre::VES_BLEND_INDICES) && HasSemantic(Ogre::VES_BLEND_WEIGHTS)) {
auto componentCount = joints.size() / vertexCount;
for (auto i = 0; i < vertexCount; ++i)
for (auto j = 0; j < componentCount; ++j)
subMesh->addBoneAssignment(Ogre::VertexBoneAssignment(i, joints[i * componentCount + j], weights[i * componentCount + j]));
subMesh->_compileBoneAssignments();
}
if (gltfPrimitive.targets.size() > 0) {
std::vector<std::vector<float>> positionsList;
std::vector<std::vector<float>> normalsList;
for (auto& gltfDisplacements : gltfPrimitive.targets) {
auto elementStride = 0;
for (auto& [gltfSemanticName, accessorIndex] : gltfDisplacements) {
if (gltfSemanticName == "TANGENT")
continue;
auto& gltfAccessor = gltfModel.accessors[accessorIndex];
auto& gltfBufferView = gltfModel.bufferViews[gltfAccessor.bufferView];
auto& gltfBuffer = gltfModel.buffers[gltfBufferView.buffer];
auto byteStride = gltfAccessor.ByteStride(gltfBufferView);
auto sourceOffset = gltfBuffer.data.data() + gltfBufferView.byteOffset + gltfAccessor.byteOffset;
std::vector<float> buffer(vertexCount * 3);
for (auto i = 0; i < vertexCount; ++i)
std::memcpy(reinterpret_cast<char*>(buffer.data()) + i * sizeof(float) * 3, sourceOffset + i * byteStride, sizeof(float) * 3);
if (gltfSemanticName == "POSITION")
positionsList.emplace_back(buffer);
else if (gltfSemanticName == "NORMAL")
normalsList.emplace_back(buffer);
else
OGRE_EXCEPT(Ogre::Exception::ERR_NOT_IMPLEMENTED, "", __FUNCTION__);
}
}
std::vector<const float*> plist;
for (auto& positions : positionsList)
plist.emplace_back(positions.data());
if (normalsList.size() > 0) {
std::vector<const float*> nlist;
for (auto& normals : normalsList)
nlist.emplace_back(normals.data());
subMesh->createPoses(plist.data(), nlist.data(), plist.size(), positionsList[0].size());
} else {
subMesh->createPoses(plist.data(), nullptr, plist.size(), positionsList[0].size());
}
}
}
mesh->_setBounds(boundingBox, false);
mesh->_setBoundingSphereRadius(boundingBox.getRadius());
return mesh;
}