5
\$\begingroup\$

I'm trying to render a large number of identical rigged entities with independent animation.

Compared to standard skeletal animation, I can't compute the pose of each entities and send it to the GPU for each frame. This would demand too much work for the CPU and saturate the VRAM very quickly. Let's say I have 5000 entities with 20 bones, I would have to compute and send 5000 * 20 * (size of matrix/quaternion-translation/dual-quaternion) poses to the GPU, each frame.

I recently found this article/paper : https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch02.html Their idea is to load animations to the GPU in order to access them independently with each instance. Correct me if I'm wrong but I think this is equivalent to move the interpolation part to the GPU.

I pre-computed all the bone transformation in local space for each frame for each animation and packed it into texture buffers. It works as expected.

However, I experience strange results with my animated models... In fact the interpolation is necessarily wrong ! I am trying to interpolate between poses in local space, not in bone space like with standard CPU interpolation. There is no problem with translations, but with rotations. (the axis is shifted to the local space origin).

I imagine I could send the bone space transformations AND the bone hierarchy. But that would impose to parse the hierarchy for each vertex (in vertex shader), in order to re-compute the interpolated bone transformations.

One other idea I had is to upsample the animations in order to have smaller interpolation intervals, but again, it will use more VRAM.

To be honest I'm not sure I understood well the paper (especially in the interpolation part)...

Any suggestion ?


EDIT 1 : It's maybe unclear : by "local space" I mean entity space, so it is more like "global space"...


EDIT 2 : To provide more details about my implementation :

In the CPU (c++) pre-process part, I pre-compute all the bone transformations in local space for each frame (natively, I get bone-space transformations using Assimp library so I have to do a bit of work here).

Then, I send this information as an array to the GPU (array of numFrames * numBones * numAnims dual quaternions). In the vertex shader, I get information about the current instance using gl_instanceID. I get world position, current animation and time in current animation.

Then I compute the interpolated pose of the bones influencing the current vertex. I do that by finding the bounding frames using the instance's time. I interpolate my pairs of transformations (pairs of dual quaternions) for each concerned bone.

(I know there is a mistake because the interpolation must be performed in bone space).

Once they are interpolated, I blend linearly the transformations into one using the bone weights attached to my vertex (and I normalize my final dual quaternion).

It worked when i was doing interpolation in bone space in CPU, I'm just trying to move the interpolation part on the GPU.

\$\endgroup\$
  • \$\begingroup\$ I think you will increase your chances of getting an answer if you provide more details on your implementation. Specifically your shader code and what steps you perform to bake keyframes. \$\endgroup\$ – Ocelot Jul 24 at 11:48
  • 2
    \$\begingroup\$ This sounds like a good candidate for a compute shader, rather than the vertex shader. Each thread could run through the transforms in hierarchy order and spit out final net transformations to a temporary buffer. \$\endgroup\$ – DMGregory Jul 24 at 11:51
  • \$\begingroup\$ @Ocelot I wrote a more detailed description of my current skinning system, hope it will be more clear... \$\endgroup\$ – Kiord Jul 24 at 14:47
  • 1
    \$\begingroup\$ After reading the paper, it sounds to me like your problem might be the interpolation. The paper doesn't refer to interpolating between animation keyframes, it seems to refer only to reading animation frames. ie. it sounds like they've encoded the object space matrix for each bone for each displayable frame, so they don't need to do any hierarchy traversal at runtime. They just snap to the closest sample and render that pose directly — effectively the extreme end of your "upsample the animations" proposal. \$\endgroup\$ – DMGregory Jul 24 at 15:11
  • 1
    \$\begingroup\$ Note that 5000 entities * 20 bones * sizeof(mat4) only comes out to about 6 megabytes of data. Assuming you send it all in one big block, it shouldn't be a problem either for vram storage or for the bandwidth of how much you can send to the card each frame. \$\endgroup\$ – Trevor Powell Jul 28 at 1:29
2
\$\begingroup\$

The solution I chose was to pre-compute all the frames rather than the keyframes. In fact, I plan to have less than 10 seconds for all the animations.

To store a 10 seconds animation at 50 Hz, I need 10 seconds * 50 Hz * ~50 bones * 2 quaternion * 4 floats * 4 bytes = 800 kb of VRAM, which is acceptable in my case.

\$\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.