GPU skinning refactoring

This commit is contained in:
Vladislav Khorev 2026-04-16 17:48:23 +03:00
parent 83cceecccd
commit 2ef2e456f7
4 changed files with 161 additions and 138 deletions

View File

@ -758,7 +758,7 @@ namespace ZL
prepared = true;
}
/*
void BoneSystem::ComputeSkinningMatrices(int frame, std::vector<Matrix4f>& outMatrices) const
{
int startingKeyFrame = -1;
@ -815,7 +815,7 @@ namespace ZL
outMatrices[i] = currentBoneMatrixWorld4 * invertedStartBoneMatrixWorld4;
}
}
*/
void BoneSystem::Interpolate(int frame)
{
int startingFrame = -1;
@ -928,18 +928,18 @@ namespace ZL
}
void BoneAnimationData::prepareGpuSkinningVBOs() {
void GpuSkinningShaderData::prepareGpuSkinningVBOs(BoneSystem& model) {
if (gpuSkinningPrepared)
{
return;
}
model.gpuBoneData.PrepareGpuSkinningData(model.verticesBoneWeight);
gpuBoneData.PrepareGpuSkinningData(model.verticesBoneWeight);
// Upload bind-pose mesh (static, done once)
bindPoseMutable.AssignFrom(model.startMesh);
auto& gpu = model.gpuBoneData;
auto& gpu = gpuBoneData;
boneIndices0VBO = std::make_shared<VBOHolder>();
glBindBuffer(GL_ARRAY_BUFFER, boneIndices0VBO->getBuffer());
@ -964,5 +964,90 @@ namespace ZL
gpuSkinningPrepared = true;
}
void GpuSkinningShaderData::RenderVBO(Renderer& renderer)
{
// Bind position and texcoord VBOs
glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.positionVBO->getBuffer());
renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
if (bindPoseMutable.texCoordVBO) {
glBindBuffer(GL_ARRAY_BUFFER, bindPoseMutable.texCoordVBO->getBuffer());
renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
}
// Bind bone index VBOs
glBindBuffer(GL_ARRAY_BUFFER, boneIndices0VBO->getBuffer());
renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, boneIndices1VBO->getBuffer());
renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
// Bind bone weight VBOs
glBindBuffer(GL_ARRAY_BUFFER, boneWeights0VBO->getBuffer());
renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, boneWeights1VBO->getBuffer());
renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(bindPoseMutable.data.PositionData.size()));
}
void GpuSkinningShaderData::ComputeSkinningMatrices(const std::vector<Bone>& startBones, const std::vector<AnimationKeyFrame>& keyFrames, int frame)
{
int startingKeyFrame = -1;
for (size_t i = 0; i < keyFrames.size() - 1; i++)
{
int oldFrame = keyFrames[i].frame;
int nextFrame = keyFrames[i + 1].frame;
if (frame >= oldFrame && frame < nextFrame)
{
startingKeyFrame = static_cast<int>(i);
break;
}
}
skinningMatrices.resize(startBones.size());
if (startingKeyFrame == -1)
{
for (auto& m : skinningMatrices) m = Matrix4f::Identity();
return;
}
int modifiedFrameNumber = frame - keyFrames[startingKeyFrame].frame;
int diffFrames = keyFrames[startingKeyFrame + 1].frame - keyFrames[startingKeyFrame].frame;
float t = (modifiedFrameNumber + 0.f) / diffFrames;
const std::vector<Bone>& oneFrameBones = keyFrames[startingKeyFrame].bones;
const std::vector<Bone>& nextFrameBones = keyFrames[startingKeyFrame + 1].bones;
for (size_t i = 0; i < startBones.size(); i++)
{
Vector3f interpPos;
interpPos(0) = oneFrameBones[i].boneStartWorld(0) + t * (nextFrameBones[i].boneStartWorld(0) - oneFrameBones[i].boneStartWorld(0));
interpPos(1) = oneFrameBones[i].boneStartWorld(1) + t * (nextFrameBones[i].boneStartWorld(1) - oneFrameBones[i].boneStartWorld(1));
interpPos(2) = oneFrameBones[i].boneStartWorld(2) + t * (nextFrameBones[i].boneStartWorld(2) - oneFrameBones[i].boneStartWorld(2));
Matrix3f oneFrameBonesMatrix = oneFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
Matrix3f nextFrameBonesMatrix = nextFrameBones[i].boneMatrixWorld.block<3, 3>(0, 0);
Eigen::Quaternionf q1 = Eigen::Quaternionf(oneFrameBonesMatrix).normalized();
Eigen::Quaternionf q2 = Eigen::Quaternionf(nextFrameBonesMatrix).normalized();
Eigen::Quaternionf result = q1.slerp(t, q2);
Matrix3f boneMatrixWorld3 = result.toRotationMatrix();
Matrix4f currentBoneMatrixWorld4 = Eigen::Matrix4f::Identity();
currentBoneMatrixWorld4.block<3, 3>(0, 0) = boneMatrixWorld3;
currentBoneMatrixWorld4.block<3, 1>(0, 3) = interpPos;
Matrix4f startBoneMatrixWorld4 = keyFrames[0].bones[i].boneMatrixWorld;
Matrix4f invertedStartBoneMatrixWorld4 = startBoneMatrixWorld4.inverse();
skinningMatrices[i] = currentBoneMatrixWorld4 * invertedStartBoneMatrixWorld4;
}
}
}

View File

@ -12,8 +12,6 @@ namespace ZL
Vector3f boneStartWorld;
float boneLength;
Matrix4f boneMatrixWorld;
// boneVector = boneLength * (0, 1, 0) <20> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// Then multiply by boneMatrixWorld <20> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>
int parent;
std::vector<int> children;
@ -59,22 +57,18 @@ namespace ZL
std::vector<Animation> animations;
int startingFrame = 0;
GpuBoneData gpuBoneData;
void LoadFromFile(const std::string& fileName, const std::string& ZIPFileName = "");
void LoadFromBinaryFile(const std::string& fileName, const std::string& ZIPFileName = "");
void Interpolate(int frame);
// GPU skinning: compute skinning matrices without modifying the mesh
void ComputeSkinningMatrices(int frame, std::vector<Matrix4f>& outMatrices) const;
//void ComputeSkinningMatrices(int frame, std::vector<Matrix4f>& outMatrices) const;
};
struct BoneAnimationData {
BoneSystem model;
float currentFrame = 0.f;
int lastFrame = -1;
int totalFrames = 1;
struct GpuSkinningShaderData {
GpuBoneData gpuBoneData;
// GPU skinning data (lazily initialized)
VertexRenderStruct bindPoseMutable;
@ -85,10 +79,22 @@ namespace ZL
bool gpuSkinningPrepared = false;
std::vector<Eigen::Matrix4f> skinningMatrices;
void prepareGpuSkinningVBOs();
void prepareGpuSkinningVBOs(BoneSystem& model);
void RenderVBO(Renderer& renderer);
void ComputeSkinningMatrices(const std::vector<Bone>& startBones, const std::vector<AnimationKeyFrame>& keyFrames, int frame);
};
struct BoneAnimationData {
BoneSystem model;
float currentFrame = 0.f;
int lastFrame = -1;
int totalFrames = 1;
GpuSkinningShaderData gpuSkinningShaderData;
};
};

View File

@ -27,9 +27,6 @@ void Character::loadBinaryAnimation(AnimationState state, const std::string& fil
data.totalFrames = 1;
}
}
/*void Character::setTexture(std::shared_ptr<Texture> tex) {
texture = tex;
}*/
void Character::setTarget(const Eigen::Vector3f& target,
std::function<void()> onArrived) {
@ -259,7 +256,7 @@ void Character::update(int64_t deltaMs) {
if (static_cast<int>(anim.currentFrame) != anim.lastFrame) {
if (useGpuSkinning) {
anim.model.ComputeSkinningMatrices(static_cast<int>(anim.currentFrame), anim.skinningMatrices);
anim.gpuSkinningShaderData.ComputeSkinningMatrices(anim.model.startBones, anim.model.animations[0].keyFrames, static_cast<int>(anim.currentFrame));
} else {
anim.model.Interpolate(static_cast<int>(anim.currentFrame));
}
@ -310,14 +307,15 @@ void Character::drawGpuSkinning(Renderer& renderer) {
}
auto& anim = it->second;
anim.prepareGpuSkinningVBOs();
anim.gpuSkinningShaderData.prepareGpuSkinningVBOs(anim.model);
if (anim.skinningMatrices.empty())
if (anim.gpuSkinningShaderData.skinningMatrices.empty())
{
if (anim.model.animations.empty() || anim.model.animations[0].keyFrames.empty()) return;
anim.model.ComputeSkinningMatrices(
anim.model.animations[0].keyFrames[0].frame, anim.skinningMatrices);
if (anim.skinningMatrices.empty()) return;
anim.gpuSkinningShaderData.ComputeSkinningMatrices(anim.model.startBones, anim.model.animations[0].keyFrames, static_cast<int>(anim.currentFrame));
if (anim.gpuSkinningShaderData.skinningMatrices.empty()) return;
}
static const std::string skinningShaderName = "skinning";
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
@ -337,51 +335,56 @@ void Character::drawGpuSkinning(Renderer& renderer) {
// Upload bone skinning matrices
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
static_cast<int>(anim.skinningMatrices.size()), false,
anim.skinningMatrices[0].data());
static_cast<int>(anim.gpuSkinningShaderData.skinningMatrices.size()), false,
anim.gpuSkinningShaderData.skinningMatrices[0].data());
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
// Bind VAO (desktop only)
#ifndef EMSCRIPTEN
#ifndef __ANDROID__
if (anim.bindPoseMutable.vao) {
glBindVertexArray(anim.bindPoseMutable.vao->getBuffer());
if (anim.gpuSkinningShaderData.bindPoseMutable.vao) {
glBindVertexArray(anim.gpuSkinningShaderData.bindPoseMutable.vao->getBuffer());
renderer.shaderManager.EnableVertexAttribArrays();
}
#endif
#endif
// Bind position and texcoord VBOs
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.positionVBO->getBuffer());
renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
if (anim.bindPoseMutable.texCoordVBO) {
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.texCoordVBO->getBuffer());
renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
}
// Bind bone index VBOs
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices0VBO->getBuffer());
renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices1VBO->getBuffer());
renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
// Bind bone weight VBOs
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights0VBO->getBuffer());
renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights1VBO->getBuffer());
renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(anim.bindPoseMutable.data.PositionData.size()));
anim.gpuSkinningShaderData.RenderVBO(renderer);
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
}
bool Character::prepareGpuSkinning()
{
AnimationState drawState = resolveActiveState();
auto it = animations.find(drawState);
if (it == animations.end())
{
return false;
}
auto& anim = it->second;
anim.gpuSkinningShaderData.prepareGpuSkinningVBOs(anim.model);
if (anim.gpuSkinningShaderData.skinningMatrices.empty()) {
if (anim.model.animations.empty() || anim.model.animations[0].keyFrames.empty())
{
return false;
}
anim.gpuSkinningShaderData.ComputeSkinningMatrices(anim.model.startBones, anim.model.animations[0].keyFrames, static_cast<int>(anim.currentFrame));
if (anim.gpuSkinningShaderData.skinningMatrices.empty())
{
return false;
}
}
return true;
}
// ==================== Shadow depth pass ====================
void Character::drawShadowDepth(Renderer& renderer) {
@ -418,14 +421,7 @@ void Character::drawShadowDepthGpuSkinning(Renderer& renderer) {
auto it = animations.find(drawState);
if (it == animations.end()) return;
auto& anim = it->second;
anim.prepareGpuSkinningVBOs();
if (anim.skinningMatrices.empty()) {
if (anim.model.animations.empty() || anim.model.animations[0].keyFrames.empty()) return;
anim.model.ComputeSkinningMatrices(
anim.model.animations[0].keyFrames[0].frame, anim.skinningMatrices);
if (anim.skinningMatrices.empty()) return;
}
if (!prepareGpuSkinning()) return;
static const std::string shadowSkinningShader = "shadow_depth_skinning";
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
@ -440,36 +436,10 @@ void Character::drawShadowDepthGpuSkinning(Renderer& renderer) {
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
static_cast<int>(anim.skinningMatrices.size()), false,
anim.skinningMatrices[0].data());
#ifndef EMSCRIPTEN
#ifndef __ANDROID__
if (anim.bindPoseMutable.vao) {
glBindVertexArray(anim.bindPoseMutable.vao->getBuffer());
renderer.shaderManager.EnableVertexAttribArrays();
}
#endif
#endif
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.positionVBO->getBuffer());
renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
if (anim.bindPoseMutable.texCoordVBO) {
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.texCoordVBO->getBuffer());
renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
}
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices0VBO->getBuffer());
renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices1VBO->getBuffer());
renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights0VBO->getBuffer());
renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights1VBO->getBuffer());
renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(anim.bindPoseMutable.data.PositionData.size()));
static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false,
it->second.gpuSkinningShaderData.skinningMatrices[0].data());
it->second.gpuSkinningShaderData.RenderVBO(renderer);
renderer.PopMatrix();
renderer.shaderManager.PopShader();
@ -528,15 +498,7 @@ void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matri
auto it = animations.find(drawState);
if (it == animations.end() || !texture) return;
auto& anim = it->second;
anim.prepareGpuSkinningVBOs();
if (anim.skinningMatrices.empty()) {
if (anim.model.animations.empty() || anim.model.animations[0].keyFrames.empty()) return;
anim.model.ComputeSkinningMatrices(
anim.model.animations[0].keyFrames[0].frame, anim.skinningMatrices);
if (anim.skinningMatrices.empty()) return;
}
if (!prepareGpuSkinning()) return;
static const std::string skinningShadowShader = "skinning_shadow";
static const std::string boneMatricesUniform = "uBoneMatrices[0]";
@ -562,44 +524,12 @@ void Character::drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matri
renderer.RotateMatrix(modelCorrectionRotation.toRotationMatrix());
renderer.RenderUniformMatrix4fvArray(boneMatricesUniform,
static_cast<int>(anim.skinningMatrices.size()), false,
anim.skinningMatrices[0].data());
static_cast<int>(it->second.gpuSkinningShaderData.skinningMatrices.size()), false,
it->second.gpuSkinningShaderData.skinningMatrices[0].data());
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
#ifndef EMSCRIPTEN
#ifndef __ANDROID__
if (anim.bindPoseMutable.vao) {
glBindVertexArray(anim.bindPoseMutable.vao->getBuffer());
renderer.shaderManager.EnableVertexAttribArrays();
}
#endif
#endif
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.positionVBO->getBuffer());
renderer.VertexAttribPointer3fv("vPosition", 0, NULL);
if (anim.bindPoseMutable.texCoordVBO) {
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.texCoordVBO->getBuffer());
renderer.VertexAttribPointer2fv("vTexCoord", 0, NULL);
}
// Bind normals for diffuse lighting (skinning_shadow shader skins them)
if (anim.bindPoseMutable.normalVBO) {
glBindBuffer(GL_ARRAY_BUFFER, anim.bindPoseMutable.normalVBO->getBuffer());
renderer.VertexAttribPointer3fv("vNormal", 0, NULL);
}
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices0VBO->getBuffer());
renderer.VertexAttribPointer4fv("aBoneIndices0", 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, anim.boneIndices1VBO->getBuffer());
renderer.VertexAttribPointer2fv("aBoneIndices1", 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights0VBO->getBuffer());
renderer.VertexAttribPointer4fv("aBoneWeights0", 0, NULL);
glBindBuffer(GL_ARRAY_BUFFER, anim.boneWeights1VBO->getBuffer());
renderer.VertexAttribPointer2fv("aBoneWeights1", 0, NULL);
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(anim.bindPoseMutable.data.PositionData.size()));
it->second.gpuSkinningShaderData.RenderVBO(renderer);
renderer.PopMatrix();
renderer.PopProjectionMatrix();

View File

@ -70,8 +70,8 @@ public:
bool canAttack = false;
Character* attackTarget = nullptr;
bool isPlayer = false;
bool useGpuSkinning = true;
//bool useGpuSkinning = false;
//bool useGpuSkinning = true;
bool useGpuSkinning = false;
float interactionRadius = 0.0f;
@ -97,6 +97,8 @@ private:
// if the requested state has no loaded animation.
AnimationState resolveActiveState() const;
bool prepareGpuSkinning();
// GPU skinning: draw using shader-based skinning
void drawGpuSkinning(Renderer& renderer);
// Shadow: draw into depth map (no texture, no projection push)