vk-book/shared/UtilsGLTF.cpp

1258 lines
47 KiB
C++
Raw Normal View History

2025-05-23 21:13:53 -04:00
#include "UtilsGLTF.h"
bool assignUVandSampler(
const GLTFGlobalSamplers& samplers, const aiMaterial* mtlDescriptor, aiTextureType textureType, uint32_t& uvIndex,
uint32_t& textureSampler, int index)
{
aiString path;
aiTextureMapMode mapmode[3] = { aiTextureMapMode_Clamp, aiTextureMapMode_Clamp, aiTextureMapMode_Clamp };
const bool res = mtlDescriptor->GetTexture(textureType, index, &path, 0, &uvIndex, 0, 0, mapmode) == AI_SUCCESS;
switch (mapmode[0]) {
case aiTextureMapMode_Clamp:
textureSampler = samplers.clamp.index();
break;
case aiTextureMapMode_Wrap:
textureSampler = samplers.wrap.index();
break;
case aiTextureMapMode_Mirror:
textureSampler = samplers.mirror.index();
break;
default:
break;
}
return res;
}
namespace
{
void loadMaterialTexture(
const aiMaterial* mtlDescriptor, aiTextureType textureType, const char* assetFolder, lvk::Holder<lvk::TextureHandle>& textureHandle,
const std::unique_ptr<lvk::IContext>& ctx, bool sRGB, int index = 0)
{
if (mtlDescriptor->GetTextureCount(textureType) > 0) {
aiString path;
if (mtlDescriptor->GetTexture(textureType, index, &path) == AI_SUCCESS) {
aiString fullPath(assetFolder);
fullPath.Append(path.C_Str());
textureHandle = loadTexture(ctx, fullPath.C_Str(), lvk::TextureType_2D, sRGB);
if (textureHandle.empty()) {
assert(0);
exit(256);
}
}
}
}
uint32_t getNextMtxId(GLTFContext& gltf, const char* name, uint32_t& nextEmptyId, const mat4& mtx)
{
const auto it = gltf.bonesByName.find(name);
const uint32_t mtxId = (it == gltf.bonesByName.end()) ? nextEmptyId++ : it->second.boneId;
if (gltf.matrices.size() <= mtxId) {
gltf.matrices.resize(mtxId + 1);
}
gltf.matrices[mtxId] = mtx;
return mtxId;
}
} // namespace
GLTFMaterialDataGPU setupglTFMaterialData(
const std::unique_ptr<lvk::IContext>& ctx, const GLTFGlobalSamplers& samplers, const aiMaterial* mtlDescriptor, const char* assetFolder,
GLTFDataHolder& glTFDataholder, bool& useVolumetric, bool& usedoublesided)
{
GLTFMaterialTextures mat;
uint32_t materialTypeFlags = MaterialType_Invalid;
const uint32_t whitePixel = 0xFFFFFFFF;
mat.white = ctx->createTexture(
{
.type = lvk::TextureType_2D,
.format = lvk::Format_RGBA_SRGB8,
.dimensions = {1, 1},
.usage = lvk::TextureUsageBits_Sampled,
.data = &whitePixel,
.debugName = "white1x1",
},
"white1x1");
aiShadingMode shadingMode = aiShadingMode_NoShading;
if (mtlDescriptor->Get(AI_MATKEY_SHADING_MODEL, shadingMode) == AI_SUCCESS) {
if (shadingMode == aiShadingMode_Unlit) {
materialTypeFlags = MaterialType_Unlit;
}
}
loadMaterialTexture(mtlDescriptor, aiTextureType_BASE_COLOR, assetFolder, mat.baseColorTexture, ctx, true);
loadMaterialTexture(mtlDescriptor, aiTextureType_METALNESS, assetFolder, mat.surfacePropertiesTexture, ctx, false);
materialTypeFlags = MaterialType_MetallicRoughness;
// Load common textures
loadMaterialTexture(mtlDescriptor, aiTextureType_LIGHTMAP, assetFolder, mat.occlusionTexture, ctx, false);
loadMaterialTexture(mtlDescriptor, aiTextureType_EMISSIVE, assetFolder, mat.emissiveTexture, ctx, true);
loadMaterialTexture(mtlDescriptor, aiTextureType_NORMALS, assetFolder, mat.normalTexture, ctx, false);
// Sheen
loadMaterialTexture(mtlDescriptor, aiTextureType_SHEEN, assetFolder, mat.sheenColorTexture, ctx, true, 0);
loadMaterialTexture(mtlDescriptor, aiTextureType_SHEEN, assetFolder, mat.sheenRoughnessTexture, ctx, false, 1);
// Clearcoat
loadMaterialTexture(mtlDescriptor, aiTextureType_CLEARCOAT, assetFolder, mat.clearCoatTexture, ctx, true, 0);
loadMaterialTexture(mtlDescriptor, aiTextureType_CLEARCOAT, assetFolder, mat.clearCoatRoughnessTexture, ctx, false, 1);
loadMaterialTexture(mtlDescriptor, aiTextureType_CLEARCOAT, assetFolder, mat.clearCoatNormalTexture, ctx, false, 2);
// Specular
loadMaterialTexture(mtlDescriptor, aiTextureType_SPECULAR, assetFolder, mat.specularTexture, ctx, true, 0);
loadMaterialTexture(mtlDescriptor, aiTextureType_SPECULAR, assetFolder, mat.specularColorTexture, ctx, true, 1);
// Transmission
loadMaterialTexture(mtlDescriptor, aiTextureType_TRANSMISSION, assetFolder, mat.transmissionTexture, ctx, true, 0);
// Volume
loadMaterialTexture(mtlDescriptor, aiTextureType_TRANSMISSION, assetFolder, mat.thicknessTexture, ctx, true, 1);
// Iridescence
// loadMaterialTexture(mtlDescriptor, aiTextureType_IRID, assetFolder, mat.specularTexture, ctx, true, 0);
// Anisotropy
GLTFMaterialDataGPU res = {
.baseColorFactor = vec4(1.0f, 1.0f, 1.0f, 1.0f),
.metallicRoughnessNormalOcclusion = vec4(1.0f, 1.0f, 1.0f, 1.0f),
.specularFactors = vec4(1.0f, 1.0f, 1.0f, 1.0f),
.emissiveFactorAlphaCutoff = vec4(0.0f, 0.0f, 0.0f, 0.5f),
.occlusionTexture = mat.occlusionTexture.index(),
.emissiveTexture = mat.emissiveTexture.valid() ? mat.emissiveTexture.index() : mat.white.index(),
.baseColorTexture = mat.baseColorTexture.valid() ? mat.baseColorTexture.index() : mat.white.index(),
.surfacePropertiesTexture = mat.surfacePropertiesTexture.valid() ? mat.surfacePropertiesTexture.index() : mat.white.index(),
.normalTexture = mat.normalTexture.valid() ? mat.normalTexture.index() : ~0,
.sheenColorTexture = mat.sheenColorTexture.valid() ? mat.sheenColorTexture.index() : mat.white.index(),
.sheenRoughnessTexture = mat.sheenRoughnessTexture.valid() ? mat.sheenRoughnessTexture.index() : mat.white.index(),
.clearCoatTexture = mat.clearCoatTexture.valid() ? mat.clearCoatTexture.index() : mat.white.index(),
.clearCoatRoughnessTexture = mat.clearCoatRoughnessTexture.valid() ? mat.clearCoatRoughnessTexture.index() : mat.white.index(),
.clearCoatNormalTexture = mat.clearCoatNormalTexture.valid() ? mat.clearCoatNormalTexture.index() : mat.white.index(),
.specularTexture = mat.specularTexture.valid() ? mat.specularTexture.index() : mat.white.index(),
.specularColorTexture = mat.specularColorTexture.valid() ? mat.specularColorTexture.index() : mat.white.index(),
.transmissionTexture = mat.transmissionTexture.valid() ? mat.transmissionTexture.index() : mat.white.index(),
.thicknessTexture = mat.thicknessTexture.valid() ? mat.thicknessTexture.index() : mat.white.index(),
.iridescenceTexture = mat.iridescenceTexture.index(),
.iridescenceThicknessTexture = mat.iridescenceThicknessTexture.index(),
.anisotropyTexture = mat.anisotropyTexture.index(),
.materialTypeFlags = materialTypeFlags,
};
aiColor4D aiColor;
if (mtlDescriptor->Get(AI_MATKEY_COLOR_DIFFUSE, aiColor) == AI_SUCCESS) {
res.baseColorFactor = vec4(aiColor.r, aiColor.g, aiColor.b, aiColor.a);
}
assignUVandSampler(samplers, mtlDescriptor, aiTextureType_DIFFUSE, res.baseColorTextureUV, res.baseColorTextureSampler);
if (mtlDescriptor->Get(AI_MATKEY_COLOR_EMISSIVE, aiColor) == AI_SUCCESS) {
// mat.emissiveFactor = vec3(aiColor.r, aiColor.g, aiColor.b);
res.emissiveFactorAlphaCutoff = vec4(aiColor.r, aiColor.g, aiColor.b, 0.5f);
}
assignUVandSampler(samplers, mtlDescriptor, aiTextureType_EMISSIVE, res.emissiveTextureUV, res.emissiveTextureSampler);
ai_real emissiveStrength = 1.0f;
if (mtlDescriptor->Get(AI_MATKEY_EMISSIVE_INTENSITY, emissiveStrength) == AI_SUCCESS) {
res.emissiveFactorAlphaCutoff *= vec4(emissiveStrength, emissiveStrength, emissiveStrength, 1.0f);
}
if (materialTypeFlags & MaterialType_MetallicRoughness) {
ai_real metallicFactor;
if (mtlDescriptor->Get(AI_MATKEY_METALLIC_FACTOR, metallicFactor) == AI_SUCCESS) {
res.metallicRoughnessNormalOcclusion.x = metallicFactor;
}
assignUVandSampler(
samplers, mtlDescriptor, aiTextureType_METALNESS, res.surfacePropertiesTextureUV, res.surfacePropertiesTextureSampler);
ai_real roughnessFactor;
if (mtlDescriptor->Get(AI_MATKEY_ROUGHNESS_FACTOR, roughnessFactor) == AI_SUCCESS) {
res.metallicRoughnessNormalOcclusion.y = roughnessFactor;
}
} else if (materialTypeFlags & MaterialType_SpecularGlossiness) {
ai_real specularFactor[3];
if (mtlDescriptor->Get(AI_MATKEY_SPECULAR_FACTOR, specularFactor) == AI_SUCCESS) {
res.specularGlossiness.x = specularFactor[0];
res.specularGlossiness.y = specularFactor[1];
res.specularGlossiness.z = specularFactor[2];
}
assignUVandSampler(
samplers, mtlDescriptor, aiTextureType_SPECULAR, res.surfacePropertiesTextureUV, res.surfacePropertiesTextureSampler);
ai_real glossinessFactor;
if (mtlDescriptor->Get(AI_MATKEY_GLOSSINESS_FACTOR, glossinessFactor) == AI_SUCCESS) {
res.specularGlossiness.w = glossinessFactor;
}
}
ai_real normalScale;
if (mtlDescriptor->Get(AI_MATKEY_GLTF_TEXTURE_SCALE(aiTextureType_NORMALS, 0), normalScale) == AI_SUCCESS) {
res.metallicRoughnessNormalOcclusion.z = normalScale;
}
assignUVandSampler(samplers, mtlDescriptor, aiTextureType_NORMALS, res.normalTextureUV, res.normalTextureSampler);
ai_real occlusionStrength;
if (mtlDescriptor->Get(AI_MATKEY_GLTF_TEXTURE_SCALE(aiTextureType_LIGHTMAP, 0), occlusionStrength) == AI_SUCCESS) {
res.metallicRoughnessNormalOcclusion.w = occlusionStrength;
}
assignUVandSampler(samplers, mtlDescriptor, aiTextureType_LIGHTMAP, res.occlusionTextureUV, res.occlusionTextureSampler);
aiString alphaMode = aiString("OPAQUE");
if (mtlDescriptor->Get(AI_MATKEY_GLTF_ALPHAMODE, alphaMode) == AI_SUCCESS) {
if (alphaMode == aiString("MASK")) {
res.alphaMode = GLTFMaterialDataGPU::AlphaMode_Mask;
} else if (alphaMode == aiString("BLEND")) {
res.alphaMode = GLTFMaterialDataGPU::AlphaMode_Blend;
} else {
res.alphaMode = GLTFMaterialDataGPU::AlphaMode_Opaque;
}
}
ai_real alphaCutoff;
if (mtlDescriptor->Get(AI_MATKEY_GLTF_ALPHACUTOFF, alphaCutoff) == AI_SUCCESS) {
res.emissiveFactorAlphaCutoff.w = alphaCutoff;
}
// Extensions
// Sheen
{
bool useSheen = !mat.sheenColorTexture.empty() || !mat.sheenRoughnessTexture.empty();
aiColor4D sheenColorFactor;
if (mtlDescriptor->Get(AI_MATKEY_SHEEN_COLOR_FACTOR, sheenColorFactor) == AI_SUCCESS) {
res.sheenFactors = vec4(sheenColorFactor.r, sheenColorFactor.g, sheenColorFactor.b, sheenColorFactor.a);
useSheen = true;
}
ai_real sheenRoughnessFactor;
if (mtlDescriptor->Get(AI_MATKEY_SHEEN_ROUGHNESS_FACTOR, sheenRoughnessFactor) == AI_SUCCESS) {
res.sheenFactors.w = sheenRoughnessFactor;
useSheen = true;
}
if (assignUVandSampler(samplers, mtlDescriptor, aiTextureType_SHEEN, res.sheenColorTextureUV, res.sheenColorTextureSampler, 0)) {
useSheen = true;
}
if (assignUVandSampler(
samplers, mtlDescriptor, aiTextureType_SHEEN, res.sheenRoughnessTextureUV, res.sheenRoughnessTextureSampler, 1)) {
useSheen = true;
}
if (useSheen) {
res.materialTypeFlags |= MaterialType_Sheen;
}
}
// Clear coat
{
bool useClearCoat = !mat.clearCoatTexture.empty() || !mat.clearCoatRoughnessTexture.empty() || !mat.clearCoatNormalTexture.empty();
ai_real clearcoatFactor;
if (mtlDescriptor->Get(AI_MATKEY_CLEARCOAT_FACTOR, clearcoatFactor) == AI_SUCCESS) {
res.clearcoatTransmissionThickness.x = clearcoatFactor;
useClearCoat = true;
}
ai_real clearcoatRoughnessFactor;
if (mtlDescriptor->Get(AI_MATKEY_CLEARCOAT_ROUGHNESS_FACTOR, clearcoatRoughnessFactor) == AI_SUCCESS) {
res.clearcoatTransmissionThickness.y = clearcoatRoughnessFactor;
useClearCoat = true;
}
if (assignUVandSampler(
samplers, mtlDescriptor, aiTextureType_CLEARCOAT, res.clearCoatTextureUV, res.clearCoatNormalTextureSampler, 0)) {
useClearCoat = true;
}
if (assignUVandSampler(
samplers, mtlDescriptor, aiTextureType_CLEARCOAT, res.clearCoatRoughnessTextureUV, res.clearCoatRoughnessTextureSampler, 1)) {
useClearCoat = true;
}
if (assignUVandSampler(
samplers, mtlDescriptor, aiTextureType_CLEARCOAT, res.clearCoatNormalTextureUV, res.clearCoatNormalTextureSampler, 2)) {
useClearCoat = true;
}
if (useClearCoat) {
res.materialTypeFlags |= MaterialType_ClearCoat;
}
}
// Specular
{
bool useSpecular = !mat.specularColorTexture.empty() || !mat.specularTexture.empty();
ai_real specularFactor;
if (mtlDescriptor->Get(AI_MATKEY_SPECULAR_FACTOR, specularFactor) == AI_SUCCESS) {
res.specularFactors.w = specularFactor;
useSpecular = true;
}
assignUVandSampler(samplers, mtlDescriptor, aiTextureType_SPECULAR, res.specularTextureUV, res.specularTextureSampler, 0);
aiColor4D specularColorFactor;
if (mtlDescriptor->Get(AI_MATKEY_COLOR_SPECULAR, specularColorFactor) == AI_SUCCESS) {
res.specularFactors = vec4(specularColorFactor.r, specularColorFactor.g, specularColorFactor.b, res.specularFactors.w);
useSpecular = true;
}
assignUVandSampler(samplers, mtlDescriptor, aiTextureType_SPECULAR, res.specularColorTextureUV, res.specularColorTextureSampler, 1);
if (useSpecular) {
res.materialTypeFlags |= MaterialType_Specular;
}
}
// Transmission
{
bool useTransmission = !mat.transmissionTexture.empty();
ai_real transmissionFactor = 0.0f;
if (mtlDescriptor->Get(AI_MATKEY_TRANSMISSION_FACTOR, transmissionFactor) == AI_SUCCESS) {
res.clearcoatTransmissionThickness.z = transmissionFactor;
useTransmission = true;
}
if (useTransmission) {
res.materialTypeFlags |= MaterialType_Transmission;
useVolumetric = true;
}
assignUVandSampler(samplers, mtlDescriptor, aiTextureType_TRANSMISSION, res.transmissionTextureUV, res.transmissionTextureSampler, 0);
}
{
bool useVolume = !mat.thicknessTexture.empty();
ai_real thicknessFactor = 0.0f;
if (mtlDescriptor->Get(AI_MATKEY_VOLUME_THICKNESS_FACTOR, thicknessFactor) == AI_SUCCESS) {
res.clearcoatTransmissionThickness.w = thicknessFactor;
useVolume = true;
}
ai_real attenuationDistance = 0.0f;
if (mtlDescriptor->Get(AI_MATKEY_VOLUME_ATTENUATION_DISTANCE, attenuationDistance) == AI_SUCCESS) {
res.attenuation.w = attenuationDistance;
useVolume = true;
}
aiColor4D volumeAttenuationColor;
if (mtlDescriptor->Get(AI_MATKEY_VOLUME_ATTENUATION_COLOR, volumeAttenuationColor) == AI_SUCCESS) {
res.attenuation.x = volumeAttenuationColor.r;
res.attenuation.y = volumeAttenuationColor.g;
res.attenuation.z = volumeAttenuationColor.b;
useVolume = true;
}
if (useVolume) {
res.materialTypeFlags |= MaterialType_Transmission | MaterialType_Volume;
useVolumetric = true;
}
assignUVandSampler(samplers, mtlDescriptor, aiTextureType_TRANSMISSION, res.thicknessTextureUV, res.thicknessTextureSampler, 1);
}
// IOR
ai_real ior;
if (mtlDescriptor->Get(AI_MATKEY_REFRACTI, ior) == AI_SUCCESS) {
res.ior = ior;
}
// Doublesided
bool ds = false;
if (mtlDescriptor->Get(AI_MATKEY_TWOSIDED, ds) == AI_SUCCESS) {
usedoublesided |= ds;
}
mat.wasLoaded = true;
glTFDataholder.textures.push_back(std::move(mat));
return res;
}
static uint32_t getNumVertices(const aiScene& scene)
{
uint32_t num = 0;
for (uint32_t i = 0; i != scene.mNumMeshes; i++) {
num += scene.mMeshes[i]->mNumVertices;
}
return num;
}
static uint32_t getNumIndices(const aiScene& scene)
{
uint32_t num = 0;
for (uint32_t i = 0; i != scene.mNumMeshes; i++) {
for (uint32_t j = 0; j != scene.mMeshes[i]->mNumFaces; j++) {
LVK_ASSERT(scene.mMeshes[i]->mFaces[j].mNumIndices == 3);
num += scene.mMeshes[i]->mFaces[j].mNumIndices;
}
}
return num;
}
static uint32_t getNumMorphVertices(const aiScene& scene)
{
uint32_t num = 0;
for (uint32_t i = 0; i != scene.mNumMeshes; i++) {
num += scene.mMeshes[i]->mNumVertices * scene.mMeshes[i]->mNumAnimMeshes;
}
return num;
}
static uint32_t getNodeId(GLTFContext& gltf, const char* name)
{
for (uint32_t i = 0; i != gltf.nodesStorage.size(); i++) {
if (gltf.nodesStorage[i].name == name)
return i;
}
return ~0;
}
void updateLights(GLTFContext& gltf, lvk::ICommandBuffer& buf)
{
for (LightDataGPU& light : gltf.lights) {
if (light.nodeId == -1)
continue;
light.position = vec3(gltf.matrices[light.nodeId][3]);
light.direction = gltf.matrices[light.nodeId] * vec4(light.direction, 0.0);
}
LVK_ASSERT(gltf.lights.size() <= kMaxLights);
buf.cmdUpdateBuffer(gltf.lightsBuffer, 0, gltf.lights.size() * sizeof(LightDataGPU), gltf.lights.data());
}
void loadGLTF(GLTFContext& gltf, const char* glTFName, const char* glTFDataPath)
{
const aiScene* scene = aiImportFile(glTFName, aiProcess_Triangulate);
if (!scene || !scene->HasMeshes()) {
printf("Unable to load %s\n", glTFName);
exit(255);
}
SCOPE_EXIT
{
aiReleaseImport(scene);
};
std::vector<Vertex> vertices;
std::vector<VertexBoneData> skinningData;
std::vector<uint32_t> indices;
std::vector<uint32_t> startVertex;
std::vector<uint32_t> startIndex;
startVertex.push_back(0);
startIndex.push_back(0);
vertices.reserve(getNumVertices(*scene));
indices.reserve(getNumIndices(*scene));
skinningData.resize(getNumVertices(*scene));
std::vector<Vertex> morphData;
gltf.morphTargets.resize(scene->mNumMeshes);
morphData.reserve(getNumMorphVertices(*scene));
uint32_t numBones = 0;
uint32_t morphTargetsOffset = 0;
uint32_t vertOffset = 0;
for (uint32_t m = 0; m < scene->mNumMeshes; ++m) {
const aiMesh* mesh = scene->mMeshes[m];
gltf.meshesRemap[mesh->mName.C_Str()] = m;
for (uint32_t i = 0; i < mesh->mNumVertices; i++) {
const aiVector3D v = mesh->mVertices[i];
const aiVector3D n = mesh->mNormals ? mesh->mNormals[i] : aiVector3D(0.0f, 1.0f, 0.0f);
const aiColor4D c = mesh->mColors[0] ? mesh->mColors[0][i] : aiColor4D(1.0f, 1.0f, 1.0f, 1.0f);
const aiVector3D uv0 = mesh->mTextureCoords[0] ? mesh->mTextureCoords[0][i] : aiVector3D(0.0f, 0.0f, 0.0f);
const aiVector3D uv1 = mesh->mTextureCoords[1] ? mesh->mTextureCoords[1][i] : aiVector3D(0.0f, 0.0f, 0.0f);
vertices.push_back({
.position = vec3(v.x, v.y, v.z),
.normal = vec3(n.x, n.y, n.z),
.color = vec4(c.r, c.g, c.b, c.a),
.uv0 = vec2(uv0.x, 1.0f - uv0.y),
.uv1 = vec2(uv1.x, 1.0f - uv1.y),
});
if (mesh->mNumBones == 0) {
auto& vertex = skinningData[vertices.size() - 1];
vertex.meshId = m;
vertex.position = vec4(v.x, v.y, v.z, 0.0f);
vertex.normal = vec4(n.x, n.y, n.z, 0.0f);
}
}
startVertex.push_back((uint32_t)vertices.size());
for (uint32_t i = 0; i < mesh->mNumFaces; i++) {
for (int j = 0; j != 3; j++) {
indices.push_back(mesh->mFaces[i].mIndices[j]);
}
}
startIndex.push_back((uint32_t)indices.size());
gltf.hasBones = mesh->mNumBones > 0;
// load bones
for (uint32_t id = 0; id < mesh->mNumBones; id++) {
const aiBone& bone = *mesh->mBones[id];
const char* boneName = bone.mName.C_Str();
const bool hasBone = gltf.bonesByName.contains(boneName);
const uint32_t boneId = hasBone ? gltf.bonesByName[boneName].boneId : numBones++;
if (!hasBone) {
gltf.bonesByName[boneName] = {
.boneId = boneId,
.transform = aiMatrix4x4ToMat4(bone.mOffsetMatrix),
};
}
for (uint32_t w = 0; w < bone.mNumWeights; w++) {
const uint32_t vertexId = bone.mWeights[w].mVertexId;
assert(vertexId <= vertices.size());
VertexBoneData& vtx = skinningData[vertexId + vertOffset];
assert(vtx.meshId == ~0u || vtx.meshId == m);
vtx.position = vec4(vertices[vertexId + vertOffset].position, 1.0f);
vtx.normal = vec4(vertices[vertexId + vertOffset].normal, 0.0f);
vtx.meshId = m;
for (uint32_t i = 0; i < MAX_BONES_PER_VERTEX; i++) {
if (vtx.boneId[i] == ~0u) {
vtx.weight[i] = bone.mWeights[w].mWeight;
vtx.boneId[i] = boneId;
break;
}
}
}
}
vertOffset += mesh->mNumVertices;
}
// load morph targets
for (uint32_t meshId = 0; meshId != scene->mNumMeshes; meshId++) {
const aiMesh* m = scene->mMeshes[meshId];
if (!m->mNumAnimMeshes)
continue;
MorphTarget& morphTarget = gltf.morphTargets[meshId];
morphTarget.meshId = meshId;
for (uint32_t a = 0; a < m->mNumAnimMeshes; a++) {
const aiAnimMesh* mesh = m->mAnimMeshes[a];
for (uint32_t i = 0; i < mesh->mNumVertices; i++) {
const aiVector3D v = mesh->mVertices[i];
const aiVector3D n = mesh->mNormals ? mesh->mNormals[i] : aiVector3D(0.0f, 1.0f, 0.0f);
const aiVector3D srcNorm = m->mNormals ? m->mNormals[i] : aiVector3D(0.0f, 1.0f, 0.0f);
const aiColor4D c = mesh->mColors[0] ? mesh->mColors[0][i] : aiColor4D(1.0f, 1.0f, 1.0f, 1.0f);
const aiVector3D uv0 = mesh->mTextureCoords[0] ? mesh->mTextureCoords[0][i] : aiVector3D(0.0f, 0.0f, 0.0f);
const aiVector3D uv1 = mesh->mTextureCoords[1] ? mesh->mTextureCoords[1][i] : aiVector3D(0.0f, 0.0f, 0.0f);
morphData.push_back({
.position = vec3(v.x - m->mVertices[i].x, v.y - m->mVertices[i].y, v.z - m->mVertices[i].z),
.normal = vec3(n.x - srcNorm.x, n.y - srcNorm.y, n.z - srcNorm.z),
.color = vec4(c.r, c.g, c.b, c.a),
.uv0 = vec2(uv0.x, 1.0f - uv0.y),
.uv1 = vec2(uv1.x, 1.0f - uv1.y),
});
}
morphTarget.offset.push_back(morphTargetsOffset);
morphTargetsOffset += mesh->mNumVertices;
}
}
if (!scene->mRootNode) {
printf("Scene has no root node\n");
exit(255);
}
auto& ctx = gltf.app.ctx_;
for (unsigned int mtl = 0; mtl < scene->mNumMaterials; ++mtl) {
const aiMaterial* mtlDescriptor = scene->mMaterials[mtl];
gltf.matPerFrame.materials[mtl] = setupglTFMaterialData(
ctx, gltf.samplers, mtlDescriptor, glTFDataPath, gltf.glTFDataholder, gltf.isVolumetricMaterial, gltf.doublesided);
gltf.inspector.materials.push_back({
.name = mtlDescriptor->GetName().C_Str() ? mtlDescriptor->GetName().C_Str() : "Material",
.materialMask = gltf.matPerFrame.materials[mtl].materialTypeFlags,
.currentMaterialMask = gltf.matPerFrame.materials[mtl].materialTypeFlags,
});
}
uint32_t nonBoneMtxId = numBones;
const char* rootName = scene->mRootNode->mName.C_Str() ? scene->mRootNode->mName.C_Str() : "root";
gltf.nodesStorage.push_back({
.name = rootName,
.modelMtxId = getNextMtxId(gltf, rootName, nonBoneMtxId, aiMatrix4x4ToMat4(scene->mRootNode->mTransformation)),
.transform = aiMatrix4x4ToMat4(scene->mRootNode->mTransformation),
});
gltf.root = gltf.nodesStorage.size() - 1;
std::function<void(const aiNode* rootNode, GLTFNodeRef gltfNode)> traverseTree = [&](const aiNode* rootNode, GLTFNodeRef gltfNode) {
for (unsigned int m = 0; m < rootNode->mNumMeshes; ++m) {
const uint32_t meshIdx = rootNode->mMeshes[m];
const aiMesh* mesh = scene->mMeshes[meshIdx];
gltf.meshesStorage.push_back({
.primitive = lvk::Topology_Triangle,
.vertexOffset = startVertex[meshIdx],
.vertexCount = mesh->mNumVertices,
.indexOffset = startIndex[meshIdx],
.indexCount = mesh->mNumFaces * 3,
.matIdx = mesh->mMaterialIndex,
.sortingType =
gltf.matPerFrame.materials[mesh->mMaterialIndex].alphaMode == GLTFMaterialDataGPU::AlphaMode_Blend ? SortingType_Transparent
: gltf.matPerFrame.materials[mesh->mMaterialIndex].materialTypeFlags & MaterialType_Transmission ? SortingType_Transmission
: SortingType_Opaque,
});
gltf.nodesStorage[gltfNode].meshes.push_back(gltf.meshesStorage.size() - 1);
}
for (GLTFNodeRef i = 0; i < rootNode->mNumChildren; i++) {
const aiNode* node = rootNode->mChildren[i];
const char* childName = node->mName.C_Str() ? node->mName.C_Str() : "node";
const GLTFNode childNode{
.name = childName,
.modelMtxId = getNextMtxId(
gltf, childName, nonBoneMtxId,
gltf.matrices[gltf.nodesStorage[gltfNode].modelMtxId] * aiMatrix4x4ToMat4(node->mTransformation)),
.transform = aiMatrix4x4ToMat4(node->mTransformation),
};
gltf.nodesStorage.push_back(childNode);
const size_t nodeIdx = gltf.nodesStorage.size() - 1;
gltf.nodesStorage[gltfNode].children.push_back(nodeIdx);
traverseTree(node, nodeIdx);
}
};
traverseTree(scene->mRootNode, gltf.root);
initAnimations(gltf, scene);
// add dummy vertices to align buffer to 16 to run compute shader
gltf.maxVertices = (1 + (vertices.size() / 16)) * 16;
vertices.resize(gltf.maxVertices);
gltf.vertexBuffer = ctx->createBuffer({
.usage = lvk::BufferUsageBits_Vertex | lvk::BufferUsageBits_Storage,
.storage = lvk::StorageType_Device,
.size = sizeof(Vertex) * vertices.size(),
.data = vertices.data(),
.debugName = "Buffer: vertex",
});
size_t vssize = (1 + (skinningData.size() / 16)) * 16;
skinningData.resize(vssize);
assert(vssize == gltf.maxVertices);
gltf.vertexSkinningBuffer = ctx->createBuffer({
.usage = lvk::BufferUsageBits_Vertex | lvk::BufferUsageBits_Storage,
.storage = lvk::StorageType_Device,
.size = sizeof(VertexBoneData) * skinningData.size(),
.data = skinningData.data(),
.debugName = "Buffer: skinning vertex data",
});
const bool hasMorphData = !morphData.empty();
gltf.vertexMorphingBuffer = ctx->createBuffer({
.usage = lvk::BufferUsageBits_Vertex | lvk::BufferUsageBits_Storage,
.storage = lvk::StorageType_Device,
.size = hasMorphData ? sizeof(Vertex) * morphData.size() : sizeof(Vertex), // always have dummy buffer
.data = hasMorphData ? morphData.data() : nullptr,
.debugName = "Buffer: morphing vertex data",
});
gltf.morphStatesBuffer = gltf.app.ctx_->createBuffer({
.usage = lvk::BufferUsageBits_Vertex | lvk::BufferUsageBits_Storage,
.storage = lvk::StorageType_HostVisible,
.size = MAX_MORPHS * sizeof(MorphState),
.debugName = "Morphs matrices",
});
gltf.indexBuffer = ctx->createBuffer({
.usage = lvk::BufferUsageBits_Index,
.storage = lvk::StorageType_Device,
.size = sizeof(uint32_t) * indices.size(),
.data = indices.data(),
.debugName = "Buffer: index",
});
const lvk::VertexInput vdesc = {
.attributes = { { .location = 0, .format = lvk::VertexFormat::Float3, .offset = 0 },
{ .location = 1, .format = lvk::VertexFormat::Float3, .offset = 12 },
{ .location = 2, .format = lvk::VertexFormat::Float4, .offset = 24 },
{ .location = 3, .format = lvk::VertexFormat::Float2, .offset = 40 },
{ .location = 4, .format = lvk::VertexFormat::Float2, .offset = 48 }, },
.inputBindings = { { .stride = sizeof(Vertex) } },
};
gltf.vert = loadShaderModule(ctx, "data/shaders/gltf/main.vert");
gltf.frag = loadShaderModule(ctx, "data/shaders/gltf/main.frag");
gltf.animation = loadShaderModule(ctx, "data/shaders/gltf/animation.comp");
gltf.pipelineSolid = ctx->createRenderPipeline({
.vertexInput = vdesc,
.smVert = gltf.vert,
.smFrag = gltf.frag,
.color = { { .format = ctx->getSwapchainFormat() } },
.depthFormat = gltf.app.getDepthFormat(),
.cullMode = gltf.doublesided ? lvk::CullMode_None : lvk::CullMode_Back,
});
gltf.pipelineTransparent = ctx->createRenderPipeline({
.vertexInput = vdesc,
.smVert = gltf.vert,
.smFrag = gltf.frag,
.color = { {
.format = ctx->getSwapchainFormat(),
.blendEnabled = true,
.rgbBlendOp = lvk::BlendOp_Subtract,
.alphaBlendOp = lvk::BlendOp_Subtract,
.srcRGBBlendFactor = lvk::BlendFactor_SrcColor,
.srcAlphaBlendFactor = lvk::BlendFactor_SrcAlpha,
.dstRGBBlendFactor = lvk::BlendFactor_OneMinusDstColor,
.dstAlphaBlendFactor = lvk::BlendFactor_OneMinusDstAlpha,
} },
.depthFormat = gltf.app.getDepthFormat(),
.cullMode = lvk::CullMode_Back,
});
gltf.matBuffer = ctx->createBuffer({
.usage = lvk::BufferUsageBits_Storage,
.storage = lvk::StorageType_HostVisible,
.size = sizeof(gltf.matPerFrame),
.data = &gltf.matPerFrame,
.debugName = "PerFrame materials",
});
const EnvironmentsPerFrame envPerFrame = {
.environments = { {
.envMapTexture = gltf.envMapTextures.envMapTexture.index(),
.envMapTextureSampler = gltf.samplers.clamp.index(),
.envMapTextureIrradiance = gltf.envMapTextures.envMapTextureIrradiance.index(),
.envMapTextureIrradianceSampler = gltf.samplers.clamp.index(),
.lutBRDFTexture = gltf.envMapTextures.texBRDF_LUT.index(),
.lutBRDFTextureSampler = gltf.samplers.clamp.index(),
.envMapTextureCharlie = gltf.envMapTextures.envMapTextureCharlie.index(),
.envMapTextureCharlieSampler = gltf.samplers.clamp.index(),
} },
};
gltf.envBuffer = ctx->createBuffer({
.usage = lvk::BufferUsageBits_Storage,
.storage = lvk::StorageType_HostVisible,
.size = sizeof(envPerFrame),
.data = &envPerFrame,
.debugName = "PerFrame environments",
});
gltf.lightsBuffer = ctx->createBuffer({
.usage = lvk::BufferUsageBits_Storage,
.storage = lvk::StorageType_HostVisible,
.size = sizeof(LightDataGPU) * kMaxLights,
.debugName = "Lights",
});
// Load lights
// Atten = 1/( att0 + att1 * d + att2 * d*d)
{
for (size_t i = 0; i < scene->mNumLights; ++i) {
LightDataGPU ld;
const aiLight* light = scene->mLights[i];
ld.color = vec3(light->mColorDiffuse[0], light->mColorDiffuse[1], light->mColorDiffuse[2]);
ld.nodeId = getNodeId(gltf, light->mName.C_Str());
ld.direction = vec3(light->mDirection[0], light->mDirection[1], light->mDirection[2]);
ld.range = 1000.0f;
if (light->mType == aiLightSource_POINT) {
ld.type = LightType_Point;
} else if (light->mType == aiLightSource_SPOT) {
ld.type = LightType_Spot;
ld.innerConeCos = light->mAngleInnerCone;
ld.outerConeCos = light->mAngleOuterCone;
} else if (light->mType == aiLightSource_DIRECTIONAL) {
ld.type = LightType_Directional;
}
gltf.lights.push_back(ld);
}
if (gltf.lights.empty()) {
gltf.lights.push_back(LightDataGPU());
}
}
{
lvk::ICommandBuffer& buf = ctx->acquireCommandBuffer();
updateLights(gltf, buf);
ctx->submit(buf);
}
gltf.perFrameBuffer = ctx->createBuffer({
.usage = lvk::BufferUsageBits_Uniform,
.storage = lvk::StorageType_Device,
.size = sizeof(GLTFFrameData),
.data = &gltf.frameData,
.debugName = "GLTFContext::perFrameBuffer",
});
LVK_ASSERT(gltf.pipelineSolid.valid());
// Cameras
gltf.cameras.reserve(scene->mNumCameras);
for (uint32_t c = 0; c < scene->mNumCameras; ++c) {
aiCamera* camera = scene->mCameras[c];
gltf.cameras.push_back({ .name = camera->mName.C_Str(),
.nodeIdx = getNodeId(gltf, camera->mName.C_Str()),
.pos = aiVector3DToVec3(camera->mPosition),
.up = aiVector3DToVec3(camera->mUp),
.lookAt = aiVector3DToVec3(camera->mUp),
.hFOV = camera->mHorizontalFOV,
.near = camera->mClipPlaneNear,
.far = camera->mClipPlaneFar,
.aspect = camera->mAspect,
.orthoWidth = camera->mOrthographicWidth });
}
}
void updateCamera(GLTFContext& gltf, const mat4& model, mat4& view, mat4& proj, float aspectRatio)
{
if (gltf.inspector.activeCamera == ~0u || gltf.inspector.activeCamera >= gltf.cameras.size())
return;
const GLTFCamera& cam = gltf.cameras[gltf.inspector.activeCamera];
if (cam.nodeIdx == ~0u)
return;
view = glm::inverse(model * gltf.matrices[cam.nodeIdx]);
proj = cam.getProjection(aspectRatio);
}
void buildTransformsList(GLTFContext& gltf)
{
gltf.transforms.clear();
gltf.opaqueNodes.clear();
gltf.transmissionNodes.clear();
gltf.transparentNodes.clear();
std::function<void(GLTFNodeRef gltfNode)> traverseTree = [&](GLTFNodeRef nodeRef) {
GLTFNode& node = gltf.nodesStorage[nodeRef];
for (GLTFNodeRef meshId : node.meshes) {
const GLTFMesh& mesh = gltf.meshesStorage[meshId];
gltf.transforms.push_back({
.modelMtxId = node.modelMtxId,
.matId = mesh.matIdx,
.nodeRef = nodeRef,
.meshRef = meshId,
.sortingType = mesh.sortingType,
});
if (mesh.sortingType == SortingType_Transparent) {
gltf.transparentNodes.push_back(gltf.transforms.size() - 1);
} else if (mesh.sortingType == SortingType_Transmission) {
gltf.transmissionNodes.push_back(gltf.transforms.size() - 1);
} else {
gltf.opaqueNodes.push_back(gltf.transforms.size() - 1);
}
}
for (GLTFNodeRef child : node.children) {
traverseTree(child);
}
};
traverseTree(gltf.root);
gltf.transformBuffer = gltf.app.ctx_->createBuffer({
.usage = lvk::BufferUsageBits_Storage,
.storage = lvk::StorageType_HostVisible,
.size = gltf.transforms.size() * sizeof(GLTFTransforms),
.data = gltf.transforms.data(),
.debugName = "Per Frame data",
});
gltf.matricesBuffer = gltf.app.ctx_->createBuffer({
.usage = lvk::BufferUsageBits_Storage,
.storage = lvk::StorageType_HostVisible,
.size = gltf.matrices.size() * sizeof(mat4),
.data = gltf.matrices.data(),
.debugName = "Node matrices",
});
}
void sortTransparentNodes(GLTFContext& gltf, const vec3& cameraPos)
{
// glTF spec expects to sort based on pivot positions (not sure correct way though)
std::sort(gltf.transparentNodes.begin(), gltf.transparentNodes.end(), [&](uint32_t a, uint32_t b) {
float sqrDistA = glm::length2(cameraPos - vec3(gltf.matrices[gltf.transforms[a].modelMtxId][3]));
float sqrDistB = glm::length2(cameraPos - vec3(gltf.matrices[gltf.transforms[b].modelMtxId][3]));
return sqrDistA < sqrDistB;
});
}
void renderGLTF(GLTFContext& gltf, const mat4& model, const mat4& view, const mat4& proj, bool rebuildRenderList)
{
auto& ctx = gltf.app.ctx_;
const vec4 camPos = glm::inverse(view)[3];
gltf.inspector.animations = animationsGLTF(gltf);
gltf.inspector.cameras = camerasGLTF(gltf);
if (rebuildRenderList || gltf.transforms.empty()) {
buildTransformsList(gltf);
}
sortTransparentNodes(gltf, camPos);
gltf.frameData = {
.model = model,
.view = view,
.proj = proj,
.cameraPos = camPos,
};
struct PushConstants {
uint64_t draw;
uint64_t materials;
uint64_t environments;
uint64_t lights;
uint64_t transforms;
uint64_t matrices;
uint32_t envId;
uint32_t transmissionFramebuffer;
uint32_t transmissionFramebufferSampler;
uint32_t lightsCount;
} pushConstants = {
.draw = ctx->gpuAddress(gltf.perFrameBuffer),
.materials = ctx->gpuAddress(gltf.matBuffer),
.environments = ctx->gpuAddress(gltf.envBuffer),
.lights = ctx->gpuAddress(gltf.lightsBuffer),
.transforms = ctx->gpuAddress(gltf.transformBuffer),
.matrices = ctx->gpuAddress(gltf.matricesBuffer),
.envId = 0,
.transmissionFramebuffer = 0,
.transmissionFramebufferSampler = gltf.samplers.clamp.index(),
.lightsCount = (uint32_t)gltf.lights.size(),
};
lvk::ICommandBuffer& buf = ctx->acquireCommandBuffer();
buf.cmdUpdateBuffer(gltf.perFrameBuffer, gltf.frameData);
const bool isSizeChanged = ctx->getDimensions(ctx->getCurrentSwapchainTexture()) != ctx->getDimensions(gltf.offscreenTex[0]);
if (gltf.offscreenTex[0].empty() || isSizeChanged) {
const lvk::Dimensions res = ctx->getDimensions(ctx->getCurrentSwapchainTexture());
for (lvk::Holder<lvk::TextureHandle>& holder : gltf.offscreenTex) {
holder = ctx->createTexture({
.type = lvk::TextureType_2D,
.format = ctx->getSwapchainFormat(),
.dimensions = {res.width, res.height},
.usage = lvk::TextureUsageBits_Attachment | lvk::TextureUsageBits_Sampled,
.numMipLevels = lvk::calcNumMipLevels(res.width, res.height),
.debugName = "offscreenTex",
});
}
}
auto drawUI = [&](lvk::ICommandBuffer& buf, const lvk::Framebuffer& framebuffer) {
if (gltf.inspector.activeCamera == ~0u)
gltf.app.drawGrid(buf, proj, vec3(0, -1.0f, 0));
else
gltf.app.drawGrid(buf, proj * view, vec3(0, -1.0f, 0), camPos);
gltf.app.imgui_->beginFrame(framebuffer);
gltf.app.drawFPS();
gltf.app.drawMemo();
gltf.app.drawGTFInspector(gltf.inspector);
bool uploadMat = false;
for (uint32_t m = 0; m != gltf.inspector.materials.size(); m++) {
if (gltf.inspector.materials[m].modified) {
gltf.inspector.materials[m].modified = false;
uploadMat = true;
gltf.matPerFrame.materials[m].materialTypeFlags = gltf.inspector.materials[m].currentMaterialMask;
}
}
if (uploadMat) {
ctx->upload(gltf.matBuffer, &gltf.matPerFrame, sizeof(gltf.matPerFrame));
}
if (gltf.inspector.activeCamera >= gltf.cameras.size() && !gltf.cameras.empty()) {
gltf.canvas3d.clear();
gltf.canvas3d.setMatrix(proj * view);
const float windowAspect = ctx->getAspectRatio(framebuffer.color[0].texture);
for (const auto& c : gltf.cameras) {
if (c.nodeIdx == ~0u)
continue;
const mat4 camView = glm::inverse(model * gltf.matrices[c.nodeIdx]);
const mat4 camProj = c.getProjection(windowAspect);
gltf.canvas3d.frustum(camView, camProj, vec4(1, 0, 0, 1));
gltf.canvas3d.render(*ctx.get(), framebuffer, buf);
}
}
gltf.app.imgui_->endFrame(buf);
};
if (gltf.animated) {
buf.cmdUpdateBuffer(gltf.matricesBuffer, 0, gltf.matrices.size() * sizeof(mat4), gltf.matrices.data());
if (gltf.morphing) {
buf.cmdUpdateBuffer(gltf.morphStatesBuffer, 0, gltf.morphStates.size() * sizeof(MorphState), gltf.morphStates.data());
}
updateLights(gltf, buf);
if ((gltf.skinning && gltf.hasBones) || gltf.morphing) {
// Run compute shader to do skinning and morphing
struct ComputeSetup {
uint64_t matrices;
uint64_t morphStates;
uint64_t morphVertexBuffer;
uint64_t inBuffer;
uint64_t outBuffer;
uint32_t numMorphStates;
} pc = {
.matrices = ctx->gpuAddress(gltf.matricesBuffer),
.morphStates = ctx->gpuAddress(gltf.morphStatesBuffer),
.morphVertexBuffer = ctx->gpuAddress(gltf.vertexMorphingBuffer),
.inBuffer = ctx->gpuAddress(gltf.vertexSkinningBuffer),
.outBuffer = ctx->gpuAddress(gltf.vertexBuffer),
.numMorphStates = static_cast<uint32_t>(gltf.morphStates.size()),
};
buf.cmdBindComputePipeline(gltf.pipelineComputeAnimations);
buf.cmdPushConstants(pc);
// clang-format off
buf.cmdDispatchThreadGroups(
{ .width = gltf.maxVertices / 16 },
{ .buffers = { lvk::BufferHandle(gltf.vertexBuffer),
lvk::BufferHandle(gltf.morphStatesBuffer),
lvk::BufferHandle(gltf.matricesBuffer),
lvk::BufferHandle(gltf.vertexSkinningBuffer) } });
// clang-format on
}
}
const bool screenCopy = gltf.isScreenCopyRequired();
{
// 1st pass
pushConstants.transmissionFramebuffer = 0;
const lvk::RenderPass renderPass = {
.color = { { .loadOp = lvk::LoadOp_Clear, .clearColor = { 1.0f, 1.0f, 1.0f, 1.0f } } },
.depth = { .loadOp = lvk::LoadOp_Clear, .clearDepth = 1.0f },
};
const lvk::Framebuffer framebuffer = {
.color = { { .texture = screenCopy ? gltf.offscreenTex[gltf.currentOffscreenTex] : ctx->getCurrentSwapchainTexture() } },
.depthStencil = { .texture = gltf.app.getDepthTexture() },
};
{
buf.cmdBeginRendering(renderPass, framebuffer, { .buffers = { { lvk::BufferHandle(gltf.vertexBuffer) } } });
buf.cmdBindVertexBuffer(0, gltf.vertexBuffer, 0);
buf.cmdBindIndexBuffer(gltf.indexBuffer, lvk::IndexFormat_UI32);
buf.cmdBindDepthState({ .compareOp = lvk::CompareOp_Less, .isDepthWriteEnabled = true });
buf.cmdBindRenderPipeline(gltf.pipelineSolid);
buf.cmdPushConstants(pushConstants);
for (uint32_t transformId : gltf.opaqueNodes) {
const GLTFTransforms transform = gltf.transforms[transformId];
buf.cmdPushDebugGroupLabel(gltf.nodesStorage[transform.nodeRef].name.c_str(), 0xff0000ff);
const GLTFMesh submesh = gltf.meshesStorage[transform.meshRef];
buf.cmdDrawIndexed(submesh.indexCount, 1, submesh.indexOffset, submesh.vertexOffset, transformId);
buf.cmdPopDebugGroupLabel();
}
if (!screenCopy) {
drawUI(buf, framebuffer);
}
buf.cmdEndRendering();
}
}
if (!gltf.transmissionNodes.empty() || !gltf.transparentNodes.empty()) {
// 2nd pass
const lvk::RenderPass renderPass = {
.color = { { .loadOp = lvk::LoadOp_Load } },
.depth = { .loadOp = lvk::LoadOp_Load },
};
const lvk::Framebuffer framebuffer = {
.color = { { .texture = ctx->getCurrentSwapchainTexture() } },
.depthStencil = { .texture = gltf.app.getDepthTexture() },
};
if (screenCopy) {
buf.cmdCopyImage(
gltf.offscreenTex[gltf.currentOffscreenTex], ctx->getCurrentSwapchainTexture(),
ctx->getDimensions(ctx->getCurrentSwapchainTexture()));
buf.cmdGenerateMipmap(gltf.offscreenTex[gltf.currentOffscreenTex]);
pushConstants.transmissionFramebuffer = gltf.offscreenTex[gltf.currentOffscreenTex].index();
buf.cmdPushConstants(pushConstants);
}
buf.cmdBeginRendering(renderPass, framebuffer, { .textures = { lvk::TextureHandle(gltf.offscreenTex[gltf.currentOffscreenTex]) } });
buf.cmdBindVertexBuffer(0, gltf.vertexBuffer, 0);
buf.cmdBindIndexBuffer(gltf.indexBuffer, lvk::IndexFormat_UI32);
buf.cmdBindDepthState({ .compareOp = lvk::CompareOp_Less, .isDepthWriteEnabled = true });
// Volumetric opaque
buf.cmdBindRenderPipeline(gltf.pipelineSolid);
buf.cmdPushConstants(pushConstants);
for (uint32_t transformId : gltf.transmissionNodes) {
const GLTFTransforms transform = gltf.transforms[transformId];
buf.cmdPushDebugGroupLabel(gltf.nodesStorage[transform.nodeRef].name.c_str(), 0x00FF00ff);
const GLTFMesh submesh = gltf.meshesStorage[transform.meshRef];
buf.cmdDrawIndexed(submesh.indexCount, 1, submesh.indexOffset, submesh.vertexOffset, transformId);
buf.cmdPopDebugGroupLabel();
}
//
buf.cmdBindRenderPipeline(gltf.pipelineTransparent);
buf.cmdPushConstants(pushConstants);
for (uint32_t transformId : gltf.transparentNodes) {
const GLTFTransforms transform = gltf.transforms[transformId];
buf.cmdPushDebugGroupLabel(gltf.nodesStorage[transform.nodeRef].name.c_str(), 0x00FF00ff);
const GLTFMesh submesh = gltf.meshesStorage[transform.meshRef];
buf.cmdDrawIndexed(submesh.indexCount, 1, submesh.indexOffset, submesh.vertexOffset, transformId);
buf.cmdPopDebugGroupLabel();
}
drawUI(buf, framebuffer);
buf.cmdEndRendering();
}
ctx->wait(ctx->submit(buf, ctx->getCurrentSwapchainTexture()));
gltf.currentOffscreenTex = (gltf.currentOffscreenTex + 1) % LVK_ARRAY_NUM_ELEMENTS(gltf.offscreenTex);
}
MaterialType detectMaterialType(const aiMaterial* mtl)
{
aiShadingMode shadingMode = aiShadingMode_NoShading;
if (mtl->Get(AI_MATKEY_SHADING_MODEL, shadingMode) == AI_SUCCESS) {
if (shadingMode == aiShadingMode_Unlit) {
return MaterialType_Unlit;
}
}
if (shadingMode == aiShadingMode_PBR_BRDF) {
ai_real factor = 0;
if (mtl->Get(AI_MATKEY_GLOSSINESS_FACTOR, factor) == AI_SUCCESS) {
return MaterialType_SpecularGlossiness;
} else if (mtl->Get(AI_MATKEY_METALLIC_FACTOR, factor) == AI_SUCCESS) {
return MaterialType_MetallicRoughness;
}
}
LLOGW("Unknown material type\n");
return MaterialType_Invalid;
}
void printPrefix(int ofs)
{
for (int i = 0; i < ofs; i++)
printf("\t");
}
void printMat4(const aiMatrix4x4& m)
{
if (!m.IsIdentity()) {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
printf("%f ;", m[i][j]);
} else {
printf(" Identity");
}
}
void animateGLTF(GLTFContext& gltf, AnimationState& anim, float dt)
{
if (gltf.transforms.empty())
return;
if (gltf.pipelineComputeAnimations.empty()) {
gltf.pipelineComputeAnimations = gltf.app.ctx_->createComputePipeline({
.smComp = gltf.animation,
});
}
// we support only one single animation at this time
anim.active = anim.animId != ~0;
gltf.animated = anim.active;
if (anim.active) {
updateAnimation(gltf, anim, dt);
}
}
void animateBlendingGLTF(GLTFContext& gltf, AnimationState& anim1, AnimationState& anim2, float weight, float dt)
{
if (gltf.transforms.empty())
return;
if (gltf.pipelineComputeAnimations.empty()) {
gltf.pipelineComputeAnimations = gltf.app.ctx_->createComputePipeline({
.smComp = gltf.animation,
});
}
anim1.active = anim1.animId != ~0;
anim2.active = anim2.animId != ~0;
gltf.animated = anim1.active || anim2.active;
if (anim1.active && anim2.active) {
updateAnimationBlending(gltf, anim1, anim2, weight, dt);
} else if (anim1.active) {
updateAnimation(gltf, anim1, dt);
} else if (anim2.active) {
updateAnimation(gltf, anim2, dt);
}
}
std::vector<std::string> camerasGLTF(GLTFContext& gltf)
{
std::vector<std::string> names;
names.reserve(gltf.cameras.size() + 1);
for (auto c : gltf.cameras) {
names.push_back(c.name);
}
names.push_back("Application cam");
return names;
}
std::vector<std::string> animationsGLTF(GLTFContext& gltf)
{
std::vector<std::string> names;
names.reserve(gltf.animations.size());
for (auto c : gltf.animations) {
names.push_back(c.name);
}
return names;
}