0
\$\begingroup\$

Trying to implement animation on my engine.
I'm at the first stage, rendering default pose of skinned meshes.
Working as expected but very slow.

With the below calculation, the shader takes 6ms to run.

struct MeshUniform {
    mat4 transform;
    mat4 normalMatrix;
    vec4 baseColorFactor;
    vec4 roughnessMetallicNormal;
    vec4 hasColorMetallicNormalTexture;
    mat4[30] jointMatrices;
}; 

layout (std430, binding = 4) buffer meshUniformSSBO { MeshUniform[] meshUniforms; };

layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 uv;
layout (location = 3) in vec4 tangent;
layout (location = 4) in uvec4 joints;
layout (location = 5) in vec4 weights;
layout (location = 6) in int drawId;

void main() {
   MeshUniform meshUniform = meshUniforms[drawId];
   mat4 model = meshUniform.transform;

   mat4 skinMat =
     meshUniform.jointMatrices[joints[0]] * weights[0] +
     meshUniform.jointMatrices[joints[1]] * weights[1] +
     meshUniform.jointMatrices[joints[2]] * weights[2] +
     meshUniform.jointMatrices[joints[3]] * weights[3];

   vec4 positionVec4 = skinMat * model * vec4(position, 1.0);
   
   ...
}

If I remove skinMat calculation and multiplication, same shader takes 1ms.

// mat4 skinMat =
//  meshUniform.jointMatrices[joints[0]] * weights[0] +
//  meshUniform.jointMatrices[joints[1]] * weights[1] +
//  meshUniform.jointMatrices[joints[2]] * weights[2] +
//  meshUniform.jointMatrices[joints[3]] * weights[3];

// vec4 positionVec4 = skinMat * model * vec4(position, 1.0);

vec4 positionVec4 = model * vec4(position, 1.0);
  • Scene has 87706 vertices, shown in Blender statistics.
  • I'm using glMultiDrawElementsIndirect with single VAO.
  • Joint matrices for non-skinned meshes are identity matrix.
  • MeshUniform is persistent, coherent ssbo map. Only updated when needed.
  • I'm using the same calculation on shadowmaps, so it takes another 6ms.
  • Gpu is 1080 Ti.

I tried adding "jointCount" to MeshUniform struct, and doing the joint calculation only if jointCount > 0. But it still took 6 ms to calculate.

Is this to be expected and what can I do to improve?


Edit with DMGregory's suggestions:

I tried,

  • Multiplying joint matrices by position vector, then summing the results.
  • Pre-multiplying model with joint matrices on cpu.

It looks like this now;

vec4 positionVec4 = vec4(position, 1.0);

vec4 sum =
  meshUniform.jointMatrices[joints[0]] * weights[0] * positionVec4 +
  meshUniform.jointMatrices[joints[1]] * weights[1] * positionVec4 +
  meshUniform.jointMatrices[joints[2]] * weights[2] * positionVec4 +
  meshUniform.jointMatrices[joints[3]] * weights[3] * positionVec4;

positionVec4 = sum;

It's still taking 5-6ms to run.


Someone in lwjgl forums posted a question similar to mine in 2012.
http://forum.lwjgl.org/index.php?topic=4519.0

In his last message he said;

using a constant as the array index while accessing boneMatrixes brings performance up

Sure enough if I exclude joints array lookup from above code like this;

vec4 positionVec4 = vec4(position, 1.0);

vec4 sum =
  meshUniform.jointMatrices[0] * weights[0] * positionVec4 +
  meshUniform.jointMatrices[1] * weights[1] * positionVec4 +
  meshUniform.jointMatrices[2] * weights[2] * positionVec4 +
  meshUniform.jointMatrices[3] * weights[3] * positionVec4;

positionVec4 = sum;

it renders in 1ms. But of course resulting image is not correct.

Maybe it will give some ideas to more experienced people on OpenGL.

\$\endgroup\$
2
  • \$\begingroup\$ Did you try multiplying the four joint matrices by your position vector, then summing the resulting vectors instead? It's a similar cost in terms of arithmetic, but your temporaries are 4 floats instead of 16, which can make more efficient use of the hardware. Also, the order of your skin matrices and the model matrix looks a bit strange - have you considered pre-multiplying the model matrix into the joint matrix uniforms you pass to the shader, so you do those multiplications once per bone instead of once per vertex? \$\endgroup\$
    – DMGregory
    Aug 13 at 11:55
  • \$\begingroup\$ Thanks for the suggestions, I updated the question. \$\endgroup\$ Aug 13 at 13:52
2
\$\begingroup\$

Thanks to Khronos forum user mhagain, he suggested to try different storage options.

I tried an SSBO that only holds the jointMatrices data and it worked!

layout (location = 4) in ivec4 joints;
layout (location = 5) in vec4 weights;
layout (location = 6) in int drawId;

struct JM {
    mat4[30] jointMatrices;
}; layout (std430, binding = 7) buffer jmSSBO { JM[] jm; };

void main() {
  ... 
    vec4 positionVec4 = vec4(position, 1.0);
    vec4 sum =
      jm[drawId].jointMatrices[joints.x] * positionVec4 * weights.x +
      jm[drawId].jointMatrices[joints.y] * positionVec4 * weights.y +
      jm[drawId].jointMatrices[joints.z] * positionVec4 * weights.z +
      jm[drawId].jointMatrices[joints.w] * positionVec4 * weights.w;

  ...
}

With this, render time is under 1ms.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.