From 37e109db4d7f771a7e0b6ce91f8ab86f062f885a Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Wed, 20 Sep 2017 18:42:52 +0800 Subject: [PATCH] Add support for loading and rendering skinned meshes --- data | 2 +- lib/emergent | 2 +- src/game/ant.cpp | 30 +++++++++++++- src/game/ant.hpp | 5 ++- src/model-loader.cpp | 83 +++++++++++++++++++++++++++++++++++++++ src/model-loader.hpp | 19 +++++++++ src/render-passes.cpp | 80 ++++++++++++++++++++++++++----------- src/render-passes.hpp | 7 +++- src/states/play-state.cpp | 1 + 9 files changed, 202 insertions(+), 27 deletions(-) diff --git a/data b/data index c08b221..e0fb251 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit c08b221aed4d3dbe82e8ffe5bc67d7b786993b28 +Subproject commit e0fb2511f4d08bb4823535ebdaa79de3cf2caf59 diff --git a/lib/emergent b/lib/emergent index 18ec684..0d4b841 160000 --- a/lib/emergent +++ b/lib/emergent @@ -1 +1 @@ -Subproject commit 18ec684943f9530a1a3851271dc1805a18e2b102 +Subproject commit 0d4b84100382a6053dfd2da5e7707bc28477ac87 diff --git a/src/game/ant.cpp b/src/game/ant.cpp index 9980bd4..3b42d01 100644 --- a/src/game/ant.cpp +++ b/src/game/ant.cpp @@ -25,9 +25,37 @@ Ant::Ant(Colony* colony): colony(colony), state(Ant::State::IDLE), transform(Transform::getIdentity()), - skeletonPose(nullptr) + pose(nullptr) { + pose = new Pose(colony->getAntModel()->getSkeleton()); + for (std::size_t i = 0; i < pose->getSkeleton()->getBoneCount(); ++i) + { + pose->setRelativeTransform(i, pose->getSkeleton()->getBindPose()->getRelativeTransform(i)); + } + pose->concatenate(); + modelInstance.setModel(colony->getAntModel()); + modelInstance.setPose(pose); +} + +Ant::~Ant() +{ + delete pose; +} + +void Ant::rotateHead() +{ + const Bone* headBone = pose->getSkeleton()->getBone("left-flagellum"); + if (headBone != nullptr) + { + std::size_t boneIndex = headBone->getIndex(); + + Transform transform = pose->getRelativeTransform(boneIndex); + transform.rotation = glm::normalize(transform.rotation * glm::angleAxis(glm::radians(5.0f), Vector3(0.0f, 1.0f, 0.0f))); + + pose->setRelativeTransform(boneIndex, transform); + pose->concatenate(); + } } void Ant::move(const Vector3& velocity) diff --git a/src/game/ant.hpp b/src/game/ant.hpp index c886402..b18ef71 100644 --- a/src/game/ant.hpp +++ b/src/game/ant.hpp @@ -65,6 +65,9 @@ public: * Creates an instance of Ant. */ Ant(Colony* colony); + ~Ant(); + + void rotateHead(); void move(const Vector3& velocity); @@ -96,7 +99,7 @@ private: Transform transform; ModelInstance modelInstance; - Pose* skeletonPose; + Pose* pose; }; inline const Colony* Ant::getColony() const diff --git a/src/model-loader.cpp b/src/model-loader.cpp index 6667854..6cb58a5 100644 --- a/src/model-loader.cpp +++ b/src/model-loader.cpp @@ -88,6 +88,7 @@ Model* ModelLoader::load(const std::string& filename) // Allocate model data ModelData* modelData = new ModelData(); + SkeletonData* skeletonData = nullptr; // Allocate material groups read32(&modelData->groupCount, &bufferOffset); @@ -168,6 +169,43 @@ Model* ModelLoader::load(const std::string& filename) read32(&modelData->indexData[i], &bufferOffset); } + // Read skeleton data + if (modelData->vertexFormat & WEIGHTS) + { + // Allocate skeleton data + skeletonData = new SkeletonData(); + + // Read bone count + read16(&skeletonData->boneCount, &bufferOffset); + + // Allocate bones + skeletonData->boneData = new BoneData[skeletonData->boneCount]; + + // Read bones + for (std::size_t i = 0; i < skeletonData->boneCount; ++i) + { + BoneData* boneData = &skeletonData->boneData[i]; + boneData->children = nullptr; + + readString(&boneData->name, &bufferOffset); + read16(&boneData->parent, &bufferOffset); + read16(&boneData->childCount, &bufferOffset); + boneData->children = new std::uint16_t[boneData->childCount]; + for (std::size_t j = 0; j < boneData->childCount; ++j) + { + read16(&boneData->children[j], &bufferOffset); + } + read32(&boneData->translation.x, &bufferOffset); + read32(&boneData->translation.y, &bufferOffset); + read32(&boneData->translation.z, &bufferOffset); + read32(&boneData->rotation.w, &bufferOffset); + read32(&boneData->rotation.x, &bufferOffset); + read32(&boneData->rotation.y, &bufferOffset); + read32(&boneData->rotation.z, &bufferOffset); + read32(&boneData->length, &bufferOffset); + } + } + // Free file data buffer delete[] buffer; @@ -294,12 +332,39 @@ Model* ModelLoader::load(const std::string& filename) model->addGroup(modelGroup); } + // Create skeleton + if (skeletonData != nullptr) + { + // Allocate skeleton + Skeleton* skeleton = new Skeleton(); + + // Construct bone hierarchy from bone data + constructBoneHierarchy(skeleton->getRootBone(), skeletonData->boneData, 0); + + // Calculate bind pose + skeleton->calculateBindPose(); + + // Add skeleton to model + model->setSkeleton(skeleton); + } + // Delete model data groups delete[] modelData->groups; // Delete model data delete modelData; + // Delete skeleton data + if (skeletonData != nullptr) + { + for (std::size_t i = 0; i < skeletonData->boneCount; ++i) + { + delete[] skeletonData->boneData[i].children; + } + delete[] skeletonData->boneData; + delete skeletonData; + } + return model; } @@ -307,3 +372,21 @@ void ModelLoader::setMaterialLoader(MaterialLoader* materialLoader) { this->materialLoader = materialLoader; } + +void ModelLoader::constructBoneHierarchy(Bone* bone, const BoneData* data, std::uint16_t index) +{ + bone->setName(data[index].name); + + Transform transform; + transform.translation = data[index].translation; + transform.rotation = data[index].rotation; + transform.scale = Vector3(1.0f); + + bone->setRelativeTransform(transform); + bone->setLength(data[index].length); + + for (std::uint16_t i = 0; i < data[index].childCount; ++i) + { + constructBoneHierarchy(bone->createChild(), data, data[index].children[i]); + } +} diff --git a/src/model-loader.hpp b/src/model-loader.hpp index a75e555..4a1d966 100644 --- a/src/model-loader.hpp +++ b/src/model-loader.hpp @@ -63,6 +63,25 @@ private: std::uint32_t* indexData; }; + struct BoneData + { + std::string name; + std::uint16_t parent; + std::uint16_t childCount; + std::uint16_t* children; + Vector3 translation; + Quaternion rotation; + float length; + }; + + struct SkeletonData + { + std::uint16_t boneCount; + BoneData* boneData; + }; + + static void constructBoneHierarchy(Bone* bone, const BoneData* data, std::uint16_t index); + MaterialLoader* materialLoader; }; diff --git a/src/render-passes.cpp b/src/render-passes.cpp index df10a2e..eb3d32b 100644 --- a/src/render-passes.cpp +++ b/src/render-passes.cpp @@ -305,6 +305,9 @@ LightingRenderPass::LightingRenderPass(): 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f); + maxBoneCount = 64; + + matrixPaletteParam = parameterSet.addParameter("matrixPalette", ShaderParameter::Type::MATRIX_4, maxBoneCount); modelParam = parameterSet.addParameter("modelMatrix", ShaderParameter::Type::MATRIX_4, 1); modelViewParam = parameterSet.addParameter("modelViewMatrix", ShaderParameter::Type::MATRIX_4, 1); modelViewProjectionParam = parameterSet.addParameter("modelViewProjectionMatrix", ShaderParameter::Type::MATRIX_4, 1); @@ -315,7 +318,7 @@ LightingRenderPass::LightingRenderPass(): directionalLightColorsParam = parameterSet.addParameter("directionalLightColors", ShaderParameter::Type::VECTOR_3, 1); directionalLightDirectionsParam = parameterSet.addParameter("directionalLightDirections", ShaderParameter::Type::VECTOR_3, 1); albedoOpacityMapParam = parameterSet.addParameter("albedoOpacityMap", ShaderParameter::Type::INT, 1); - metalnessRoughnessMapParam = parameterSet.addParameter("metalnessRoughness", ShaderParameter::Type::INT, 1); + metalnessRoughnessMapParam = parameterSet.addParameter("metalnessRoughnessMap", ShaderParameter::Type::INT, 1); normalOcclusionMapParam = parameterSet.addParameter("normalOcclusionMap", ShaderParameter::Type::INT, 1); diffuseCubemapParam = parameterSet.addParameter("diffuseCubemap", ShaderParameter::Type::INT, 1); specularCubemapParam = parameterSet.addParameter("specularCubemap", ShaderParameter::Type::INT, 1); @@ -348,15 +351,22 @@ bool LightingRenderPass::load(const RenderContext* renderContext) std::cerr << "Failed to load cubemap" << std::endl; } - // Load lighting shader + // Load unskinned shader shaderLoader.undefine(); shaderLoader.define("TEXTURE_COUNT", 0); shaderLoader.define("VERTEX_POSITION", EMERGENT_VERTEX_POSITION); shaderLoader.define("VERTEX_NORMAL", EMERGENT_VERTEX_NORMAL); shaderLoader.define("VERTEX_TEXCOORD", EMERGENT_VERTEX_TEXCOORD); + unskinnedShader = shaderLoader.load("data/shaders/lit-object.glsl", ¶meterSet); + + // Load skinned shader + shaderLoader.define("SKINNED"); + shaderLoader.define("MAX_BONE_COUNT", maxBoneCount); + shaderLoader.define("VERTEX_BONE_INDICES", EMERGENT_VERTEX_BONE_INDICES); + shaderLoader.define("VERTEX_BONE_WEIGHTS", EMERGENT_VERTEX_BONE_WEIGHTS); + skinnedShader = shaderLoader.load("data/shaders/lit-object.glsl", ¶meterSet); - lightingShader = shaderLoader.load("data/shaders/lit-object.glsl", ¶meterSet); - if (!lightingShader) + if (!unskinnedShader || !skinnedShader) { return false; } @@ -368,8 +378,11 @@ bool LightingRenderPass::load(const RenderContext* renderContext) void LightingRenderPass::unload() { - delete lightingShader; - lightingShader = nullptr; + delete unskinnedShader; + delete skinnedShader; + + unskinnedShader = nullptr; + skinnedShader = nullptr; for (auto it = shaderCache.begin(); it != shaderCache.end(); ++it) { @@ -789,34 +802,20 @@ void LightingRenderPass::render(const RenderContext* renderContext) glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - // Bind shader - Shader* shader = lightingShader; - shader->bind(); - - // Pass texture units to shader - shader->setParameter(albedoOpacityMapParam, 0); - shader->setParameter(normalOcclusionMapParam, 2); int directionalLightCount = 1; Vector3 directionalLightColors[3]; Vector3 directionalLightDirections[3]; directionalLightColors[0] = Vector3(1); directionalLightDirections[0] = glm::normalize(Vector3(camera.getView() * -Vector4(0, 0, -1, 0))); - - shader->setParameter(directionalLightCountParam, directionalLightCount); - shader->setParameter(directionalLightColorsParam, 0, &directionalLightColors[0], directionalLightCount); - shader->setParameter(directionalLightDirectionsParam, 0, &directionalLightDirections[0], directionalLightCount); - - shader->setParameter(cameraPositionParam, camera.getTranslation()); - + glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_CUBE_MAP, diffuseCubemap->getTextureID()); - shader->setParameter(diffuseCubemapParam, 3); glActiveTexture(GL_TEXTURE4); glBindTexture(GL_TEXTURE_CUBE_MAP, specularCubemap->getTextureID()); - shader->setParameter(specularCubemapParam, 4); + Shader* shader = nullptr; Texture* albedoOpacityMap = nullptr; Texture* metalnessRoughnessMap = nullptr; Texture* normalOcclusionMap = nullptr; @@ -837,6 +836,43 @@ void LightingRenderPass::render(const RenderContext* renderContext) // Skip render operations with unsupported vertex formats + // Select shader + Shader* targetShader = nullptr; + if (operation.pose != nullptr) + { + targetShader = skinnedShader; + } + else + { + targetShader = unskinnedShader; + } + + // Switch shader if necessary + if (shader != targetShader) + { + shader = targetShader; + + // Bind shader + shader->bind(); + + // Pass static params + shader->setParameter(albedoOpacityMapParam, 0); + shader->setParameter(metalnessRoughnessMapParam, 1); + shader->setParameter(normalOcclusionMapParam, 2); + shader->setParameter(diffuseCubemapParam, 3); + shader->setParameter(specularCubemapParam, 4); + shader->setParameter(directionalLightCountParam, directionalLightCount); + shader->setParameter(directionalLightColorsParam, 0, &directionalLightColors[0], directionalLightCount); + shader->setParameter(directionalLightDirectionsParam, 0, &directionalLightDirections[0], directionalLightCount); + shader->setParameter(cameraPositionParam, camera.getTranslation()); + } + + // Pass matrix palette + if (operation.pose != nullptr) + { + shader->setParameter(matrixPaletteParam, 0, operation.pose->getMatrixPalette(), operation.pose->getSkeleton()->getBoneCount()); + } + // Bind albedo-opacity map if (material->albedoOpacityMap != albedoOpacityMap) { diff --git a/src/render-passes.hpp b/src/render-passes.hpp index 564adeb..ae016ec 100644 --- a/src/render-passes.hpp +++ b/src/render-passes.hpp @@ -131,6 +131,7 @@ private: bool loadShader(const RenderOperation& operation); ShaderParameterSet parameterSet; + const ShaderParameter* matrixPaletteParam; const ShaderParameter* modelParam; const ShaderParameter* modelViewParam; const ShaderParameter* modelViewProjectionParam; @@ -146,9 +147,13 @@ private: const ShaderParameter* diffuseCubemapParam; const ShaderParameter* specularCubemapParam; + Shader* unskinnedShader; + Shader* skinnedShader; + + int maxBoneCount; ShaderLoader shaderLoader; std::map shaderCache; - Shader* lightingShader; + //Shader* lightingShader; Matrix4 biasMatrix; GLuint shadowMap; diff --git a/src/states/play-state.cpp b/src/states/play-state.cpp index bcace00..c783bdd 100644 --- a/src/states/play-state.cpp +++ b/src/states/play-state.cpp @@ -195,6 +195,7 @@ void PlayState::execute() if (pickAnt != nullptr) { pickAnt->getModelInstance()->setTranslation(pick); + pickAnt->rotateHead(); } // Update colony