Welcome to pay attention to the public number: Sumsmile, graphics, mobile development ~~
The contents, codes and illustrations of this article mainly refer to Learnopengl-Skeletal -Animation. The original code has a bug and cannot run normally. The repaired code is linked at the end of this article
Bone animation Effect
Front knowledge
- Opengl basic knowledge and Mesh concept
- IO /03 Model Loading/01 Assimp/
If you are not familiar with the above knowledge, understanding will be relatively jumping
What is bone animation
In 3D model animation, a very important one is “skeleton animation”, which defines a series of skeletons with “hierarchical relationship” in the model, and drives each module of the model to make different movements through the movement of the bones.
As in the figure above, the thigh is at a higher level than the calf, and the leg moves with it.
Principle of bone animation
Perceptual understanding of bone animation
Imagine the skeleton of a robot in Figure 1. The robot’s movement is relatively stiff, lifting the forearm, only the forearm movement, will not affect other parts.
In humans and animals, the same area is often affected by multiple bones, such as the muscles of the shoulder affected by the upper arm, back, neck, etc. In a practical project, there is an upper limit to the number of sources of influence that a single point can receive, for example, in the following demo we set it to 4, and the weight of each influence source is also different.
The interpolation
Bone animation only defines discrete key frames, and the smoothing in the middle needs to be interpolated.
Commonly used linear interpolation algorithm: x = a * (1-t) + b * t A and B respectively represent the starting and ending coordinates of a point. With the change of time t, x represents the coordinate value at time T.
OK, the above is the most basic knowledge of bone animation.
Code implementation
Engineering structure
The Demo project was developed using CLion. The main consideration is CLion cross-platform, easy to migrate to other platforms. CLion’s interface is similar to that of mainstream ides and is easy to use.
Implementation is rough, call logic, render loop is directly implemented in main. CPP, without too much encapsulation.
- Main.cpp handles the scheduling logic for rendering
- Render directory contains “file read/write”, “skeleton data”, “animation data”, “Mesh, Model package”, “Shader package”
- The shader directory places shaders used in rendering (if you don’t know shaders, start with OpengL)
- Third -part third- party tool library, using 5 third- party libraries
- Assimp is a simple 3D model loading tool
- Glad OpengL compatible tool, compatible with different platform OpengL environment differences, simplify GL environment configuration
- GLFW window management class, OpengL and hardware of the middle layer, and EGL, SDL similar
- GLM common mathematical library, dealing with vectors, matrices and other operations
- STB Image image loading tool, used to load texture resources
All the tripartite library, I in the demo project in the way of source integrated link, convenient debugging and learning. These are classic open source libraries for graphics.
Take a look at the cmake file to get an idea of how the project is organized. The comments are very detailed.
# set cmake minimum to 3.20, it doesn't matter, as long as it works. Cmake_minimum_required (VERSION 3.20) project(summerGL) # set c++ standard to c++ 14 set CMAKE_CXX_STANDARD 14 GLFW add_subdirectory(third-part/glfw-3.3.6) # GLFW directories (/usr/local/lib Assimp add_subdirectory(third-part/ ASsimp-5.0.1) # Link to assimp libraries(GLFW) GLM add_subdirectory(third-part/glm-0.9.9.8) # link GLM libraries(GLM libraries) # add the source file set(SRCS SRC /glad. C) # build the main project Executable (${PROJECT_NAME} ${SRCS} main.cpp) # Configure_file (configuration/ root_directory.h.I. Configuration /root_directory.h) # set header include directory Include_directories (${CMAKE_BINARY_DIR}/configuration) set(THIRD_PARTY_INC render/include The third - part/GLFW - 3.3.6 / include/third part/glad/include/third - part/assimp - 5.0.1 / include third - part/GLM - 0.9.9.8 Third -part/ stB-master) message(STATUS ${THIRD_PARTY_INC}) # target_include_directories(${PROJECT_NAME} PUBLIC ${THIRD_PARTY_INC})Copy the code
Here comes the key point, the students cheer up!!
The model is loaded
I’ll take a quick look at the implementation of the code, and I’ll talk more about why
// load models // Model encapsulates 3D Model file loading and data Model ourModel(FileSystem::getPath("resources/objects/vampire/dancing_vampire.dae")); // Animation encapsulates the data defined by the Animation in the 3D model. Also includes the weight of bone Animation danceAnimation (FileSystem: : getPath (" resources/objects/deliberate/dancing_vampire dae will work "), & ourModel); Animator (&danceAnimation) Animator (&danceAnimation);Copy the code
Three classes are used
- Model encapsulates the loading and data of 3D Model files
- Animation encapsulates the data defined by the Animation in the 3D model, including the weight of the skeleton
- Animation actuator, the main work is to do interpolation, weight calculation and so on
In the while loop of the rendering thread, the position of the skeleton is continuously updated, and the coordinates of each vertex and triangle of the 3D model are constantly updated. After that, the 3D model is constantly redrawn to form a continuous animation.
Related codes:
while (! glfwWindowShouldClose(window)) { ... Float currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; animator.UpdateAnimation(deltaTime); . / / 2. Update the shader variables, the skeleton of the weighting in the shader, auto control points by shader migration transforms = animator. GetFinalBoneMatrices (); for (int i = 0; i < transforms.size(); ++i) ourShader.setMat4("finalBonesMatrices[" + std::to_string(i) + "]", transforms[i]); // 3. Draw model ourModel.draw (ourShader); }Copy the code
Demo project, there are keyboard, mouse input processing, used to control the camera Angle of change, such as rotation, zoom, etc.
It may be abstract, so I suggest you download the code, run around and understand the logic of the code.
Model and animation data parsing
In demo project, 3D models in DAE format are used. In fact, there are hundreds of TYPES of 3D models, of which about ten are the most common. The DAE format is characterized by the ability to contain a complete scene (multiple 3D models, lighting, camera, animation, etc.), that is, a DAE file can render a complete picture.
Dae Format introduction
Data loaded through ASSIMP contains aiScene(scenes), a term you’ll be familiar with if you have a background in game development.
AiScene contains a RootNode, RootNode, and a set of animated data, Animations[]
RootNode is an aiNode type, including name, 4*4 matrix Transformation, and a set of sub-nodes, as shown in the figure below:
AiScene also contains a Mesh array. A Mesh is a minimum rendering module that contains all the elements of the image, including vertices, bones and the weight of each bone on other vertices, texture weight, etc.
The effect of the skeleton on the vertexID of the vertex is searched by mapping
Finally, the Shader slice shader samples the Texture_diffuse texture without special handling
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D texture_diffuse1;
void main()
{
FragColor = texture(texture_diffuse1, TexCoords);
}
Copy the code
The logic is all in the vertex shader. The core logic is to find all the bones that affect the current point and calculate the sum of the bone influence points according to the weight. Note that the current upper limit for affected bones is 4, of course more is more realistic but will affect performance.
The other tangent vectors and the normal vectors are not used, so you don’t have to worry about them. If you want to add light, the normal vector norm will be involved.
#version 330 core
layout(location = 0) in vec3 pos;
layout(location = 1) in vec3 norm;
layout(location = 2) in vec2 tex;
layout(location = 3) in vec3 tangent;
layout(location = 4) in vec3 bitangent;
layout(location = 5) in ivec4 boneIds;
layout(location = 6) in vec4 weights;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
const int MAX_BONES = 100;
const int MAX_BONE_INFLUENCE = 4;
uniform mat4 finalBonesMatrices[MAX_BONES];
out vec2 TexCoords;
void main()
{
vec4 totalPosition = vec4(0.0f);
for(int i = 0 ; i < MAX_BONE_INFLUENCE ; i++)
{
if(boneIds[i] == -1)
continue;
if(boneIds[i] >=MAX_BONES)
{
totalPosition = vec4(pos,1.0f);
break;
}
vec4 localPosition = finalBonesMatrices[boneIds[i]] * vec4(pos,1.0f);
totalPosition += localPosition * weights[i];
vec3 localNormal = mat3(finalBonesMatrices[boneIds[i]]) * norm;
}
mat4 viewModel = view * model;
gl_Position = projection * viewModel * totalPosition;
TexCoords = tex;
}
Copy the code
The entire project file is too big and exceeds github’s limit, only the main file is uploaded github.com/summer-go/g…
Complete engineering and material documents in the cloud disk: link: pan.baidu.com/s/1DJ4B4vNj… Password: i2d7
Welcome to pay attention to the public number: Sumsmile, graphics, mobile development ~~