实验题目来自2021年春季学期山东大学软件学院计算机动画基础课程
本人比较菜,代码有很多bug以及莫名其妙的地方,发在这记录一下写代码的艰辛😭,仅供参考思路哦!
现在代码已经找不到了,请不要找我要文件啦!(当然,欢迎指正)
- 使用glfw,glad库,C++编写,参考LearnOpenGL
实验题目
给出人们见面时碰击手肘打招呼的角色动画
可以是图形方式,也可以是图像方式
思路
建立树形结构存储两个人物,其中分别存储身体、大臂、小臂对应的变换矩阵。
首先使用路径动画的方法使两个要打招呼的人慢慢走近。
其次,使用FK-正向运动学的方法确定两人胳膊各部分的位置,使其相交,以造成碰击手肘打招呼的效果。需要使用插值计算变换的中间位置。
实现效果
步骤
-
定义人物的数据结构,使用类实现。(比较粗糙,意思一下。。。)
/* two people(manmask1,manmask2)knock the elbows one person devide into 3 parts -- body, upper arm, and lower arm use class man to describe */ class man { public: glm::mat4 trans_body; glm::mat4 trans_upperarm; glm::mat4 trans_lowerarm; man(); man(glm::mat4 tb, glm::mat4 tu, glm::mat4 tl) { trans_body = tb; trans_upperarm = tu; trans_lowerarm = tl; } };
-
初始化变换。
t11 = glm::translate(t11,glm::vec3(tran11, 0.0f, 0.0f)); t12 = glm::translate(t12, glm::vec3(tran12, 0.0f, 0.0f)); t12 = glm::rotate(t12, glm::radians(rota12), glm::vec3(0.0, 0.0, 1.0)); t13 = glm::translate(t13, glm::vec3(tran13, 0.0f, 0.0f)); t13 = glm::rotate(t13, glm::radians(rota13), glm::vec3(0.0, 0.0, 1.0)); t21 = glm::translate(t21, glm::vec3(tran21, 0.0f, 0.0f)); t22 = glm::translate(t22, glm::vec3(tran22, 0.0f, 0.0f)); t22 = glm::rotate(t22, glm::radians(rota22), glm::vec3(0.0, 0.0, 1.0)); t23 = glm::translate(t23, glm::vec3(tran23, 0.0f, 0.0f)); t23 = glm::rotate(t23, glm::radians(rota23), glm::vec3(0.0, 0.0, 1.0));
-
定义顶点数组以绘制人物以及其身体各部分,这里需要分开绘制,以便实现不同的变换。因为很难拟合身体各部分的边缘,因此这里需要使用png格式的图片,而png格式的图片因为存在透明部分,所以其顶点位置只需为正方形,因此绘制四个顶点就可以。
-
绑定vao和vbo,不同的身体部分需要绑定到不同的vao,以便在顶点着色器中实现不同的变换。
-
加载纹理,需要分为单独的人的身体、单独的大臂、单独的小臂。
-
在渲染循环中不断改变各部分的旋转角度,即变换矩阵,并将其传给着色器,以实现碰击手肘的动画效果。(瞎写的,有点乱,慎看,能用就行。。。)
if (i <= 66) {//change the people place tran11 += 0.0009; tran21 -= 0.0009; rota12 += 1.15 / 1.5; rota22 -= 2.3 / 1.5; if (rota13 < 95) rota13 += 2.4 / 1.5; if (rota23 > -70) rota23 -= 1.8 / 1.5; } else if (i>66 &&i <= 90) {//rotate while changing the people place rota12 += 1.15/1.5; rota22 -= 2.3/1.5; if(rota13 < 95) rota13 += 2.4/1.5; if(rota23 > -70) rota23 -= 1.8/1.5; } else if(i > 90){ i = 0; tran11 = -0.16; tran21 = 0.16; rota12 = -10; rota22 = 23; rota13 = rota23 = 0; } t11 = glm::mat4(1.0f); t12 = glm::mat4(1.0f); t13 = glm::mat4(1.0f); t21 = glm::mat4(1.0f); t22 = glm::mat4(1.0f); t23 = glm::mat4(1.0f); //upper arms tran12 = -0.01; tran22 = 0.015; tran22_2 = -0.01; //lower arms tran13 = 0.08; tran13_2 = -0.074; tran23 = -0.0066; tran23_2 = -0.068; if (rota13 > 90) { //perform the clap tran13 = 0.081; tran13_2 = -0.076; tran12 = -0.018; } if (rota23 < -68) { tran22 = 0.004; tran22_2 = -0.01; } t11 = glm::translate(t11, glm::vec3(tran11, 0.0f, 0.0f)); t12 = glm::rotate(t12, glm::radians(rota12), glm::vec3(0.0, 0.0, 1.0)); t12 = glm::translate(t12, glm::vec3(0.0f, tran12, 0.0f)); t13 = glm::translate(t13, glm::vec3(0.0f, tran13_2, 0.0f)); t13 = glm::translate(t13, glm::vec3(tran13, 0.0f, 0.0f)); t13 = glm::rotate(t13, glm::radians(rota13), glm::vec3(0.0, 0.0, 1.0)); t21 = glm::translate(t21, glm::vec3(tran21, 0.0f, 0.0f)); t22 = glm::translate(t22, glm::vec3(tran22, 0.0f, 0.0f)); t22 = glm::translate(t22, glm::vec3( 0.0f,tran22_2, 0.0f)); t22 = glm::rotate(t22, glm::radians(rota22), glm::vec3(0.0, 0.0, 1.0)); t23 = glm::translate(t23, glm::vec3(tran23, 0.0f, 0.0f)); t23 = glm::translate(t23, glm::vec3( 0.0f,tran23_2, 0.0f)); t23 = glm::rotate(t23, glm::radians(rota23), glm::vec3(0.0, 0.0, 1.0)); man1 = man(t11, t12, t13); man2 = man(t21, t22, t23);//build two men //background! // activate shader ourShader.use(); // create transformations glm::mat4 model1 = glm::mat4(1.0f); glm::mat4 view1 = glm::mat4(1.0f); // make sure to initialize matrix to identity matrix first glm::mat4 projection1 = glm::mat4(1.0f); projection1 = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); view1 = glm::translate(view1, glm::vec3(-0.3f, 0.0f, -3.0f)); view1 = glm::scale(view1, glm::vec3(2, 2, 2)); // pass transformation matrices to the shader ourShader.setMat4("projection1", projection1); / ourShader.setMat4("view1", view1); ourShader.setMat4("model1", model1); glBindVertexArray(VAO_bg); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); //body 1 ! // activate shader ourShader1.use(); // create transformations glm::mat4 model2 = glm::mat4(1.0f); glm::mat4 view2 = glm::mat4(1.0f); // make sure to initialize matrix to identity matrix first glm::mat4 projection2 = glm::mat4(1.0f); projection2 = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); ourShader1.setMat4("projection1", projection2); ourShader1.setMat4("model1", model2); //body view2 = view2 * man1.trans_body; ourShader1.setMat4("view1", view2); glBindVertexArray(VAO_pp11); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); //upper arm 1 ! // activate shader ourShader2.use(); glBindVertexArray(VAO_pp12); ourShader2.setMat4("projection1", projection2); ourShader2.setMat4("model1", model2); //upperarm view2 = view2 * man1.trans_upperarm; ourShader2.setMat4("view1", view2); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); //lower arm 1 ! // activate shader ourShader3.use(); glBindVertexArray(VAO_pp13); ourShader3.setMat4("projection1", projection2); ourShader3.setMat4("model1", model2); //lowerarm view2 = view2 * man1.trans_lowerarm; ourShader3.setMat4("view1", view2); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
结论分析与体会
-
角色动画的基本思路并不复杂,主要有两个比较关键的部分,一个是树形数据结构的构建,另一个是使用IK或FK的运动学方法求解位置变换信息。
树形结构的构建:
弧对应关节,节点对应链杆。
节点,包含:
1)关节链对象数据
2)该对象数据的变换信息,使其能够绕自身原点旋转(非必须)
弧,包含:
1)用来描述关节链之间的相对位置信息
2)关节链实际运动的变换信息
对于本实验,因为人物身体的其他部分没有发生移动,因此可以对该数据结构进行适当的简化。
使用IK或FK的运动学方法求解位置变换信息:- FK-正向运动学的计算方法思路比较简单,直接由程序员从根节点开始逐个设置每一个部件的变换信息,需要有一定的经验,同时也可能需要多次进行调试才能达到满意的效果。
- IK-逆向运动学的方法比较直观,只需要由程序员定义末端影响器的位置,电脑即可自动算出其他部件的位置与变换。但是运算比较复杂,可能需要牛顿迭代法等算法才能求解。
- 本次实验因为需要移动的胳膊部分部件较少,因此使用正向运动学的方法直接求解。
(其实是逆向不太会啦)
-
在使用正向运动学求解各部件位置时,要格外注意变换处于的坐标系。可以理解为对之前部件的每一次变换,都相当于也作用到了坐标系上。
一点不重要的
- 再次感谢某位同学一直以来的帮助!