0
\$\begingroup\$

I want to store my animations on the GPU via buffers (instead of using a single constant buffer which contains the current skeleton frame only), so the vertex shaders of the skeletal meshes can read it and transform the vertices accordingly. However, I am not entirely sure how exactly to store the keyframes.

My skeleton is currently stored as a single array that consists of local matrices. The same goes for the keyframes: An animation is simply a list of matrices. The count of the matrices is the count of joints. How am I supposed to store that in a 2D texture?

\$\endgroup\$
1

1 Answer 1

1
\$\begingroup\$

You can store a matrix in 4 float4 textures. However, since these are transform matrices, you don't need the bottom row, so you can suffice with 3x float4 textures. (And reconstruct the 4th row in the shader, which is simply (0, 0, 0, 1))

Since you need one data set per bone, you could use a 2D texture where each row represents one bone. (But you'll need to make sure you sample in the center of each row to avoid interpolating data from other bones. Alternatively, you could use a Texture2DArray which won't have the chance of accidentally interpolating other bones.

You can optimize this further however. Do you actually need animated scale in your animations? If not, you can condense your data to something like a position, and a float3 for rotation that stores pitch/yaw/roll or similar. This means only 2 texture reads. Taking it a step further, it's possible to condense a quaternion into a single uint with minimal precision loss. That way, you could store the entire animation in a float4 texture, storing the rotation as a uint32 quaternion in the alpha channel, and manually unpacking.

The downside of the last approach however, is that you won't be able to linearly interpolate in the shader. You'll have to do 2 fetches, unpack the rotation, and manually interpolate. (However you could use a Gather instruction to speed this up)

Alternatively, you could consider not using a texture, and simply fetching from a buffer or structured buffer, which you can pack/index/unpack however you like. One benefit to this is you're not limited to simple linear interpolation. Animations are often authored as bezier curves with input and output tangents, for choosing between ease-in/ease-out, linear, constant, or other forms of interpolation. Storing the keyframes as position, intangent, and outtangent would allow you to do this, making your gpu animations as high quality as a CPU implementation.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for your answer! I'll probably do the latter approach; storing translations, rotations and scales in a buffer. One concern I have though is whether it is feasible to assemble all the matrices in the vertex shader? \$\endgroup\$
    – Raildex
    Feb 1, 2023 at 18:02
  • \$\begingroup\$ Yeah it's definitely feasible to assemble matrices in a vertex shader. If you're just reading 3x float4 textures, then construction a matrix is simply 3 mov4 instructions. (Which move a float4 into a register) Even if it looks like a lot of shader code at the end, the shader compiler will optimize away a lot of it anyway. (Eg if you manually set the bottom row to float4(0, 0, 0, 1) then the shader will optimize some of the matrix multiplications as the bottom row is constant) If you can, look at the compiled code/instruction count compared to a standard vertex shader. It should be close \$\endgroup\$ Feb 3, 2023 at 0:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .