变形动画实现方式主要有两种,一种是通过美术人员利用Max工具自己调出动画,这种调出的动画太僵硬而且不能根据用户的要求随意变形,只能按照预先调好的动画变形,这种做法可以排除了。第二种做法是通过程序实现自由的动画变换,在这里给大家介绍利用OpenGL实现的变形动画,目的是把动画的变换原理给大家介绍清楚。
在介绍3D变形的之前,大家首先要掌握的是3D的固定流水线, 现在做游戏的开发者对于什么是3D固定流水线一无所知,市面上使用的大部分引擎都封装的非常好,开发者也沦为了只会使用工具,对于一些常识一概不知。在游戏公司做的主要事情就是写写逻辑,调用调用接口,如果需求超出接口的范畴基本上就是一筹莫展,或者说引擎的功能满足不了。面对这种情况,作为开发者必须把3D的基本知识掌握了,这样遇到3D的任何问题都不会感觉难下手,至少知道解决问题的思路。如果大家对于3D固定流水线不清楚可以自己去查阅,我也写过关于3D固定流水线的博客,在这里就不做介绍了,简单的一句话表示3D固定流水线就是将3D模型在2D屏幕上显示的过程,这个过程就是对于矩阵的换算,对应3D固定流水线是3D可编程流水线,不清楚的大家自行查阅一下,这个都必须要掌握的。
接下来介绍实现变形的基本思路:
第一步,准备使用的模型,模型格式很多种,这个可以使用网上的,也可以自己编写插件实现自定义的模型。本项目对应的模型是后缀名为m的自定义的模型格式,模型格式内容如下所示:

上图显示的只是一部分,为了能让大家看清楚,并没有对模型进行加密,为了计算方便,这里只给出了Vertex顶点和Face面的数据。
第二步,定义模型的数据结构体
- #ifndef halfedge_h
- #define halfedge_h
-
- #include <cstdlib>
-
- using namespace std;
-
-
- struct HE_edge;
- struct HE_vert;
- struct HE_face;
- struct HE_line;
-
- struct HE_edge
- {
- int edgeID;
- HE_vert* vertS;
- HE_vert* vertE;
- HE_edge* pair;
- HE_face* face;
- HE_edge* next;
- };
-
- struct HE_vert
- {
- int vertID;
- float x, y, z;
- float nx, ny, nz;
- bool selected;
- HE_edge* edge;
- };
-
- struct HE_face
- {
- int faceID;
- int v1, v2, v3;
- float nx, ny, nz;
- HE_edge* edge;
- };
-
- struct HE_line
- {
- unsigned int startVert, endVert;
- bool operator < (const HE_line &other) const
- {
- if(startVert < other.startVert)
- return true;
- else if(startVert == other.startVert)
- return endVert < other.endVert;
- else
- return false;
- }
- HE_line(){};
- HE_line(const HE_line &vert) {
- startVert = vert.startVert;
- endVert = vert.endVert;
- }
- HE_line(unsigned int v1, unsigned int v2) {
- startVert = v1;
- endVert = v2;
- }
- };
-
- #endif
第三步,需要加载模型,并计算顶点和面的法线以及设置包围盒,头文件的内容给大家展示如下:copy
- #ifndef mesh_h
- #define mesh_h
-
- #include <cstdlib>
- #include <GL\glui.h>
-
- #include <map>
- #include <Windows.h>
- #include <WTypes.h>
-
- using namespace std; copy
-
- void loadMesh(std::string path);
- void findMinMax(float tempX, float tempY, float tempZ);
- void calcFaceNorm(void);
- void calcVertNorm(void);
- void setToOrigin(void);
- void scaleToOrigin(void);
-
-
- double editLineWidth(int width);
- void drawMeshWire(void);
- void drawMeshSolid(void);
- void drawMeshPoint(void);
- void drawBbox(void);
- void drawGrid(void);
- void drawAxes(void);
-
-
- void createFileDialog(void);
- int createMsgbox(int msgboxID);
- enum
- {
- ERR_no_mesh = 0,
- ERR_no_pt
- };
-
- #endif
具体实现会在博客的末尾给大家展示。
第四步,开始对模型的变形处理,在这里并没有使用什么高深的算法,其实就是对它们的顶点进行矩阵变换。首先进行的是世界变换,投影变换,最后是视口变换。
变形的函数如下所示: copy
- void deformMesh()
- {
- copyVertices = new Vertex[numOfVert]();
- for(int count = 0; count < numOfVert; count ) {
-
- copyVertices[count].x = v0[count].x;
- copyVertices[count].y = v0[count].y;
- copyVertices[count].z = v0[count].z;
-
-
- if(vertices[count].selected) {
- findSelectedMinMax(vertices[count].x, vertices[count].y, vertices[count].z);
- }
- }
- for(int count = 0; count < numOfVert; count ) {
-
- if(vertices[count].selected) {
-
- glColor3f(0.5, 0.5, 1);
- glPushMatrix();
- glTranslatef(copyVertices[count].x, copyVertices[count].y, copyVertices[count].z);
- glutSolidSphere(0.1, 20, 20);
- glPopMatrix();
- glColor3f(1, 1, 1);
-
- vertices[count].x = 0;
- vertices[count].y = 0;
-
-
-
- Vertex tempResult = trivariateBernstein(copyVertices[count]);
- vertices[count].x = tempResult.x;
- vertices[count].y = tempResult.y;
- vertices[count].z = tempResult.z;
-
-
- }
- }
- }
当你看到代码后,第一印象就是太简单了,确实是这样,变形其实就是将需要改变的顶点进行一系列矩阵变换,在这里使用了OpenGL的库函数接口 copy- glPushMatrix();
- glTranslatef(copyVertices[count].x, copyVertices[count].y, copyVertices[count].z);
- glutSolidSphere(0.1, 20, 20);
- glPopMatrix();
进行顶点的移动,同时调用了函数进行顶点的变换以及权值计算,代码如下所示: copy
- Vertex trivariateBernstein(Vertex vert)
- {
- Vertex stuVert;
- stuVert.x = 0;
- stuVert.y = 0;
- stuVert.z = 0;
- stuVert = convertToSTU(vert);
-
-
-
- double weight = 0.0;
- Vertex convertedVert;
- convertedVert.x = 0;
- convertedVert.y = 0;
- convertedVert.z = 0;
-
-
- for(int i = 0; i <= l; i ) {
- for(int j = 0; j <= m; j ) {
- for(int k = 0; k <= n; k ) {
- weight = 0;
- weight = bernsteinPoly(n, k, stuVert.z) * bernsteinPoly(m, j, stuVert.y) * bernsteinPoly(l, i, stuVert.x);
- convertedVert.x = weight * lattice[i][j][k].x;
- convertedVert.y = weight * lattice[i][j][k].y;
- convertedVert.z = weight * lattice[i][j][k].z;
- }
- }
- }
- return convertedVert;
- }
没有使用任何算法,实现的就是模型的矩阵变换。最后一步就是实现,首先要做的事情是选择需要变形的顶点或者区域,使用了函数如下所示:
- void moveSelectedCP(int endX, int endY)
- {
-
-
- float moveX = 0;
- float moveY = 0;
- moveX = (endX-startX);
- moveY = (endY-startY);
-
-
-
- startX = endX;
- startY = endY;
-
- if(!(moveX == 0) || !(moveY == 0)) {
-
-
- lattice2d[sel_i][sel_j][sel_k].x = moveX;
- lattice2d[sel_i][sel_j][sel_k].y = moveY;
-
-
- <strong>gluUnProject(lattice2d[sel_i][sel_j][sel_k].x, lattice2d[sel_i][sel_j][sel_k].y, lattice2d[sel_i][sel_j][sel_k].z,
- modelView, projection, viewport,
- &lattice[sel_i][sel_j][sel_k].x, &lattice[sel_i][sel_j][sel_k].y, &lattice[sel_i][sel_j][sel_k].z);</strong>
- cout << "Move CP: [" << sel_i << "][" << sel_j << "][" << sel_k << "]\n";
-
-
- }
- }
在再main函数中调用封装的函数即可。其实做起来还是比较简单的,就是对变形的部分做了矩阵的运算。大家关注上面加粗的部分。代码下载地址:链接:http://pan.baidu.com/s/1i5klXXV 密码:m1f5