From c0e3c59b99e92ca5c4ab8748383cbf2f8b081e14 Mon Sep 17 00:00:00 2001 From: Johannes Unterguggenberger Date: Thu, 11 Apr 2024 15:05:40 +0200 Subject: [PATCH] Added custom tangent space calculation (based on new (and shitty) ASSIMP code. --- auto_vk_toolkit/include/model.hpp | 14 +++ auto_vk_toolkit/src/model.cpp | 186 ++++++++++++++++++++++++++---- 2 files changed, 178 insertions(+), 22 deletions(-) diff --git a/auto_vk_toolkit/include/model.hpp b/auto_vk_toolkit/include/model.hpp index 419ecea0..c6a864d5 100644 --- a/auto_vk_toolkit/include/model.hpp +++ b/auto_vk_toolkit/include/model.hpp @@ -381,6 +381,19 @@ namespace avk * @param aMeshIndices Vector of mesh indices to meshes which shall be included in the animation. */ animation prepare_animation(uint32_t aAnimationIndex, const std::vector& aMeshIndices); + + /** Calculates the tangent space with Auto-Vk-Toolkit's implementation, + * possibly overwriting ASSIMPs tangents/bitangents. + * @param aMeshIndex Mesh which the tangent space shall be computed for + * @param aConfigSourceUV The set of UV coordinates to be used for tangent space calculation + */ + void calculate_tangent_space_for_mesh(mesh_index_t aMeshIndex, uint32_t aConfigSourceUV = 0); + + /** Calculates the tangent space with Auto-Vk-Toolkit's implementation for all meshes, + * possibly overwriting ASSIMPs tangents/bitangents. + * @param aConfigSourceUV The set of UV coordinates to be used for tangent space calculation + */ + void calculate_tangent_space_for_all_meshes(uint32_t aConfigSourceUV = 0); private: void initialize_materials(); @@ -418,6 +431,7 @@ namespace avk std::string mModelPath; const aiScene* mScene; std::vector> mMaterialConfigPerMesh; + std::unordered_map, std::vector>> mTangentsAndBitangents; }; using model = avk::owning_resource; diff --git a/auto_vk_toolkit/src/model.cpp b/auto_vk_toolkit/src/model.cpp index ede74aaa..8bea4877 100644 --- a/auto_vk_toolkit/src/model.cpp +++ b/auto_vk_toolkit/src/model.cpp @@ -2,6 +2,7 @@ #include #include "model.hpp" +#include namespace avk { @@ -600,43 +601,57 @@ namespace avk std::vector model_t::tangents_for_mesh(mesh_index_t aMeshIndex) const { - const aiMesh* paiMesh = mScene->mMeshes[aMeshIndex]; - auto n = paiMesh->mNumVertices; std::vector result; - result.reserve(n); - if (nullptr == paiMesh->mTangents) { - LOG_WARNING(std::format("The mesh at index {} does not contain tangents. Will return (1,0,0) tangents for each vertex.", aMeshIndex)); - for (decltype(n) i = 0; i < n; ++i) { - result.emplace_back(1.f, 0.f, 0.f); - } + + if (mTangentsAndBitangents.contains(aMeshIndex)) { + result = std::get<0>(mTangentsAndBitangents.at(aMeshIndex)); } else { - // We've got tangents. Proceed as planned. - for (decltype(n) i = 0; i < n; ++i) { - result.emplace_back(paiMesh->mTangents[i][0], paiMesh->mTangents[i][1], paiMesh->mTangents[i][2]); + const aiMesh* paiMesh = mScene->mMeshes[aMeshIndex]; + auto n = paiMesh->mNumVertices; + result.reserve(n); + if (nullptr == paiMesh->mTangents) { + LOG_WARNING(std::format("The mesh at index {} does not contain tangents. Will return (1,0,0) tangents for each vertex.", aMeshIndex)); + for (decltype(n) i = 0; i < n; ++i) { + result.emplace_back(1.f, 0.f, 0.f); + } + } + else { + // We've got tangents. Proceed as planned. + for (decltype(n) i = 0; i < n; ++i) { + result.emplace_back(paiMesh->mTangents[i][0], paiMesh->mTangents[i][1], paiMesh->mTangents[i][2]); + } } } + return result; } std::vector model_t::bitangents_for_mesh(mesh_index_t aMeshIndex) const { - const aiMesh* paiMesh = mScene->mMeshes[aMeshIndex]; - auto n = paiMesh->mNumVertices; std::vector result; - result.reserve(n); - if (nullptr == paiMesh->mBitangents) { - LOG_WARNING(std::format("The mesh at index {} does not contain bitangents. Will return (0,1,0) bitangents for each vertex.", aMeshIndex)); - for (decltype(n) i = 0; i < n; ++i) { - result.emplace_back(0.f, 1.f, 0.f); - } + + if (mTangentsAndBitangents.contains(aMeshIndex)) { + result = std::get<1>(mTangentsAndBitangents.at(aMeshIndex)); } else { - // We've got bitangents. Proceed as planned. - for (decltype(n) i = 0; i < n; ++i) { - result.emplace_back(paiMesh->mBitangents[i][0], paiMesh->mBitangents[i][1], paiMesh->mBitangents[i][2]); + const aiMesh* paiMesh = mScene->mMeshes[aMeshIndex]; + auto n = paiMesh->mNumVertices; + result.reserve(n); + if (nullptr == paiMesh->mBitangents) { + LOG_WARNING(std::format("The mesh at index {} does not contain bitangents. Will return (0,1,0) bitangents for each vertex.", aMeshIndex)); + for (decltype(n) i = 0; i < n; ++i) { + result.emplace_back(0.f, 1.f, 0.f); + } + } + else { + // We've got bitangents. Proceed as planned. + for (decltype(n) i = 0; i < n; ++i) { + result.emplace_back(paiMesh->mBitangents[i][0], paiMesh->mBitangents[i][1], paiMesh->mBitangents[i][2]); + } } } + return result; } @@ -1388,4 +1403,131 @@ namespace avk add_all_to_node_map(aNodeMap, aNode->mChildren[i]); } } + + void model_t::calculate_tangent_space_for_mesh(mesh_index_t aMeshIndex, uint32_t aConfigSourceUV) + { + mTangentsAndBitangents[aMeshIndex] = std::make_tuple(std::vector{}, std::vector{}); + auto& [meshTang, meshBitang] = mTangentsAndBitangents[aMeshIndex]; + auto numVerts = number_of_vertices_for_mesh(aMeshIndex); + // create space for the tangents and bitangents + meshTang.resize(numVerts); + meshBitang.resize(numVerts); + + // The following is mostly a copy&paste from ASSIMP's code (but not necessarily from the latest version, + // since at some point, incorrect tangents/bitangents calculation has been introduced, namely s.t. + // the bitangents would always be orthogonal to the tangents. This is wrong.) + + const aiMesh* pMesh = mScene->mMeshes[aMeshIndex]; + + // If the mesh consists of lines and/or points but not of + // triangles or higher-order polygons the normal vectors + // are undefined. + if (!(pMesh->mPrimitiveTypes & (aiPrimitiveType_TRIANGLE | aiPrimitiveType_POLYGON))) { + LOG_INFO("Tangents are undefined for line and point meshes"); + return; + } + + // what we can check, though, is if the mesh has normals and texture coordinates. That's a requirement + if (pMesh->mNormals == nullptr) { + LOG_ERROR("Failed to compute tangents; need normals"); + return; + } + if (aConfigSourceUV >= AI_MAX_NUMBER_OF_TEXTURECOORDS || !pMesh->mTextureCoords[aConfigSourceUV]) { + LOG_ERROR(std::format("Failed to compute tangents; need UV data in channel[{}]", aConfigSourceUV)); + return; + } + + const float angleEpsilon = 0.9999f; + + std::vector vertexDone(pMesh->mNumVertices, false); + const float qnan = std::numeric_limits::quiet_NaN(); + + const aiVector3D *meshPos = pMesh->mVertices; + const aiVector3D *meshNorm = pMesh->mNormals; + const aiVector3D *meshTex = pMesh->mTextureCoords[aConfigSourceUV]; + + // calculate the tangent and bitangent for every face + for (unsigned int a = 0; a < pMesh->mNumFaces; a++) { + const aiFace &face = pMesh->mFaces[a]; + if (face.mNumIndices < 3) { + // There are less than three indices, thus the tangent vector + // is not defined. We are finished with these vertices now, + // their tangent vectors are set to qnan. + for (unsigned int i = 0; i < face.mNumIndices; ++i) { + unsigned int idx = face.mIndices[i]; + vertexDone[idx] = true; + meshTang[idx] = to_vec3(aiVector3D(qnan)); + meshBitang[idx] = to_vec3(aiVector3D(qnan)); + } + + continue; + } + + // triangle or polygon... we always use only the first three indices. A polygon + // is supposed to be planar anyways.... + // FIXME: (thom) create correct calculation for multi-vertex polygons maybe? + const unsigned int p0 = face.mIndices[0], p1 = face.mIndices[1], p2 = face.mIndices[2]; + + // position differences p1->p2 and p1->p3 + aiVector3D v = meshPos[p1] - meshPos[p0], w = meshPos[p2] - meshPos[p0]; + + // texture offset p1->p2 and p1->p3 + float sx = meshTex[p1].x - meshTex[p0].x, sy = meshTex[p1].y - meshTex[p0].y; + float tx = meshTex[p2].x - meshTex[p0].x, ty = meshTex[p2].y - meshTex[p0].y; + float dirCorrection = (tx * sy - ty * sx) < 0.0f ? -1.0f : 1.0f; + // when t1, t2, t3 in same position in UV space, just use default UV direction. + if (sx * ty == sy * tx) { + sx = 0.0; + sy = 1.0; + tx = 1.0; + ty = 0.0; + } + + // tangent points in the direction where to positive X axis of the texture coord's would point in model space + // bitangent's points along the positive Y axis of the texture coord's, respectively + aiVector3D tangent, bitangent; + tangent.x = (w.x * sy - v.x * ty) * dirCorrection; + tangent.y = (w.y * sy - v.y * ty) * dirCorrection; + tangent.z = (w.z * sy - v.z * ty) * dirCorrection; + bitangent.x = (- w.x * sx + v.x * tx) * dirCorrection; + bitangent.y = (- w.y * sx + v.y * tx) * dirCorrection; + bitangent.z = (- w.z * sx + v.z * tx) * dirCorrection; + + // store for every vertex of that face + for (unsigned int b = 0; b < face.mNumIndices; ++b) { + unsigned int p = face.mIndices[b]; + + // project tangent and bitangent into the plane formed by the vertex' normal + aiVector3D localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]); + aiVector3D localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]) - localTangent * (bitangent * localTangent); + localTangent.NormalizeSafe(); + localBitangent.NormalizeSafe(); + + // reconstruct tangent/bitangent according to normal and bitangent/tangent when it's infinite or NaN. + bool invalid_tangent = is_special_float(localTangent.x) || is_special_float(localTangent.y) || is_special_float(localTangent.z); + bool invalid_bitangent = is_special_float(localBitangent.x) || is_special_float(localBitangent.y) || is_special_float(localBitangent.z); + if (invalid_tangent != invalid_bitangent) { + if (invalid_tangent) { + localTangent = meshNorm[p] ^ localBitangent; + localTangent.NormalizeSafe(); + } else { + localBitangent = localTangent ^ meshNorm[p]; + localBitangent.NormalizeSafe(); + } + } + + // and write it into the mesh. + meshTang[p] = to_vec3(localTangent); + meshBitang[p] = to_vec3(localBitangent); + } + } + } + + void model_t::calculate_tangent_space_for_all_meshes(uint32_t aConfigSourceUV) + { + auto n = mScene->mNumMeshes; + for (decltype(n) i = 0; i < n; ++i) { + calculate_tangent_space_for_mesh(i, aConfigSourceUV); + } + } }