get tangent generation working again

This commit is contained in:
Bailey Harrison 2024-02-16 22:08:14 +00:00
parent 6d726e6501
commit b06403ff32
7 changed files with 392 additions and 25 deletions

View File

@ -36,6 +36,8 @@ set(SRC_FILES
"src/libs/stb_impl.cpp"
"src/libs/tiny_gltf.h"
"src/libs/tiny_gltf_impl.cpp"
"src/libs/weldmesh.c"
"src/libs/weldmesh.h"
"src/renderer.cpp"
"src/resources/font.cpp"
"src/resources/material.cpp"

View File

@ -268,7 +268,7 @@ void Application::GameLoop()
int depth = find_depth(i, 0);
for (int j = 0; j < depth; ++j) tabs += std::string{" "};
ImGui::Text("%s%s", tabs.c_str(), t->tag.c_str());
ImGui::Text("%.1f %.1f %.1f", t->position.x, t->position.y, t->position.z);
//ImGui::Text("%.1f %.1f %.1f", t->position.x, t->position.y, t->position.z);
}
}
else {

194
src/libs/weldmesh.c Normal file
View File

@ -0,0 +1,194 @@
/**
* Copyright (C) 2011 by Morten S. Mikkelsen
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#include "weldmesh.h"
#include <string.h>
#include <assert.h>
#ifdef __APPLE__
#include <stdlib.h> /* OSX gets its malloc stuff through here */
#else
#include <malloc.h>
#endif
static void MergeVertsFast(int * piCurNrUniqueVertices, int * piRemapTable, float * pfVertexDataOut, int * piVertexIDs,
const float pfVertexDataIn[], const int iNrVerticesIn, const int iFloatsPerVert,
const int iL_in, const int iR_in, const int iChannelNum);
int WeldMesh(int * piRemapTable, float * pfVertexDataOut,
const float pfVertexDataIn[], const int iNrVerticesIn, const int iFloatsPerVert)
{
int iUniqueVertices = 0, i=0;
int * piVertexIDs = NULL;
if(iNrVerticesIn<=0) return 0;
iUniqueVertices = 0;
piVertexIDs = (int *) malloc(sizeof(int)*iNrVerticesIn);
if(piVertexIDs!=NULL)
{
for(i=0; i<iNrVerticesIn; i++)
{
piRemapTable[i] = -1;
piVertexIDs[i] = i;
}
MergeVertsFast(&iUniqueVertices, piRemapTable, pfVertexDataOut, piVertexIDs,
pfVertexDataIn, iNrVerticesIn, iFloatsPerVert, 0, iNrVerticesIn-1, 0);
free(piVertexIDs);
// debug check
for(i=0; i<iUniqueVertices; i++)
assert(piRemapTable[i]>=0);
}
return iUniqueVertices;
}
static void MergeVertsFast(int * piCurNrUniqueVertices, int * piRemapTable, float * pfVertexDataOut, int * piVertexIDs,
const float pfVertexDataIn[], const int iNrVerticesIn, const int iFloatsPerVert,
const int iL_in, const int iR_in, const int iChannelNum)
{
const int iCount = iR_in-iL_in+1;
int l=0;
float fMin, fMax, fAvg;
assert(iCount>0);
// make bbox
fMin = pfVertexDataIn[ piVertexIDs[iL_in]*iFloatsPerVert + iChannelNum]; fMax = fMin;
for(l=(iL_in+1); l<=iR_in; l++)
{
const int index = piVertexIDs[l]*iFloatsPerVert + iChannelNum;
const float fVal = pfVertexDataIn[index];
if(fMin>fVal) fMin=fVal;
else if(fMax<fVal) fMax=fVal;
}
// terminate recursion when the separation/average value
// is no longer strictly between fMin and fMax values.
fAvg = 0.5f*(fMax + fMin);
if(fAvg<=fMin || fAvg>=fMax || iCount==1)
{
if((iChannelNum+1) == iFloatsPerVert || iCount==1) // we are done, weld by hand
{
int iUniqueNewVertices = 0;
float * pfNewUniVertsOut = &pfVertexDataOut[ piCurNrUniqueVertices[0]*iFloatsPerVert ];
for(l=iL_in; l<=iR_in; l++)
{
const int index = piVertexIDs[l]*iFloatsPerVert;
int iFound = 0; // didn't find copy yet.
int l2=0;
while(l2<iUniqueNewVertices && iFound==0)
{
const int index2 = l2*iFloatsPerVert;
int iAllSame = 1;
int c=0;
while(iAllSame!=0 && c<iFloatsPerVert)
{
iAllSame &= (pfVertexDataIn[index+c] == pfNewUniVertsOut[index2+c] ? 1 : 0);
++c;
}
iFound = iAllSame;
if(iFound==0) ++l2;
}
// generate new entry
if(iFound==0)
{
memcpy(pfNewUniVertsOut+iUniqueNewVertices*iFloatsPerVert, pfVertexDataIn+index, sizeof(float)*iFloatsPerVert);
++iUniqueNewVertices;
}
assert(piRemapTable[piVertexIDs[l]] == -1); // has not yet been assigned
piRemapTable[piVertexIDs[l]] = piCurNrUniqueVertices[0] + l2;
}
piCurNrUniqueVertices[0] += iUniqueNewVertices;
}
else
{
MergeVertsFast(piCurNrUniqueVertices, piRemapTable, pfVertexDataOut, piVertexIDs,
pfVertexDataIn, iNrVerticesIn, iFloatsPerVert,
iL_in, iR_in, iChannelNum+1);
}
}
else
{
int iL=iL_in, iR=iR_in, index;
// seperate (by fSep) all points between iL_in and iR_in in pTmpVert[]
while(iL < iR)
{
int iReadyLeftSwap = 0;
int iReadyRightSwap = 0;
while(iReadyLeftSwap==0 && iL<iR)
{
assert(iL>=iL_in && iL<=iR_in);
index = piVertexIDs[iL]*iFloatsPerVert+iChannelNum;
iReadyLeftSwap = !(pfVertexDataIn[index]<fAvg) ? 1 : 0;
if(iReadyLeftSwap==0) ++iL;
}
while(iReadyRightSwap==0 && iL<iR)
{
assert(iR>=iL_in && iR<=iR_in);
index = piVertexIDs[iR]*iFloatsPerVert+iChannelNum;
iReadyRightSwap = pfVertexDataIn[index]<fAvg ? 1 : 0;
if(iReadyRightSwap==0) --iR;
}
assert( (iL<iR) || (iReadyLeftSwap==0 || iReadyRightSwap==0));
if(iReadyLeftSwap!=0 && iReadyRightSwap!=0)
{
int iID=0;
assert(iL<iR);
iID = piVertexIDs[iL];
piVertexIDs[iL] = piVertexIDs[iR];
piVertexIDs[iR] = iID;
++iL; --iR;
}
}
assert(iL==(iR+1) || (iL==iR));
if(iL==iR)
{
const int index = piVertexIDs[iR]*iFloatsPerVert+iChannelNum;
const int iReadyRightSwap = pfVertexDataIn[index]<fAvg ? 1 : 0;
if(iReadyRightSwap!=0) ++iL;
else --iR;
}
// recurse
if(iL_in <= iR)
MergeVertsFast(piCurNrUniqueVertices, piRemapTable, pfVertexDataOut, piVertexIDs,
pfVertexDataIn, iNrVerticesIn, iFloatsPerVert, iL_in, iR, iChannelNum); // weld all left of fSep
if(iL <= iR_in)
MergeVertsFast(piCurNrUniqueVertices, piRemapTable, pfVertexDataOut, piVertexIDs,
pfVertexDataIn, iNrVerticesIn, iFloatsPerVert, iL, iR_in, iChannelNum); // weld all right of (or equal to) fSep
}
}

49
src/libs/weldmesh.h Normal file
View File

@ -0,0 +1,49 @@
/**
* Copyright (C) 2011 by Morten S. Mikkelsen
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#ifndef __WELDMESH_H__
#define __WELDMESH_H__
#ifdef __cplusplus
extern "C" {
#endif
// piRemapTable must be initialized and point to an area in memory
// with the byte size: iNrVerticesIn * sizeof(int).
// pfVertexDataOut must be initialized and point to an area in memory
// with the byte size: iNrVerticesIn * iFloatsPerVert * sizeof(float).
// At the end of the WeldMesh() call the array pfVertexDataOut will contain
// unique vertices only. Each entry in piRemapTable contains the index to
// the new location of the vertex in pfVertexDataOut in units of iFloatsPerVert.
// Note that this code is suitable for welding both unindexed meshes but also
// indexed meshes which need to have duplicates removed. In the latter case
// one simply uses the remap table to convert the old index list to the new vertex array.
// Finally, the return value is the number of unique vertices found.
int WeldMesh(int * piRemapTable, float * pfVertexDataOut,
const float pfVertexDataIn[], const int iNrVerticesIn, const int iFloatsPerVert);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -4,6 +4,7 @@
#include "util/files.h"
#include "libs/mikktspace.h"
#include "libs/weldmesh.h"
#include "libs/tiny_gltf.h"
#include "components/mesh_renderable.h"
@ -160,6 +161,19 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic)
/* load all materials found in model */
// store some 1x1 colour textures as a hack to render solid colours
struct Color {
uint8_t r, g, b, a;
Color(const double* doubles)
{
r = static_cast<uint8_t>(lround(doubles[0] * 255.0));
g = static_cast<uint8_t>(lround(doubles[1] * 255.0));
b = static_cast<uint8_t>(lround(doubles[2] * 255.0));
a = static_cast<uint8_t>(lround(doubles[3] * 255.0));
}
};
//std::unordered_map<Color, std::shared_ptr<Texture>> colour_textures;
std::vector<std::shared_ptr<Material>> materials{};
materials.reserve(model.materials.size());
for (const tg::Material& material : model.materials) {
@ -182,11 +196,16 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic)
}
const auto& baseColorFactor4 = material.pbrMetallicRoughness.baseColorFactor;
if (baseColorFactor4[0] != 1.0 || baseColorFactor4[1] != 1.0 || baseColorFactor4[2] != 1.0 || baseColorFactor4[3] != 1.0) {
LOG_WARN("Material {} contains a base color value which isn't supported yet.", material.name);
if (material.pbrMetallicRoughness.baseColorTexture.index == -1) {
LOG_WARN("Material will be created with a white base color texture.");
LOG_INFO("Making color texture!");
throw std::runtime_error("TODO");
// convert double colors to integers
//Color col(baseColorFactor4.data());
//if (colour_textures.contains(col)) {
// }
}
else {
LOG_WARN("Material {} contains a base color multiplier which isn't supported yet.", material.name);
LOG_WARN("The material's base color texture will be used as-is.");
}
}
@ -243,6 +262,8 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic)
const size_t num_vertices = pos_accessor.count;
bool generate_tangents = false; // generating tangents creates a new index list and therefore all attribute accessors must be reassigned
// these checks are probably unneccesary assuming a valid glTF file
// if (pos_accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) throw std::runtime_error("Position att. must be float!");
// if (pos_accessor.type != 3) throw std::runtime_error("Position att. dim. must be 3!");
@ -279,7 +300,7 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic)
}
else {
// TODO: use MikkTSpace to generate tangents
throw std::runtime_error(std::string("No tangents found in primitive from ") + mesh.name);
generate_tangents = true;
}
// UV0
@ -326,13 +347,105 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic)
throw std::runtime_error(std::string("Invalid index buffer in primtive from: ") + mesh.name);
}
// combine vertices into one array
std::vector<Vertex> vertices;
if (generate_tangents) {
// generate tangents if they're not in the file
struct MeshData {
Attribute<glm::vec3>* positions;
Attribute<glm::vec3>* normals;
Attribute<glm::vec2>* uvs;
const uint32_t* indices;
size_t num_indices;
std::vector<Vertex>* new_vertices;
};
MeshData meshData{};
meshData.positions = &positions;
meshData.normals = &normals;
meshData.uvs = &uv0s;
meshData.indices = indices.data();
meshData.num_indices = num_indices;
meshData.new_vertices = &vertices;
vertices.resize(num_indices);
SMikkTSpaceInterface mts_interface{};
mts_interface.m_getNumFaces = [](const SMikkTSpaceContext* pContext) -> int {
const MeshData* meshData = static_cast<const MeshData*>(pContext->m_pUserData);
assert(meshData->num_indices % 3 == 0);
return meshData->num_indices / 3;
};
mts_interface.m_getNumVerticesOfFace = [](const SMikkTSpaceContext*, const int) -> int { return 3; };
mts_interface.m_getPosition = [](const SMikkTSpaceContext* pContext, float fvPosOut[], const int iFace, const int iVert) -> void {
const MeshData* const meshData = static_cast<const MeshData*>(pContext->m_pUserData);
const size_t i = iFace * 3 + iVert;
assert(i < meshData->num_indices);
const size_t vertex_index = meshData->indices[i];
const glm::vec3 pos = meshData->positions->operator[](vertex_index);
fvPosOut[0] = pos.x;
fvPosOut[1] = pos.y;
fvPosOut[2] = pos.z;
};
mts_interface.m_getNormal = [](const SMikkTSpaceContext* pContext, float fvNormOut[], const int iFace, const int iVert) -> void {
const MeshData* const meshData = static_cast<const MeshData*>(pContext->m_pUserData);
const size_t i = iFace * 3 + iVert;
assert(i < meshData->num_indices);
const size_t vertex_index = meshData->indices[i];
const glm::vec3 norm = meshData->normals->operator[](vertex_index);
fvNormOut[0] = norm.x;
fvNormOut[1] = norm.y;
fvNormOut[2] = norm.z;
};
mts_interface.m_getTexCoord = [](const SMikkTSpaceContext* pContext, float fvTexcOut[], const int iFace, const int iVert) -> void {
const MeshData* const meshData = static_cast<const MeshData*>(pContext->m_pUserData);
const size_t i = iFace * 3 + iVert;
assert(i < meshData->num_indices);
const size_t vertex_index = meshData->indices[i];
const glm::vec2 uv = meshData->uvs->operator[](vertex_index);
fvTexcOut[0] = uv.x;
fvTexcOut[1] = uv.y;
};
mts_interface.m_setTSpaceBasic = [](const SMikkTSpaceContext* pContext, const float fvTangent[], const float fSign, const int iFace,
const int iVert) -> void {
MeshData* const meshData = static_cast<MeshData*>(pContext->m_pUserData);
const size_t i = iFace * 3 + iVert;
assert(i < meshData->num_indices);
const size_t vertex_index = meshData->indices[i];
Vertex& new_v = meshData->new_vertices->operator[](i);
new_v.pos = meshData->positions->operator[](vertex_index);
new_v.norm = meshData->normals->operator[](vertex_index);
new_v.uv = meshData->uvs->operator[](vertex_index);
new_v.tangent.x = fvTangent[0];
new_v.tangent.y = fvTangent[1];
new_v.tangent.z = fvTangent[2];
new_v.tangent.w = fSign;
};
SMikkTSpaceContext mts_context{};
mts_context.m_pInterface = &mts_interface;
mts_context.m_pUserData = &meshData;
bool tan_result = genTangSpaceDefault(&mts_context);
if (tan_result == false) throw std::runtime_error("Failed to generate tangents!");
// regenerate indices as simple ones
indices.clear();
indices.reserve(meshData.new_vertices->size());
// temp generate simple indices
for (uint32_t i = 0; i < meshData.new_vertices->size(); ++i) {
indices.push_back(i);
}
}
else {
// combine vertices into one array
vertices.clear();
vertices.reserve(num_vertices);
for (size_t i = 0; i < num_vertices; ++i) {
Vertex v{.pos = positions[i], .norm = normals[i], .tangent = tangents[i], .uv = uv0s[i]};
vertices.push_back(v);
}
}
// generate mesh on GPU
std::shared_ptr<Mesh> engine_mesh = std::make_shared<Mesh>(scene.app()->renderer()->GetDevice(), vertices, indices);
@ -355,9 +468,12 @@ engine::Entity LoadGLTF(Scene& scene, const std::string& path, bool isStatic)
}
}
// glTF uses the Y-up convention so objects must be rotated to Z-up
const Entity parent =
scene.CreateEntity("test_node", 0, glm::vec3{}, glm::quat{glm::one_over_root_two<float>(), glm::one_over_root_two<float>(), 0.0f, 0.0f});
/* now create the entities and traverse the glTF scene hierarchy */
const std::filesystem::path filePath(path);
const std::string name = filePath.stem().string();
// glTF uses the Y-up convention so the parent object must be rotated to Z-up
const Entity parent = scene.CreateEntity(name, 0, glm::vec3{}, glm::quat{glm::one_over_root_two<float>(), glm::one_over_root_two<float>(), 0.0f, 0.0f});
std::vector<Entity> entities(model.nodes.size(), 0);
std::function<void(Entity, const tg::Node&)> generateEntities = [&](Entity parent_entity, const tg::Node& node) -> void {

View File

@ -50,7 +50,7 @@ void PlayGame(GameSettings settings)
graphics_settings.vsync = true;
graphics_settings.wait_for_present = false;
graphics_settings.msaa_level = engine::gfx::MSAALevel::kOff;
graphics_settings.enable_anisotropy = false;
graphics_settings.enable_anisotropy = true;
engine::Application::Configuration configuration{};
configuration.enable_frame_limiter = settings.enable_frame_limiter;
@ -167,15 +167,16 @@ void PlayGame(GameSettings settings)
scene2->AddComponent<CameraControllerComponent>(camera);
}
{ /* axes */
//engine::util::LoadMeshFromFile(scene2, app.GetResourcePath("models/MY_AXES.dae"), true);
{
/* axes */
// engine::util::LoadMeshFromFile(scene2, app.GetResourcePath("models/MY_AXES.dae"), true);
}
{ /* floor */
engine::Entity pivot = scene2->CreateEntity("pivot", 0, glm::vec3{ 0.0f, 0.0f, 0.0f });
engine::Entity pivot = scene2->CreateEntity("pivot", 0, glm::vec3{0.0f, 0.0f, 0.0f});
engine::Entity wall2 = scene2->CreateEntity("wall2", pivot, glm::vec3{-50.0f, -50.0f, 0.0f});
engine::Entity wall2 = scene2->CreateEntity("floor", pivot, glm::vec3{-50.0f, -50.0f, 0.0f});
auto wall_renderable = scene2->AddComponent<engine::MeshRenderableComponent>(wall2);
wall_renderable->mesh = GenCuboidMesh(app.renderer()->GetDevice(), 100.0f, 100.0f, 0.1f, 100.0f);
wall_renderable->material = std::make_unique<engine::Material>(app.renderer(), app.GetResource<engine::Shader>("builtin.fancy"));
@ -197,14 +198,19 @@ void PlayGame(GameSettings settings)
wall_renderable->material->SetAlbedoTexture(app.GetResource<engine::Texture>("builtin.white"));
}
auto teapot = engine::util::LoadGLTF(*scene2, app.GetResourcePath("models/teapot_with_tangents.glb"));
scene2->GetComponent<engine::TransformComponent>(teapot)->scale *= 10.0f;
//auto teapot2 = engine::util::LoadGLTF(*scene2, app.GetResourcePath("models/teapot.glb"));
//scene2->GetComponent<engine::TransformComponent>(teapot2)->scale *= 10.0f;
//scene2->GetComponent<engine::TransformComponent>(teapot2)->position.y += 5.0f;
//scene2->GetComponent<engine::TransformComponent>(teapot2)->rotation = glm::angleAxis(glm::pi<float>(), glm::vec3{ 0.0f, 0.0f, 1.0f });
//scene2->GetComponent<engine::TransformComponent>(teapot2)->rotation *= glm::angleAxis(glm::half_pi<float>(), glm::vec3{1.0f, 0.0f, 0.0f});
auto walls = engine::util::LoadGLTF(*scene2, app.GetResourcePath("models/walls_with_tangents.glb"));
//auto teapot = engine::util::LoadGLTF(*scene2, app.GetResourcePath("models/teapot_with_tangents.glb"));
//scene2->GetComponent<engine::TransformComponent>(teapot)->scale *= 10.0f;
auto teapot2 = engine::util::LoadGLTF(*scene2, app.GetResourcePath("models/teapot.glb"));
scene2->GetComponent<engine::TransformComponent>(teapot2)->scale *= 10.0f;
scene2->GetComponent<engine::TransformComponent>(teapot2)->position.y += 5.0f;
auto custom = scene2->AddComponent<engine::CustomComponent>(teapot2);
custom->onInit = [](void) { return; };
custom->onUpdate = [&](float dt) {
scene2->GetComponent<engine::TransformComponent>(teapot2)->rotation *= glm::angleAxis(dt, glm::vec3{0.0f, 1.0f, 0.0f});
};
// scene2->GetComponent<engine::TransformComponent>(teapot2)->rotation = glm::angleAxis(glm::pi<float>(), glm::vec3{ 0.0f, 0.0f, 1.0f });
// scene2->GetComponent<engine::TransformComponent>(teapot2)->rotation *= glm::angleAxis(glm::half_pi<float>(), glm::vec3{1.0f, 0.0f, 0.0f});
// auto walls = engine::util::LoadGLTF(*scene2, app.GetResourcePath("models/walls_with_tangents.glb"));
}
my_scene->GetSystem<CameraControllerSystem>()->next_scene_ = scene2;