
Engine: Skinning & Animation
Skinning
The process of applying skinned characteristics to animated skeletal hierarchies at runtime presents among the most challenging scenarios not only in game engine programming, but in real-time development as a whole. There exist a multitude of factors required to ensure the proper exportation and processing of large amounts of complex animation data. This data must be readily accessible within the engine and remain consistent throughout the running of the application. If one variable in the skinning process is missing, incomplete, or incorrect, the entire animation system will simply not function.
​
The following demo highlights the skinning, animation and blending functionality developed within the custom Azul Engine. This section will give a general overview pertaining to the importation and processing of skinning data retrieved from the custom converter tool (for more information on the exportation of skinning/animation data, see Engine: Model Viewer & Converter).
GPU Architecture
There is an enormous amount of data required to properly implement real-time animation and skinning functionality. This data includes: the animation clip keyframes and bone positions at each frame, the inverse bind pose of the skeleton, the indices and weights acting upon each bone, the parent/child hierarchy table (used within compute shader), and finally the vertices, normals, and uvs of the model.
​
Upon engine initialization, this animation data is sent directly to the GPU by means of Shader Storage Buffer Objects, also known as SSBOs. The animation data resonant in these particular SSBOs may be utilized by multiple animated objects sharing the same skeleton. This is made possible because the nature of the data is unchanging, thereby denoting “read-only” characteristics. The following code sample shows part of a wrapper class created for the SSBO:

Compute Shaders
By sending this data to the GPU, instead of keeping it in memory, the engine can utilize the CPU to handle more appropriate conditional tasks, to which it is better suited. The objective is to send small amounts of data from the CPU to the GPU, which will act upon the resonant, unchanging, animation data. Furthermore, this also permits the use of compute shaders to calculate world positions for each of the bones at each frame of an animation. At present, three separate shaders are utilized to perform the appropriate calculations required to implement skinning: two compute shaders, and one vertex shader. The power of these shader programs is made evident when dispatching multiple workloads to execute concurrently. This is achieved due to the inherent nature of GPU architecture, and the vast amount of cores available when compared to a typical CPU. The following code sample shows a portion of just one compute shader developed within the engine:

This compute shader is used to interpolate the locations of each skeletal bone between two specified keyframes. Each bone in the hierarchy contains a translation vector, rotation quaternion, and scale vector denoting its local position at a given time. Therefore, a position for each bone in the hierarchy must be determined every single frame. This compute shader utilizes resonant data on the GPU to optimally calculate these positions for every animated object concurrently.
​
The animations are controlled by Playback objects present within each animated object. These Playback objects utilize a State Design Pattern when handling the CPU-side logic pertaining to animation. This logic includes the timing, speed, and switching of animations. The Playback object is also responsible for running the compute shader, and handling the blending between animations (see Engine: Compression & Blending for more information):

Animation Rendering
The above demo focuses on demonstrating the lighting functionality of the skinned vertex shader being modified in real-time. Once both compute shaders have run to completion, and the local positions of each bone have been calculated, the vertex shader is executed and the object rendered to the screen. This is first accomplished by binding the matrix array of local bone coordinates, calculated by the compute shaders, to the position specified within the vertex shader as such:

The entirety of this skinning data is eventually factored into the vertices’ skin matrix, which contains the final local positional information for the skinned vertex. This includes the bone weights and indices, which can be seen in both code samples. As previously iterated, this data is unchanging and resonant on the GPU, allowing for repeated use. Lighting and positional uniforms are also factored into the final equation to give the vertex its final position in world space, where it is visually displayed on screen:
