886 const std::shared_ptr<GraphicsDevice>& device)
889 spdlog::error(
"GLB parse failed: graphics device is null");
893 tinygltf::TinyGLTF loader;
895 tinygltf::Model model;
901 const auto dot = path.rfind(
'.');
902 const bool isAscii = (dot != std::string::npos &&
903 (path.substr(dot) ==
".gltf" || path.substr(dot) ==
".GLTF"));
905 ok = loader.LoadASCIIFromFile(&model, &err, &warn, path);
907 ok = loader.LoadBinaryFromFile(&model, &err, &warn, path);
910 spdlog::warn(
"GLB parse warning [{}]: {}", path, warn);
913 spdlog::error(
"GLB parse failed [{}]: {}", path, err);
917 auto container = std::make_unique<GlbContainerResource>();
918 auto vertexFormat = std::make_shared<VertexFormat>(
sizeof(
PackedVertex),
true,
false);
919 size_t dracoPrimitiveCount = 0;
920 size_t dracoDecodeSuccessCount = 0;
921 size_t dracoDecodeFailureCount = 0;
923 auto makeDefaultMaterial = []() {
924 auto material = std::make_shared<StandardMaterial>();
925 material->setName(
"glTF-default");
926 material->setTransparent(
false);
928 material->setMetallicFactor(0.0f);
929 material->setRoughnessFactor(1.0f);
930 material->setShaderVariantKey(1);
934 std::vector<std::shared_ptr<Material>> gltfMaterials;
935 gltfMaterials.reserve(std::max<size_t>(1, model.materials.size()));
936 std::vector<std::shared_ptr<Texture>> gltfTextures(model.textures.size());
938 auto getOrCreateTexture = [&](
const int textureIndex) -> std::shared_ptr<Texture> {
939 if (textureIndex < 0 || textureIndex >=
static_cast<int>(model.textures.size())) {
943 auto& cached = gltfTextures[
static_cast<size_t>(textureIndex)];
948 const auto& srcTexture = model.textures[
static_cast<size_t>(textureIndex)];
952 int imageSource = srcTexture.source;
953 if (imageSource < 0) {
955 auto it = srcTexture.extensions.find(
"KHR_texture_basisu");
956 if (it != srcTexture.extensions.end() && it->second.IsObject()) {
957 auto sourceVal = it->second.Get(
"source");
958 if (sourceVal.IsInt()) {
959 imageSource = sourceVal.GetNumberAsInt();
964 if (imageSource < 0 || imageSource >=
static_cast<int>(model.images.size())) {
965 spdlog::warn(
"glTF texture {} has no valid image source (source={}, no basisu fallback)",
966 textureIndex, srcTexture.source);
970 const auto& srcImage = model.images[
static_cast<size_t>(imageSource)];
971 std::vector<uint8_t> rgbaPixels;
972 if (!buildRgba8Image(srcImage, rgbaPixels)) {
973 spdlog::warn(
"glTF image '{}' unsupported format (bits={}, components={}, pixelType={})",
974 srcImage.name, srcImage.bits, srcImage.component, srcImage.pixel_type);
979 options.
width =
static_cast<uint32_t
>(srcImage.width);
980 options.
height =
static_cast<uint32_t
>(srcImage.height);
986 options.
name = srcImage.name.empty() ? srcTexture.name : srcImage.name;
988 auto texture = std::make_shared<Texture>(device.get(), options);
989 texture->setLevelData(0, rgbaPixels.data(), rgbaPixels.size());
991 if (srcTexture.sampler >= 0 && srcTexture.sampler <
static_cast<int>(model.samplers.size())) {
992 const auto& sampler = model.samplers[
static_cast<size_t>(srcTexture.sampler)];
993 if (sampler.minFilter != -1) {
994 auto minFilter = mapMinFilter(sampler.minFilter);
1001 texture->setMinFilter(minFilter);
1003 if (sampler.magFilter != -1) {
1004 texture->setMagFilter(mapMagFilter(sampler.magFilter));
1006 texture->setAddressU(mapWrapMode(sampler.wrapS));
1007 texture->setAddressV(mapWrapMode(sampler.wrapT));
1011 container->addOwnedTexture(texture);
1016 if (model.materials.empty()) {
1017 gltfMaterials.push_back(makeDefaultMaterial());
1019 for (
size_t materialIndex = 0; materialIndex < model.materials.size(); ++materialIndex) {
1020 const auto& srcMaterial = model.materials[materialIndex];
1021 auto material = std::make_shared<StandardMaterial>();
1022 material->setName(srcMaterial.name.empty() ?
"glTF-material" : srcMaterial.name);
1024 const auto& pbr = srcMaterial.pbrMetallicRoughness;
1025 if (pbr.baseColorFactor.size() == 4) {
1026 const Color baseColor(
1027 static_cast<float>(pbr.baseColorFactor[0]),
1028 static_cast<float>(pbr.baseColorFactor[1]),
1029 static_cast<float>(pbr.baseColorFactor[2]),
1030 static_cast<float>(pbr.baseColorFactor[3])
1032 material->setBaseColorFactor(baseColor);
1037 Color diffuseColor(baseColor);
1038 diffuseColor.
gamma();
1039 material->setDiffuse(diffuseColor);
1040 material->setOpacity(baseColor.
a);
1042 float metallicFactor =
static_cast<float>(pbr.metallicFactor);
1043 float roughnessFactor =
static_cast<float>(pbr.roughnessFactor);
1044 material->setMetallicFactor(metallicFactor);
1045 material->setRoughnessFactor(roughnessFactor);
1049 material->setMetalness(metallicFactor);
1050 material->setGloss(1.0f - roughnessFactor);
1052 if (!srcMaterial.alphaMode.empty()) {
1053 if (srcMaterial.alphaMode ==
"BLEND") {
1055 material->setTransparent(
true);
1056 }
else if (srcMaterial.alphaMode ==
"MASK") {
1058 material->setTransparent(
false);
1061 material->setTransparent(
false);
1065 material->setAlphaCutoff(
static_cast<float>(srcMaterial.alphaCutoff));
1067 if (pbr.baseColorTexture.index >= 0) {
1068 if (
auto baseColorTexture = getOrCreateTexture(pbr.baseColorTexture.index)) {
1069 material->setBaseColorTexture(baseColorTexture.get());
1070 material->setHasBaseColorTexture(
true);
1071 material->setBaseColorUvSet(pbr.baseColorTexture.texCoord);
1075 if (srcMaterial.normalTexture.index >= 0) {
1076 if (
auto normalTexture = getOrCreateTexture(srcMaterial.normalTexture.index)) {
1077 material->setNormalTexture(normalTexture.get());
1078 material->setHasNormalTexture(
true);
1079 material->setNormalUvSet(srcMaterial.normalTexture.texCoord);
1081 material->setNormalScale(
static_cast<float>(srcMaterial.normalTexture.scale));
1083 if (pbr.metallicRoughnessTexture.index >= 0) {
1084 if (
auto mrTexture = getOrCreateTexture(pbr.metallicRoughnessTexture.index)) {
1085 material->setMetallicRoughnessTexture(mrTexture.get());
1086 material->setHasMetallicRoughnessTexture(
true);
1087 material->setMetallicRoughnessUvSet(pbr.metallicRoughnessTexture.texCoord);
1090 if (srcMaterial.occlusionTexture.index >= 0) {
1091 if (
auto occlusionTexture = getOrCreateTexture(srcMaterial.occlusionTexture.index)) {
1092 material->setOcclusionTexture(occlusionTexture.get());
1093 material->setHasOcclusionTexture(
true);
1094 material->setOcclusionUvSet(srcMaterial.occlusionTexture.texCoord);
1096 material->setOcclusionStrength(
static_cast<float>(srcMaterial.occlusionTexture.strength));
1098 if (srcMaterial.emissiveFactor.size() == 3) {
1101 Color emissiveColor(
1102 static_cast<float>(srcMaterial.emissiveFactor[0]),
1103 static_cast<float>(srcMaterial.emissiveFactor[1]),
1104 static_cast<float>(srcMaterial.emissiveFactor[2]),
1107 emissiveColor.
gamma();
1108 material->setEmissiveFactor(emissiveColor);
1110 if (srcMaterial.emissiveTexture.index >= 0) {
1111 if (
auto emissiveTexture = getOrCreateTexture(srcMaterial.emissiveTexture.index)) {
1112 material->setEmissiveTexture(emissiveTexture.get());
1113 material->setHasEmissiveTexture(
true);
1114 material->setEmissiveUvSet(srcMaterial.emissiveTexture.texCoord);
1119 const bool isUnlit = srcMaterial.extensions.contains(
"KHR_materials_unlit");
1121 uint64_t variant = 1;
1122 if (material->hasBaseColorTexture()) {
1123 variant |= (1ull << 1);
1125 if (material->hasNormalTexture()) {
1126 variant |= (1ull << 4);
1128 if (material->hasMetallicRoughnessTexture()) {
1129 variant |= (1ull << 5);
1131 if (material->hasOcclusionTexture()) {
1132 variant |= (1ull << 6);
1134 if (material->hasEmissiveTexture()) {
1135 variant |= (1ull << 7);
1138 variant |= (1ull << 2);
1140 variant |= (1ull << 3);
1143 variant |= (1ull << 32);
1145 material->setShaderVariantKey(variant);
1146 gltfMaterials.push_back(material);
1154 std::vector<Matrix4> nodeWorldMatrices(model.nodes.size(),
Matrix4::identity());
1157 for (
size_t i = 0; i < model.nodes.size(); ++i) {
1158 const auto& node = model.nodes[i];
1159 if (!node.matrix.empty() && node.matrix.size() == 16) {
1162 for (
int col = 0; col < 4; ++col)
1163 for (
int row = 0; row < 4; ++row)
1164 m.
setElement(row, col,
static_cast<float>(node.matrix[
static_cast<size_t>(col * 4 + row)]));
1165 nodeWorldMatrices[i] = m;
1170 if (node.translation.size() == 3) {
1171 t =
Vector3(
static_cast<float>(node.translation[0]),
1172 static_cast<float>(node.translation[1]),
1173 static_cast<float>(node.translation[2]));
1175 if (node.rotation.size() == 4) {
1176 q =
Quaternion(
static_cast<float>(node.rotation[0]),
1177 static_cast<float>(node.rotation[1]),
1178 static_cast<float>(node.rotation[2]),
1179 static_cast<float>(node.rotation[3])).
normalized();
1181 if (node.scale.size() == 3) {
1182 s =
Vector3(
static_cast<float>(node.scale[0]),
1183 static_cast<float>(node.scale[1]),
1184 static_cast<float>(node.scale[2]));
1190 for (
int c = 0; c < 3; ++c) {
1191 for (
int r = 0; r < 3; ++r)
1200 nodeWorldMatrices[i] = trs;
1207 std::vector<bool> isChild(model.nodes.size(),
false);
1208 for (
const auto& node : model.nodes) {
1209 for (
int childIdx : node.children) {
1210 if (childIdx >= 0 && childIdx <
static_cast<int>(model.nodes.size())) {
1211 isChild[
static_cast<size_t>(childIdx)] =
true;
1215 std::queue<size_t> bfs;
1216 for (
size_t i = 0; i < model.nodes.size(); ++i) {
1217 if (!isChild[i]) bfs.push(i);
1219 while (!bfs.empty()) {
1220 const size_t idx = bfs.front();
1222 for (
int childIdx : model.nodes[idx].children) {
1223 if (childIdx >= 0 && childIdx <
static_cast<int>(model.nodes.size())) {
1224 const auto ci =
static_cast<size_t>(childIdx);
1225 nodeWorldMatrices[ci] = nodeWorldMatrices[idx] * nodeWorldMatrices[ci];
1233 std::vector<int> meshToNodeIndex(model.meshes.size(), -1);
1234 for (
size_t i = 0; i < model.nodes.size(); ++i) {
1235 const int meshRef = model.nodes[i].mesh;
1236 if (meshRef >= 0 && meshRef <
static_cast<int>(model.meshes.size())) {
1237 meshToNodeIndex[
static_cast<size_t>(meshRef)] =
static_cast<int>(i);
1244 const bool hasAnimations = !model.animations.empty();
1250 std::vector<PackedPointVertex> mergedPointVertices;
1251 Vector3 mergedPtMin(std::numeric_limits<float>::max());
1252 Vector3 mergedPtMax(std::numeric_limits<float>::lowest());
1253 int mergedPointMaterialIndex = -1;
1254 size_t mergedPointPayloadIndex = SIZE_MAX;
1256 std::vector<std::vector<size_t>> meshToPayloadIndices(model.meshes.size());
1257 size_t nextPayloadIndex = 0;
1258 for (
size_t meshIndex = 0; meshIndex < model.meshes.size(); ++meshIndex) {
1259 const auto& mesh = model.meshes[meshIndex];
1260 for (
const auto& primitive : mesh.primitives) {
1268 if (primitive.mode == TINYGLTF_MODE_POINTS) {
1269 if (!primitive.attributes.contains(
"POSITION")) {
1272 const auto* positionAccessor = getAccessor(model, primitive.attributes.at(
"POSITION"));
1273 if (!positionAccessor || positionAccessor->count <= 0) {
1276 if (positionAccessor->componentType != TINYGLTF_COMPONENT_TYPE_FLOAT ||
1277 positionAccessor->type != TINYGLTF_TYPE_VEC3) {
1281 const auto* colorAccessor = primitive.attributes.contains(
"COLOR_0")
1282 ? getAccessor(model, primitive.attributes.at(
"COLOR_0")) :
nullptr;
1284 const auto pointVertexCount =
static_cast<size_t>(positionAccessor->count);
1286 if (hasAnimations) {
1291 std::vector<PackedPointVertex> pointVertices(pointVertexCount);
1292 Vector3 ptMin(std::numeric_limits<float>::max());
1293 Vector3 ptMax(std::numeric_limits<float>::lowest());
1295 for (
size_t i = 0; i < pointVertexCount; ++i) {
1297 if (!readFloatVec3(model, *positionAccessor, i, pos)) {
1302 float cr = 1.0f, cg = 1.0f, cb = 1.0f, ca = 1.0f;
1303 if (colorAccessor) {
1304 if (colorAccessor->type == TINYGLTF_TYPE_VEC4) {
1306 if (readFloatVec4(model, *colorAccessor, i, color)) {
1307 cr = color.
getX(); cg = color.
getY();
1308 cb = color.
getZ(); ca = color.
getW();
1310 }
else if (colorAccessor->type == TINYGLTF_TYPE_VEC3) {
1312 if (readFloatVec3(model, *colorAccessor, i, color)) {
1313 cr = color.
getX(); cg = color.
getY();
1314 cb = color.
getZ(); ca = 1.0f;
1319 pointVertices[i] = PackedPointVertex{
1325 std::min(ptMin.
getX(), pos.
getX()),
1326 std::min(ptMin.
getY(), pos.
getY()),
1330 std::max(ptMax.
getX(), pos.
getX()),
1331 std::max(ptMax.
getY(), pos.
getY()),
1336 auto pointVertexFormat = std::make_shared<VertexFormat>(
1337 static_cast<int>(
sizeof(PackedPointVertex)),
true,
false);
1339 std::vector<uint8_t> pointVertexBytes(pointVertices.size() *
sizeof(PackedPointVertex));
1340 std::memcpy(pointVertexBytes.data(), pointVertices.data(), pointVertexBytes.size());
1343 vbOpts.
data = std::move(pointVertexBytes);
1344 auto pointVB = device->createVertexBuffer(
1346 static_cast<int>(pointVertices.size()),
1350 auto meshResource = std::make_shared<Mesh>();
1351 meshResource->setVertexBuffer(pointVB);
1355 drawPrimitive.
base = 0;
1357 drawPrimitive.
count =
static_cast<int>(pointVertices.size());
1358 drawPrimitive.
indexed =
false;
1359 meshResource->setPrimitive(drawPrimitive, 0);
1362 bounds.
setCenter((ptMin + ptMax) * 0.5f);
1364 meshResource->setAabb(bounds);
1367 std::shared_ptr<Material> pointMaterial;
1368 const int matIdx = primitive.material >= 0 ? primitive.material : 0;
1369 if (matIdx <
static_cast<int>(gltfMaterials.size())) {
1370 pointMaterial = std::make_shared<StandardMaterial>(
1371 *std::static_pointer_cast<StandardMaterial>(
1372 gltfMaterials[
static_cast<size_t>(matIdx)]));
1374 pointMaterial = std::make_shared<StandardMaterial>(
1375 *std::static_pointer_cast<StandardMaterial>(gltfMaterials.front()));
1377 uint64_t ptVariant = pointMaterial->shaderVariantKey();
1378 ptVariant |= (1ull << 21);
1379 ptVariant |= (1ull << 31);
1380 ptVariant |= (1ull << 32);
1381 pointMaterial->setShaderVariantKey(ptVariant);
1385 pointMaterial->setTransparent(
true);
1390 payload.
mesh = meshResource;
1393 container->addMeshPayload(payload);
1394 meshToPayloadIndices[meshIndex].push_back(nextPayloadIndex++);
1400 if (mergedPointMaterialIndex < 0) {
1401 mergedPointMaterialIndex = primitive.material >= 0 ? primitive.material : 0;
1405 const Matrix4 meshWorldMatrix =
1406 (meshToNodeIndex[meshIndex] >= 0)
1407 ? nodeWorldMatrices[
static_cast<size_t>(meshToNodeIndex[meshIndex])]
1410 const size_t baseIndex = mergedPointVertices.size();
1411 mergedPointVertices.resize(baseIndex + pointVertexCount);
1413 for (
size_t i = 0; i < pointVertexCount; ++i) {
1415 if (!readFloatVec3(model, *positionAccessor, i, pos)) {
1421 float cr = 1.0f, cg = 1.0f, cb = 1.0f, ca = 1.0f;
1422 if (colorAccessor) {
1423 if (colorAccessor->type == TINYGLTF_TYPE_VEC4) {
1425 if (readFloatVec4(model, *colorAccessor, i, color)) {
1426 cr = color.
getX(); cg = color.
getY();
1427 cb = color.
getZ(); ca = color.
getW();
1429 }
else if (colorAccessor->type == TINYGLTF_TYPE_VEC3) {
1431 if (readFloatVec3(model, *colorAccessor, i, color)) {
1432 cr = color.
getX(); cg = color.
getY();
1433 cb = color.
getZ(); ca = 1.0f;
1438 mergedPointVertices[baseIndex + i] = PackedPointVertex{
1444 std::min(mergedPtMin.
getX(), pos.
getX()),
1445 std::min(mergedPtMin.
getY(), pos.
getY()),
1446 std::min(mergedPtMin.
getZ(), pos.
getZ())
1449 std::max(mergedPtMax.
getX(), pos.
getX()),
1450 std::max(mergedPtMax.
getY(), pos.
getY()),
1451 std::max(mergedPtMax.
getZ(), pos.
getZ())
1461 std::vector<PackedVertex> vertices;
1462 std::vector<uint32_t> parsedIndices;
1463 Vector3 minPos(std::numeric_limits<float>::max());
1464 Vector3 maxPos(std::numeric_limits<float>::lowest());
1466 bool decodedDraco =
false;
1467 if (primitiveUsesDraco(primitive)) {
1468 dracoPrimitiveCount++;
1469 decodedDraco = decodeDracoPrimitive(model, primitive, vertices, parsedIndices, minPos, maxPos);
1470 if (!decodedDraco) {
1471 dracoDecodeFailureCount++;
1472 spdlog::warn(
"Skipping glTF primitive due to Draco decode failure (mesh={})", meshIndex);
1475 dracoDecodeSuccessCount++;
1478 if (!decodedDraco) {
1479 if (!primitive.attributes.contains(
"POSITION")) {
1483 const auto* positionAccessor = getAccessor(model, primitive.attributes.at(
"POSITION"));
1484 if (!positionAccessor || positionAccessor->count <= 0) {
1487 if (positionAccessor->componentType != TINYGLTF_COMPONENT_TYPE_FLOAT ||
1488 positionAccessor->type != TINYGLTF_TYPE_VEC3) {
1492 const auto* normalAccessor = primitive.attributes.contains(
"NORMAL")
1493 ? getAccessor(model, primitive.attributes.at(
"NORMAL")) :
nullptr;
1494 const auto* uvAccessor = primitive.attributes.contains(
"TEXCOORD_0")
1495 ? getAccessor(model, primitive.attributes.at(
"TEXCOORD_0")) :
nullptr;
1496 const auto* uv1Accessor = primitive.attributes.contains(
"TEXCOORD_1")
1497 ? getAccessor(model, primitive.attributes.at(
"TEXCOORD_1")) :
nullptr;
1498 const auto* tangentAccessor = primitive.attributes.contains(
"TANGENT")
1499 ? getAccessor(model, primitive.attributes.at(
"TANGENT")) :
nullptr;
1501 const auto vertexCount =
static_cast<size_t>(positionAccessor->count);
1502 vertices.resize(vertexCount);
1503 for (
size_t i = 0; i < vertexCount; ++i) {
1505 if (!readFloatVec3(model, *positionAccessor, i, pos)) {
1509 Vector3 normal(0.0f, 1.0f, 0.0f);
1510 if (normalAccessor) {
1512 if (readFloatVec3(model, *normalAccessor, i, n)) {
1520 readFloatVec2(model, *uvAccessor, i, u, v);
1528 readFloatVec2(model, *uv1Accessor, i, u1, v1);
1534 Vector4 tangent(0.0f, 0.0f, 0.0f, 1.0f);
1535 if (tangentAccessor) {
1537 if (readFloatVec4(model, *tangentAccessor, i, t)) {
1554 std::min(minPos.
getX(), pos.
getX()),
1555 std::min(minPos.
getY(), pos.
getY()),
1556 std::min(minPos.
getZ(), pos.
getZ())
1559 std::max(maxPos.
getX(), pos.
getX()),
1560 std::max(maxPos.
getY(), pos.
getY()),
1561 std::max(maxPos.
getZ(), pos.
getZ())
1565 if (!tangentAccessor && primitive.mode == TINYGLTF_MODE_TRIANGLES) {
1566 if (primitive.indices >= 0) {
1567 if (
const auto* indexAccessor = getAccessor(model, primitive.indices)) {
1568 readIndices(model, *indexAccessor, parsedIndices);
1571 generateTangents(vertices, parsedIndices.empty() ?
nullptr : &parsedIndices);
1575 const auto vertexCount = vertices.size();
1576 if (vertexCount == 0) {
1580 if (!decodedDraco && primitive.indices >= 0 && parsedIndices.empty()) {
1581 if (primitive.indices >= 0) {
1582 if (
const auto* indexAccessor = getAccessor(model, primitive.indices)) {
1583 readIndices(model, *indexAccessor, parsedIndices);
1588 std::vector<uint8_t> vertexBytes(vertices.size() *
sizeof(
PackedVertex));
1589 std::memcpy(vertexBytes.data(), vertices.data(), vertexBytes.size());
1591 vbOptions.
data = std::move(vertexBytes);
1592 auto vertexBuffer = device->createVertexBuffer(vertexFormat,
static_cast<int>(vertexCount), vbOptions);
1593 if (!vertexBuffer) {
1594 spdlog::warn(
"GLB parse: vertex buffer creation failed mesh={} primitive", meshIndex);
1598 std::shared_ptr<IndexBuffer> indexBuffer;
1599 int drawCount =
static_cast<int>(vertexCount);
1600 bool indexed =
false;
1601 if (primitive.indices >= 0) {
1602 if (parsedIndices.empty()) {
1603 const auto* indexAccessor = getAccessor(model, primitive.indices);
1604 if (indexAccessor) {
1605 readIndices(model, *indexAccessor, parsedIndices);
1608 if (!parsedIndices.empty()) {
1609 std::vector<uint8_t> indexBytes(parsedIndices.size() *
sizeof(uint32_t));
1610 std::memcpy(indexBytes.data(), parsedIndices.data(), indexBytes.size());
1611 indexBuffer = device->createIndexBuffer(
INDEXFORMAT_UINT32,
static_cast<int>(parsedIndices.size()), indexBytes);
1612 drawCount =
static_cast<int>(parsedIndices.size());
1617 auto meshResource = std::make_shared<Mesh>();
1618 meshResource->setVertexBuffer(vertexBuffer);
1619 meshResource->setIndexBuffer(indexBuffer, 0);
1622 drawPrimitive.
type = mapPrimitiveType(primitive.mode);
1623 drawPrimitive.
base = 0;
1625 drawPrimitive.
count = drawCount;
1626 drawPrimitive.
indexed = indexed;
1627 meshResource->setPrimitive(drawPrimitive, 0);
1630 bounds.
setCenter((minPos + maxPos) * 0.5f);
1632 meshResource->setAabb(bounds);
1635 payload.
mesh = meshResource;
1636 if (primitive.material >= 0 && primitive.material <
static_cast<int>(gltfMaterials.size())) {
1637 payload.
material = gltfMaterials[
static_cast<size_t>(primitive.material)];
1639 payload.
material = gltfMaterials.front();
1641 container->addMeshPayload(payload);
1642 meshToPayloadIndices[meshIndex].push_back(nextPayloadIndex++);
1650 if (!hasAnimations && !mergedPointVertices.empty()) {
1651 auto pointVertexFormat = std::make_shared<VertexFormat>(
1652 static_cast<int>(
sizeof(PackedPointVertex)),
true,
false);
1654 std::vector<uint8_t> pointVertexBytes(mergedPointVertices.size() *
sizeof(PackedPointVertex));
1655 std::memcpy(pointVertexBytes.data(), mergedPointVertices.data(), pointVertexBytes.size());
1658 vbOptions.
data = std::move(pointVertexBytes);
1659 auto pointVB = device->createVertexBuffer(
1661 static_cast<int>(mergedPointVertices.size()),
1665 auto meshResource = std::make_shared<Mesh>();
1666 meshResource->setVertexBuffer(pointVB);
1670 drawPrimitive.
base = 0;
1672 drawPrimitive.
count =
static_cast<int>(mergedPointVertices.size());
1673 drawPrimitive.
indexed =
false;
1674 meshResource->setPrimitive(drawPrimitive, 0);
1677 bounds.
setCenter((mergedPtMin + mergedPtMax) * 0.5f);
1679 meshResource->setAabb(bounds);
1682 std::shared_ptr<Material> pointMaterial;
1683 if (mergedPointMaterialIndex >= 0 &&
1684 mergedPointMaterialIndex <
static_cast<int>(gltfMaterials.size())) {
1685 pointMaterial = std::make_shared<StandardMaterial>(
1686 *std::static_pointer_cast<StandardMaterial>(
1687 gltfMaterials[
static_cast<size_t>(mergedPointMaterialIndex)]));
1689 pointMaterial = std::make_shared<StandardMaterial>(
1690 *std::static_pointer_cast<StandardMaterial>(gltfMaterials.front()));
1694 uint64_t ptVariant = pointMaterial->shaderVariantKey();
1695 ptVariant |= (1ull << 21);
1696 ptVariant |= (1ull << 31);
1697 pointMaterial->setShaderVariantKey(ptVariant);
1700 payload.
mesh = meshResource;
1703 container->addMeshPayload(payload);
1705 mergedPointPayloadIndex = nextPayloadIndex++;
1707 spdlog::info(
"GLB merged {} point vertices into 1 draw call (AABB {:.2f}–{:.2f})",
1708 mergedPointVertices.size(),
1709 mergedPtMin.
getX(), mergedPtMax.
getX());
1715 std::vector<bool> meshFullyConsumed(model.meshes.size(),
false);
1716 if (!hasAnimations) {
1717 for (
size_t mi = 0; mi < model.meshes.size(); ++mi) {
1718 const auto& m = model.meshes[mi];
1719 bool allPoints = !m.primitives.empty();
1720 for (
const auto& prim : m.primitives) {
1721 if (prim.mode != TINYGLTF_MODE_POINTS) {
1726 meshFullyConsumed[mi] = allPoints;
1731 for (
const auto& node : model.nodes) {
1733 nodePayload.
name = node.name;
1735 if (!node.matrix.empty()) {
1738 if (node.translation.size() == 3) {
1740 static_cast<float>(node.translation[0]),
1741 static_cast<float>(node.translation[1]),
1742 static_cast<float>(node.translation[2])
1745 if (node.rotation.size() == 4) {
1747 static_cast<float>(node.rotation[0]),
1748 static_cast<float>(node.rotation[1]),
1749 static_cast<float>(node.rotation[2]),
1750 static_cast<float>(node.rotation[3])
1753 if (node.scale.size() == 3) {
1755 static_cast<float>(node.scale[0]),
1756 static_cast<float>(node.scale[1]),
1757 static_cast<float>(node.scale[2])
1761 if (node.mesh >= 0 && node.mesh <
static_cast<int>(meshToPayloadIndices.size())) {
1762 const auto& mapped = meshToPayloadIndices[
static_cast<size_t>(node.mesh)];
1769 if (node.children.empty() && node.mesh >= 0 &&
1770 node.mesh <
static_cast<int>(meshFullyConsumed.size()) &&
1771 meshFullyConsumed[
static_cast<size_t>(node.mesh)]) {
1772 nodePayload.
skip =
true;
1775 nodePayload.
children = node.children;
1776 container->addNodePayload(nodePayload);
1780 if (mergedPointPayloadIndex != SIZE_MAX) {
1782 pointNode.
name =
"__merged_point_cloud";
1784 container->addNodePayload(pointNode);
1786 container->addRootNodeIndex(
static_cast<int>(model.nodes.size()));
1789 int sceneIndex = model.defaultScene;
1790 if (sceneIndex < 0 && !model.scenes.empty()) {
1793 if (sceneIndex >= 0 && sceneIndex <
static_cast<int>(model.scenes.size())) {
1794 const auto& scene = model.scenes[
static_cast<size_t>(sceneIndex)];
1795 for (
const auto nodeIndex : scene.nodes) {
1796 container->addRootNodeIndex(nodeIndex);
1800 if (dracoPrimitiveCount > 0) {
1802 "GLB Draco summary [{}]: primitives={}, decoded={}, failed={}",
1804 dracoPrimitiveCount,
1805 dracoDecodeSuccessCount,
1806 dracoDecodeFailureCount
1811 parseAnimations(model, container.get());
1852 tinygltf::Model& model,
1853 const std::shared_ptr<GraphicsDevice>& device,
1854 const std::string& debugName)
1857 spdlog::error(
"GLB createFromModel failed: graphics device is null");
1861 auto container = std::make_unique<GlbContainerResource>();
1862 auto vertexFormat = std::make_shared<VertexFormat>(
sizeof(
PackedVertex),
true,
false);
1863 size_t dracoPrimitiveCount = 0;
1864 size_t dracoDecodeSuccessCount = 0;
1865 size_t dracoDecodeFailureCount = 0;
1867 auto makeDefaultMaterial = []() {
1868 auto material = std::make_shared<StandardMaterial>();
1869 material->setName(
"glTF-default");
1870 material->setTransparent(
false);
1872 material->setMetallicFactor(0.0f);
1873 material->setRoughnessFactor(1.0f);
1874 material->setShaderVariantKey(1);
1878 std::vector<std::shared_ptr<Material>> gltfMaterials;
1879 gltfMaterials.reserve(std::max<size_t>(1, model.materials.size()));
1880 std::vector<std::shared_ptr<Texture>> gltfTextures(model.textures.size());
1882 auto getOrCreateTexture = [&](
const int textureIndex) -> std::shared_ptr<Texture> {
1883 if (textureIndex < 0 || textureIndex >=
static_cast<int>(model.textures.size())) {
1886 auto& cached = gltfTextures[
static_cast<size_t>(textureIndex)];
1887 if (cached)
return cached;
1889 const auto& srcTexture = model.textures[
static_cast<size_t>(textureIndex)];
1890 int imageSource = srcTexture.source;
1891 if (imageSource < 0) {
1892 auto it = srcTexture.extensions.find(
"KHR_texture_basisu");
1893 if (it != srcTexture.extensions.end() && it->second.IsObject()) {
1894 auto sourceVal = it->second.Get(
"source");
1895 if (sourceVal.IsInt()) imageSource = sourceVal.GetNumberAsInt();
1898 if (imageSource < 0 || imageSource >=
static_cast<int>(model.images.size()))
return nullptr;
1900 const auto& srcImage = model.images[
static_cast<size_t>(imageSource)];
1901 std::vector<uint8_t> rgbaPixels;
1902 if (!buildRgba8Image(srcImage, rgbaPixels))
return nullptr;
1905 options.
width =
static_cast<uint32_t
>(srcImage.width);
1906 options.
height =
static_cast<uint32_t
>(srcImage.height);
1912 options.
name = srcImage.name.empty() ? srcTexture.name : srcImage.name;
1914 auto texture = std::make_shared<Texture>(device.get(), options);
1915 texture->setLevelData(0, rgbaPixels.data(), rgbaPixels.size());
1917 if (srcTexture.sampler >= 0 && srcTexture.sampler <
static_cast<int>(model.samplers.size())) {
1918 const auto& sampler = model.samplers[
static_cast<size_t>(srcTexture.sampler)];
1919 if (sampler.minFilter != -1) {
1920 auto minFilter = mapMinFilter(sampler.minFilter);
1927 texture->setMinFilter(minFilter);
1929 if (sampler.magFilter != -1) texture->setMagFilter(mapMagFilter(sampler.magFilter));
1930 texture->setAddressU(mapWrapMode(sampler.wrapS));
1931 texture->setAddressV(mapWrapMode(sampler.wrapT));
1935 container->addOwnedTexture(texture);
1940 if (model.materials.empty()) {
1941 gltfMaterials.push_back(makeDefaultMaterial());
1943 for (
size_t materialIndex = 0; materialIndex < model.materials.size(); ++materialIndex) {
1944 const auto& srcMaterial = model.materials[materialIndex];
1945 auto material = std::make_shared<StandardMaterial>();
1946 material->setName(srcMaterial.name.empty() ?
"glTF-material" : srcMaterial.name);
1948 const auto& pbr = srcMaterial.pbrMetallicRoughness;
1949 if (pbr.baseColorFactor.size() == 4) {
1950 const Color baseColor(
1951 static_cast<float>(pbr.baseColorFactor[0]),
1952 static_cast<float>(pbr.baseColorFactor[1]),
1953 static_cast<float>(pbr.baseColorFactor[2]),
1954 static_cast<float>(pbr.baseColorFactor[3]));
1955 material->setBaseColorFactor(baseColor);
1956 Color diffuseColor(baseColor);
1957 diffuseColor.
gamma();
1958 material->setDiffuse(diffuseColor);
1959 material->setOpacity(baseColor.
a);
1961 material->setMetallicFactor(
static_cast<float>(pbr.metallicFactor));
1962 material->setRoughnessFactor(
static_cast<float>(pbr.roughnessFactor));
1963 material->setMetalness(
static_cast<float>(pbr.metallicFactor));
1964 material->setGloss(1.0f -
static_cast<float>(pbr.roughnessFactor));
1966 if (!srcMaterial.alphaMode.empty()) {
1967 if (srcMaterial.alphaMode ==
"BLEND") {
1969 material->setTransparent(
true);
1970 }
else if (srcMaterial.alphaMode ==
"MASK") {
1977 material->setAlphaCutoff(
static_cast<float>(srcMaterial.alphaCutoff));
1979 if (pbr.baseColorTexture.index >= 0) {
1980 if (
auto tex = getOrCreateTexture(pbr.baseColorTexture.index)) {
1981 material->setBaseColorTexture(tex.get());
1982 material->setHasBaseColorTexture(
true);
1983 material->setBaseColorUvSet(pbr.baseColorTexture.texCoord);
1986 if (srcMaterial.normalTexture.index >= 0) {
1987 if (
auto tex = getOrCreateTexture(srcMaterial.normalTexture.index)) {
1988 material->setNormalTexture(tex.get());
1989 material->setHasNormalTexture(
true);
1990 material->setNormalUvSet(srcMaterial.normalTexture.texCoord);
1992 material->setNormalScale(
static_cast<float>(srcMaterial.normalTexture.scale));
1994 if (pbr.metallicRoughnessTexture.index >= 0) {
1995 if (
auto tex = getOrCreateTexture(pbr.metallicRoughnessTexture.index)) {
1996 material->setMetallicRoughnessTexture(tex.get());
1997 material->setHasMetallicRoughnessTexture(
true);
2000 if (srcMaterial.emissiveFactor.size() == 3) {
2001 Color emissiveColor(
2002 static_cast<float>(srcMaterial.emissiveFactor[0]),
2003 static_cast<float>(srcMaterial.emissiveFactor[1]),
2004 static_cast<float>(srcMaterial.emissiveFactor[2]), 1.0f);
2005 emissiveColor.
gamma();
2006 material->setEmissiveFactor(emissiveColor);
2009 uint64_t variant = 1;
2010 if (material->hasBaseColorTexture()) variant |= (1ull << 1);
2011 if (material->hasNormalTexture()) variant |= (1ull << 4);
2012 if (material->hasMetallicRoughnessTexture()) variant |= (1ull << 5);
2014 else if (material->alphaMode() ==
AlphaMode::MASK) variant |= (1ull << 3);
2015 material->setShaderVariantKey(variant);
2016 gltfMaterials.push_back(material);
2020 std::vector<std::vector<size_t>> meshToPayloadIndices(model.meshes.size());
2021 size_t nextPayloadIndex = 0;
2022 for (
size_t meshIndex = 0; meshIndex < model.meshes.size(); ++meshIndex) {
2023 const auto& mesh = model.meshes[meshIndex];
2024 for (
const auto& primitive : mesh.primitives) {
2025 std::vector<PackedVertex> vertices;
2026 std::vector<uint32_t> parsedIndices;
2027 Vector3 minPos(std::numeric_limits<float>::max());
2028 Vector3 maxPos(std::numeric_limits<float>::lowest());
2030 bool decodedDraco =
false;
2031 if (primitiveUsesDraco(primitive)) {
2032 dracoPrimitiveCount++;
2033 decodedDraco = decodeDracoPrimitive(model, primitive, vertices, parsedIndices, minPos, maxPos);
2034 if (!decodedDraco) { dracoDecodeFailureCount++;
continue; }
2035 dracoDecodeSuccessCount++;
2038 if (!decodedDraco) {
2039 if (!primitive.attributes.contains(
"POSITION"))
continue;
2040 const auto* posAcc = getAccessor(model, primitive.attributes.at(
"POSITION"));
2041 if (!posAcc || posAcc->count <= 0)
continue;
2042 if (posAcc->componentType != TINYGLTF_COMPONENT_TYPE_FLOAT || posAcc->type != TINYGLTF_TYPE_VEC3)
continue;
2044 const auto* normalAcc = primitive.attributes.contains(
"NORMAL") ? getAccessor(model, primitive.attributes.at(
"NORMAL")) :
nullptr;
2045 const auto* uvAcc = primitive.attributes.contains(
"TEXCOORD_0") ? getAccessor(model, primitive.attributes.at(
"TEXCOORD_0")) :
nullptr;
2046 const auto* uv1Acc = primitive.attributes.contains(
"TEXCOORD_1") ? getAccessor(model, primitive.attributes.at(
"TEXCOORD_1")) :
nullptr;
2047 const auto* tanAcc = primitive.attributes.contains(
"TANGENT") ? getAccessor(model, primitive.attributes.at(
"TANGENT")) :
nullptr;
2049 const auto vCount =
static_cast<size_t>(posAcc->count);
2050 vertices.resize(vCount);
2051 for (
size_t i = 0; i < vCount; ++i) {
2053 if (!readFloatVec3(model, *posAcc, i, pos))
continue;
2054 Vector3 normal(0.0f, 1.0f, 0.0f);
2055 if (normalAcc) {
Vector3 n;
if (readFloatVec3(model, *normalAcc, i, n)) normal = n; }
2056 float u = 0.0f, v = 0.0f;
2057 if (uvAcc) { readFloatVec2(model, *uvAcc, i, u, v); v = 1.0f - v; }
2058 float u1 = u, v1 = v;
2059 if (uv1Acc) { readFloatVec2(model, *uv1Acc, i, u1, v1); v1 = 1.0f - v1; }
2060 Vector4 tangent(0.0f, 0.0f, 0.0f, 1.0f);
2072 if (!tanAcc && primitive.mode == TINYGLTF_MODE_TRIANGLES) {
2073 if (primitive.indices >= 0) {
if (
const auto* ia = getAccessor(model, primitive.indices)) readIndices(model, *ia, parsedIndices); }
2074 generateTangents(vertices, parsedIndices.empty() ?
nullptr : &parsedIndices);
2078 if (vertices.empty())
continue;
2080 if (!decodedDraco && primitive.indices >= 0 && parsedIndices.empty()) {
2081 if (
const auto* ia = getAccessor(model, primitive.indices)) readIndices(model, *ia, parsedIndices);
2084 std::vector<uint8_t> vertexBytes(vertices.size() *
sizeof(
PackedVertex));
2085 std::memcpy(vertexBytes.data(), vertices.data(), vertexBytes.size());
2087 vbOptions.
data = std::move(vertexBytes);
2088 auto vb = device->createVertexBuffer(vertexFormat,
static_cast<int>(vertices.size()), vbOptions);
2091 std::shared_ptr<IndexBuffer> ib;
2092 int drawCount =
static_cast<int>(vertices.size());
2093 bool indexed =
false;
2094 if (!parsedIndices.empty()) {
2095 std::vector<uint8_t> indexBytes(parsedIndices.size() *
sizeof(uint32_t));
2096 std::memcpy(indexBytes.data(), parsedIndices.data(), indexBytes.size());
2097 ib = device->createIndexBuffer(
INDEXFORMAT_UINT32,
static_cast<int>(parsedIndices.size()), indexBytes);
2098 drawCount =
static_cast<int>(parsedIndices.size());
2102 auto meshResource = std::make_shared<Mesh>();
2103 meshResource->setVertexBuffer(vb);
2104 meshResource->setIndexBuffer(ib, 0);
2106 drawPrimitive.
type = mapPrimitiveType(primitive.mode);
2107 drawPrimitive.
base = 0;
2109 drawPrimitive.
count = drawCount;
2110 drawPrimitive.
indexed = indexed;
2111 meshResource->setPrimitive(drawPrimitive, 0);
2114 bounds.
setCenter((minPos + maxPos) * 0.5f);
2116 meshResource->setAabb(bounds);
2119 payload.
mesh = meshResource;
2120 payload.
material = (primitive.material >= 0 && primitive.material <
static_cast<int>(gltfMaterials.size()))
2121 ? gltfMaterials[
static_cast<size_t>(primitive.material)] : gltfMaterials.front();
2122 container->addMeshPayload(payload);
2123 meshToPayloadIndices[meshIndex].push_back(nextPayloadIndex++);
2127 for (
const auto& node : model.nodes) {
2129 nodePayload.
name = node.name;
2130 if (!node.matrix.empty()) decomposeNodeMatrix(node.matrix, nodePayload.
translation, nodePayload.
rotation, nodePayload.
scale);
2131 if (node.translation.size() == 3) nodePayload.
translation =
Vector3(
static_cast<float>(node.translation[0]),
static_cast<float>(node.translation[1]),
static_cast<float>(node.translation[2]));
2132 if (node.rotation.size() == 4) nodePayload.
rotation =
Quaternion(
static_cast<float>(node.rotation[0]),
static_cast<float>(node.rotation[1]),
static_cast<float>(node.rotation[2]),
static_cast<float>(node.rotation[3])).
normalized();
2133 if (node.scale.size() == 3) nodePayload.
scale =
Vector3(
static_cast<float>(node.scale[0]),
static_cast<float>(node.scale[1]),
static_cast<float>(node.scale[2]));
2134 if (node.mesh >= 0 && node.mesh <
static_cast<int>(meshToPayloadIndices.size())) {
2135 const auto& mapped = meshToPayloadIndices[
static_cast<size_t>(node.mesh)];
2138 nodePayload.
children = node.children;
2139 container->addNodePayload(nodePayload);
2142 int sceneIndex = model.defaultScene;
2143 if (sceneIndex < 0 && !model.scenes.empty()) sceneIndex = 0;
2144 if (sceneIndex >= 0 && sceneIndex <
static_cast<int>(model.scenes.size())) {
2145 for (
const auto nodeIndex : model.scenes[
static_cast<size_t>(sceneIndex)].nodes)
2146 container->addRootNodeIndex(nodeIndex);
2149 if (dracoPrimitiveCount > 0) {
2150 spdlog::info(
"GLB Draco summary [{}]: primitives={}, decoded={}, failed={}",
2151 debugName, dracoPrimitiveCount, dracoDecodeSuccessCount, dracoDecodeFailureCount);
2155 parseAnimations(model, container.get());
2280 tinygltf::Model& model,
2282 const std::shared_ptr<GraphicsDevice>& device,
2283 const std::string& debugName)
2286 spdlog::error(
"GLB createFromPrepared failed: graphics device is null");
2290 auto container = std::make_unique<GlbContainerResource>();
2291 auto vertexFormat = std::make_shared<VertexFormat>(
sizeof(
PackedVertex),
true,
false);
2293 auto makeDefaultMaterial = []() {
2294 auto material = std::make_shared<StandardMaterial>();
2295 material->setName(
"glTF-default");
2296 material->setTransparent(
false);
2298 material->setMetallicFactor(0.0f);
2299 material->setRoughnessFactor(1.0f);
2300 material->setShaderVariantKey(1);
2304 std::vector<std::shared_ptr<Material>> gltfMaterials;
2305 gltfMaterials.reserve(std::max<size_t>(1, model.materials.size()));
2306 std::vector<std::shared_ptr<Texture>> gltfTextures(model.textures.size());
2309 auto getOrCreateTexture = [&](
const int textureIndex) -> std::shared_ptr<Texture> {
2310 if (textureIndex < 0 || textureIndex >=
static_cast<int>(model.textures.size()))
return nullptr;
2311 auto& cached = gltfTextures[
static_cast<size_t>(textureIndex)];
2312 if (cached)
return cached;
2314 const auto& srcTexture = model.textures[
static_cast<size_t>(textureIndex)];
2315 int imageSource = srcTexture.source;
2316 if (imageSource < 0) {
2317 auto it = srcTexture.extensions.find(
"KHR_texture_basisu");
2318 if (it != srcTexture.extensions.end() && it->second.IsObject()) {
2319 auto sourceVal = it->second.Get(
"source");
2320 if (sourceVal.IsInt()) imageSource = sourceVal.GetNumberAsInt();
2323 if (imageSource < 0 || imageSource >=
static_cast<int>(prepared.images.size()))
return nullptr;
2325 const auto& prepImg = prepared.images[
static_cast<size_t>(imageSource)];
2326 if (!prepImg.valid || prepImg.rgbaPixels.empty())
return nullptr;
2329 options.
width =
static_cast<uint32_t
>(prepImg.width);
2330 options.
height =
static_cast<uint32_t
>(prepImg.height);
2337 const auto& srcImage = model.images[
static_cast<size_t>(imageSource)];
2338 options.
name = srcImage.name.empty() ? srcTexture.name : srcImage.name;
2340 auto texture = std::make_shared<Texture>(device.get(), options);
2341 texture->setLevelData(0, prepImg.rgbaPixels.data(), prepImg.rgbaPixels.size());
2343 if (srcTexture.sampler >= 0 && srcTexture.sampler <
static_cast<int>(model.samplers.size())) {
2344 const auto& sampler = model.samplers[
static_cast<size_t>(srcTexture.sampler)];
2345 if (sampler.minFilter != -1) {
2346 auto minFilter = mapMinFilter(sampler.minFilter);
2353 texture->setMinFilter(minFilter);
2355 if (sampler.magFilter != -1) texture->setMagFilter(mapMagFilter(sampler.magFilter));
2356 texture->setAddressU(mapWrapMode(sampler.wrapS));
2357 texture->setAddressV(mapWrapMode(sampler.wrapT));
2361 container->addOwnedTexture(texture);
2367 if (model.materials.empty()) {
2368 gltfMaterials.push_back(makeDefaultMaterial());
2370 for (
size_t materialIndex = 0; materialIndex < model.materials.size(); ++materialIndex) {
2371 const auto& srcMaterial = model.materials[materialIndex];
2372 auto material = std::make_shared<StandardMaterial>();
2373 material->setName(srcMaterial.name.empty() ?
"glTF-material" : srcMaterial.name);
2375 const auto& pbr = srcMaterial.pbrMetallicRoughness;
2376 if (pbr.baseColorFactor.size() == 4) {
2377 const Color baseColor(
2378 static_cast<float>(pbr.baseColorFactor[0]),
2379 static_cast<float>(pbr.baseColorFactor[1]),
2380 static_cast<float>(pbr.baseColorFactor[2]),
2381 static_cast<float>(pbr.baseColorFactor[3]));
2382 material->setBaseColorFactor(baseColor);
2383 Color diffuseColor(baseColor);
2384 diffuseColor.
gamma();
2385 material->setDiffuse(diffuseColor);
2386 material->setOpacity(baseColor.
a);
2388 material->setMetallicFactor(
static_cast<float>(pbr.metallicFactor));
2389 material->setRoughnessFactor(
static_cast<float>(pbr.roughnessFactor));
2390 material->setMetalness(
static_cast<float>(pbr.metallicFactor));
2391 material->setGloss(1.0f -
static_cast<float>(pbr.roughnessFactor));
2393 if (!srcMaterial.alphaMode.empty()) {
2394 if (srcMaterial.alphaMode ==
"BLEND") {
2396 material->setTransparent(
true);
2397 }
else if (srcMaterial.alphaMode ==
"MASK") {
2404 material->setAlphaCutoff(
static_cast<float>(srcMaterial.alphaCutoff));
2406 if (pbr.baseColorTexture.index >= 0) {
2407 if (
auto tex = getOrCreateTexture(pbr.baseColorTexture.index)) {
2408 material->setBaseColorTexture(tex.get());
2409 material->setHasBaseColorTexture(
true);
2410 material->setBaseColorUvSet(pbr.baseColorTexture.texCoord);
2413 if (srcMaterial.normalTexture.index >= 0) {
2414 if (
auto tex = getOrCreateTexture(srcMaterial.normalTexture.index)) {
2415 material->setNormalTexture(tex.get());
2416 material->setHasNormalTexture(
true);
2417 material->setNormalUvSet(srcMaterial.normalTexture.texCoord);
2419 material->setNormalScale(
static_cast<float>(srcMaterial.normalTexture.scale));
2421 if (pbr.metallicRoughnessTexture.index >= 0) {
2422 if (
auto tex = getOrCreateTexture(pbr.metallicRoughnessTexture.index)) {
2423 material->setMetallicRoughnessTexture(tex.get());
2424 material->setHasMetallicRoughnessTexture(
true);
2427 if (srcMaterial.emissiveFactor.size() == 3) {
2428 Color emissiveColor(
2429 static_cast<float>(srcMaterial.emissiveFactor[0]),
2430 static_cast<float>(srcMaterial.emissiveFactor[1]),
2431 static_cast<float>(srcMaterial.emissiveFactor[2]), 1.0f);
2432 emissiveColor.
gamma();
2433 material->setEmissiveFactor(emissiveColor);
2436 uint64_t variant = 1;
2437 if (material->hasBaseColorTexture()) variant |= (1ull << 1);
2438 if (material->hasNormalTexture()) variant |= (1ull << 4);
2439 if (material->hasMetallicRoughnessTexture()) variant |= (1ull << 5);
2441 else if (material->alphaMode() ==
AlphaMode::MASK) variant |= (1ull << 3);
2442 material->setShaderVariantKey(variant);
2443 gltfMaterials.push_back(material);
2448 std::vector<std::vector<size_t>> meshToPayloadIndices(model.meshes.size());
2449 size_t nextPayloadIndex = 0;
2451 for (
size_t meshIndex = 0; meshIndex < prepared.meshPrimitives.size(); ++meshIndex) {
2452 for (
auto& pd : prepared.meshPrimitives[meshIndex]) {
2453 if (pd.vertexBytes.empty())
continue;
2456 vbOptions.
data = std::move(pd.vertexBytes);
2457 auto vb = device->createVertexBuffer(vertexFormat, pd.vertexCount, vbOptions);
2460 std::shared_ptr<IndexBuffer> ib;
2461 if (pd.indexed && !pd.indexBytes.empty()) {
2462 const int indexCount =
static_cast<int>(pd.indexBytes.size() /
sizeof(uint32_t));
2466 auto meshResource = std::make_shared<Mesh>();
2467 meshResource->setVertexBuffer(vb);
2468 meshResource->setIndexBuffer(ib, 0);
2471 drawPrimitive.
type = mapPrimitiveType(pd.mode);
2472 drawPrimitive.
base = 0;
2474 drawPrimitive.
count = pd.drawCount;
2475 drawPrimitive.
indexed = pd.indexed;
2476 meshResource->setPrimitive(drawPrimitive, 0);
2479 bounds.
setCenter((pd.boundsMin + pd.boundsMax) * 0.5f);
2481 meshResource->setAabb(bounds);
2484 payload.
mesh = meshResource;
2485 payload.
material = (pd.materialIndex >= 0 && pd.materialIndex <
static_cast<int>(gltfMaterials.size()))
2486 ? gltfMaterials[
static_cast<size_t>(pd.materialIndex)] : gltfMaterials.front();
2487 container->addMeshPayload(payload);
2488 meshToPayloadIndices[meshIndex].push_back(nextPayloadIndex++);
2493 for (
const auto& node : model.nodes) {
2495 nodePayload.
name = node.name;
2496 if (!node.matrix.empty()) decomposeNodeMatrix(node.matrix, nodePayload.
translation, nodePayload.
rotation, nodePayload.
scale);
2497 if (node.translation.size() == 3) nodePayload.
translation =
Vector3(
static_cast<float>(node.translation[0]),
static_cast<float>(node.translation[1]),
static_cast<float>(node.translation[2]));
2498 if (node.rotation.size() == 4) nodePayload.
rotation =
Quaternion(
static_cast<float>(node.rotation[0]),
static_cast<float>(node.rotation[1]),
static_cast<float>(node.rotation[2]),
static_cast<float>(node.rotation[3])).
normalized();
2499 if (node.scale.size() == 3) nodePayload.
scale =
Vector3(
static_cast<float>(node.scale[0]),
static_cast<float>(node.scale[1]),
static_cast<float>(node.scale[2]));
2500 if (node.mesh >= 0 && node.mesh <
static_cast<int>(meshToPayloadIndices.size())) {
2501 const auto& mapped = meshToPayloadIndices[
static_cast<size_t>(node.mesh)];
2504 nodePayload.
children = node.children;
2505 container->addNodePayload(nodePayload);
2509 int sceneIndex = model.defaultScene;
2510 if (sceneIndex < 0 && !model.scenes.empty()) sceneIndex = 0;
2511 if (sceneIndex >= 0 && sceneIndex <
static_cast<int>(model.scenes.size())) {
2512 for (
const auto nodeIndex : model.scenes[
static_cast<size_t>(sceneIndex)].nodes)
2513 container->addRootNodeIndex(nodeIndex);
2517 for (
auto& [name, track] : prepared.animTracks) {
2518 container->addAnimTrack(name, track);
2521 if (prepared.dracoPrimitiveCount > 0) {
2522 spdlog::info(
"GLB Draco summary [{}]: primitives={}, decoded={}, failed={}",
2523 debugName, prepared.dracoPrimitiveCount, prepared.dracoDecodeSuccessCount, prepared.dracoDecodeFailureCount);
2526 spdlog::info(
"GLB createFromPrepared [{}]: GPU resources created (textures={}, meshes={}, nodes={})",
2527 debugName, gltfTextures.size(), nextPayloadIndex, model.nodes.size());