OpenGL实现3D自由变形

发表于2017-09-20
评论0 1.6k浏览

变形动画实现方式主要有两种,一种是通过美术人员利用Max工具自己调出动画,这种调出的动画太僵硬而且不能根据用户的要求随意变形,只能按照预先调好的动画变形,这种做法可以排除了。第二种做法是通过程序实现自由的动画变换,在这里给大家介绍利用OpenGL实现的变形动画,目的是把动画的变换原理给大家介绍清楚。


在介绍3D变形的之前,大家首先要掌握的是3D的固定流水线, 现在做游戏的开发者对于什么是3D固定流水线一无所知,市面上使用的大部分引擎都封装的非常好,开发者也沦为了只会使用工具,对于一些常识一概不知。在游戏公司做的主要事情就是写写逻辑,调用调用接口,如果需求超出接口的范畴基本上就是一筹莫展,或者说引擎的功能满足不了。面对这种情况,作为开发者必须把3D的基本知识掌握了,这样遇到3D的任何问题都不会感觉难下手,至少知道解决问题的思路。如果大家对于3D固定流水线不清楚可以自己去查阅,我也写过关于3D固定流水线的博客,在这里就不做介绍了,简单的一句话表示3D固定流水线就是将3D模型在2D屏幕上显示的过程,这个过程就是对于矩阵的换算,对应3D固定流水线是3D可编程流水线,不清楚的大家自行查阅一下,这个都必须要掌握的。

接下来介绍实现变形的基本思路:

       第一步,准备使用的模型,模型格式很多种,这个可以使用网上的,也可以自己编写插件实现自定义的模型。本项目对应的模型是后缀名为m的自定义的模型格式,模型格式内容如下所示:


上图显示的只是一部分,为了能让大家看清楚,并没有对模型进行加密,为了计算方便,这里只给出了Vertex顶点和Face面的数据。


第二步,定义模型的数据结构体

  1. #ifndef halfedge_h  
  2. #define halfedge_h  
  3.   
  4. #include <cstdlib>  
  5.   
  6. using namespace std;  
  7.   
  8. // Halfedge structures  
  9. struct HE_edge;  
  10. struct HE_vert;  
  11. struct HE_face;  
  12. struct HE_line;  
  13.   
  14. struct HE_edge  
  15. {  
  16.     int edgeID;  
  17.     HE_vert* vertS; // Vertex at the start of halfedge  
  18.     HE_vert* vertE; // Vertex at the end of halfedge  
  19.     HE_edge* pair;  // Oppositely oriented halfedge  
  20.     HE_face* face;  // Incident face  
  21.     HE_edge* next;  // Next halfedge around the face  
  22. };  
  23.   
  24. struct HE_vert  
  25. {  
  26.     int vertID;         // Vertex ID  
  27.     float x, y, z;      // Vertex coordinates  
  28.     float nx, ny, nz;   // Vertex normals  
  29.     bool selected;      // Selected for FFD  
  30.     HE_edge* edge;      // Halfedge emanating from vertex  
  31. };  
  32.   
  33. struct HE_face  
  34. {  
  35.     int faceID;         // Face ID  
  36.     int v1, v2, v3;     // Vertex IDs  
  37.     float nx, ny, nz;   // Face normals  
  38.     HE_edge* edge;      // Halfedge bordering the face  
  39. };  
  40.   
  41. struct HE_line  
  42. {  
  43.     unsigned int startVert, endVert;  
  44.     bool operator < (const HE_line &other) const  
  45.     {  
  46.         if(startVert < other.startVert)  
  47.             return true;  
  48.         else if(startVert == other.startVert)  
  49.             return endVert < other.endVert;  
  50.         else  
  51.             return false;  
  52.     }  
  53.     HE_line(){};  
  54.     HE_line(const HE_line &vert) {  
  55.         startVert = vert.startVert;  
  56.         endVert = vert.endVert;  
  57.     }  
  58.     HE_line(unsigned int v1, unsigned int v2) {  
  59.         startVert = v1;  
  60.         endVert = v2;  
  61.     }  
  62. };  
  63.   
  64. #endif  

       第三步,需要加载模型,并计算顶点和面的法线以及设置包围盒,头文件的内容给大家展示如下:copy

  1. #ifndef mesh_h  
  2. #define mesh_h  
  3.   
  4. #include <cstdlib>  
  5. #include <GL\glui.h>  
  6. //#include <GL\freeglut.h>  
  7. #include <map>  
  8. #include <Windows.h>  
  9. #include <WTypes.h>  
  10.   
  11. using namespace std;   copy
  1. // Mesh functions  
  2. void loadMesh(std::string path);  
  3. void findMinMax(float tempX, float tempY, float tempZ);  
  4. void calcFaceNorm(void);  
  5. void calcVertNorm(void);  
  6. void setToOrigin(void);  
  7. void scaleToOrigin(void);  
  8.   
  9. // Draw functions  
  10. double editLineWidth(int width);  
  11. void drawMeshWire(void);  
  12. void drawMeshSolid(void);  
  13. void drawMeshPoint(void);  
  14. void drawBbox(void);  
  15. void drawGrid(void);  
  16. void drawAxes(void);  
  17.   
  18. // Dialog functions  
  19. void createFileDialog(void);  
  20. int createMsgbox(int msgboxID);  
  21. enum  
  22. {  
  23.     ERR_no_mesh = 0,  
  24.     ERR_no_pt  
  25. };  
  26.   
  27. #endif  
具体实现会在博客的末尾给大家展示。

第四步,开始对模型的变形处理,在这里并没有使用什么高深的算法,其实就是对它们的顶点进行矩阵变换。首先进行的是世界变换,投影变换,最后是视口变换。

变形的函数如下所示: copy

  1. void deformMesh()  
  2. {  
  3.     copyVertices = new Vertex[numOfVert]();  
  4.     for(int count = 0; count < numOfVert; count ) {  
  5.         /* Create copy of vertex position */  
  6.         copyVertices[count].x = v0[count].x;  
  7.         copyVertices[count].y = v0[count].y;  
  8.         copyVertices[count].z = v0[count].z;  
  9.   
  10.         /* Recalculate new minimum/maximum vertices */  
  11.         if(vertices[count].selected) {  
  12.             findSelectedMinMax(vertices[count].x, vertices[count].y, vertices[count].z);  
  13.         }  
  14.     }  
  15.     for(int count = 0; count < numOfVert; count ) {  
  16.         /* Only calculate new position of vertex if selected */  
  17.         if(vertices[count].selected) {  
  18.             /* Highlights selected vertex */  
  19.             glColor3f(0.5, 0.5, 1);  
  20.             glPushMatrix();  
  21.                 glTranslatef(copyVertices[count].x, copyVertices[count].y, copyVertices[count].z);  
  22.                 glutSolidSphere(0.1, 20, 20);  
  23.             glPopMatrix();  
  24.             glColor3f(1, 1, 1);  
  25.   
  26.             vertices[count].x = 0;  
  27.             vertices[count].y = 0;  
  28.             //vertices[count].z = 0;  
  29.   
  30.             /* Calculate the FFD position and update vertex position */  
  31.             Vertex tempResult = trivariateBernstein(copyVertices[count]);  
  32.             vertices[count].x = tempResult.x;  
  33.             vertices[count].y = tempResult.y;  
  34.             vertices[count].z = tempResult.z;  
  35.             //cout << "Vertex (old): " << vertices[count].x << ", " << vertices[count].y << ", " << vertices[count].z << "\n";  
  36.             //cout << "Vertex (new): " << vertices[count].x << ", " << vertices[count].y << ", " << vertices[count].z << "\n";  
  37.         }  
  38.     }  
  39. }  
当你看到代码后,第一印象就是太简单了,确实是这样,变形其实就是将需要改变的顶点进行一系列矩阵变换,在这里使用了OpenGL的库函数接口 copy
  1. glPushMatrix();  
  2.     glTranslatef(copyVertices[count].x, copyVertices[count].y, copyVertices[count].z);  
  3.     glutSolidSphere(0.1, 20, 20);  
  4. glPopMatrix();  
进行顶点的移动,同时调用了函数
  1. trivariateBernstein  

进行顶点的变换以及权值计算,代码如下所示: copy

  1. Vertex trivariateBernstein(Vertex vert)  
  2. {  
  3.     Vertex stuVert;  
  4.     stuVert.x = 0;  
  5.     stuVert.y = 0;  
  6.     stuVert.z = 0;  
  7.     stuVert = convertToSTU(vert);  
  8.     /*cout << "Vertex (XYZ): " << vert.x << ", " << vert.y << ", " << vert.z << "\n"; 
  9.     cout << "Vertex (STU): " << stuVert.x << ", " << stuVert.y << ", " << stuVert.z << "\n";*/  
  10.       
  11.     double weight = 0.0;  
  12.     Vertex convertedVert;  
  13.     convertedVert.x = 0;  
  14.     convertedVert.y = 0;  
  15.     convertedVert.z = 0;  
  16.   
  17.     /* Performing summations using for loops */  
  18.     for(int i = 0; i <= l; i ) {  
  19.         for(int j = 0; j <= m; j ) {  
  20.             for(int k = 0; k <= n; k ) {  
  21.                 weight = 0;  
  22.                 weight = bernsteinPoly(n, k, stuVert.z) * bernsteinPoly(m, j, stuVert.y) * bernsteinPoly(l, i, stuVert.x);  
  23.                 convertedVert.x  = weight * lattice[i][j][k].x;  
  24.                 convertedVert.y  = weight * lattice[i][j][k].y;  
  25.                 convertedVert.z  = weight * lattice[i][j][k].z;  
  26.             }  
  27.         }  
  28.     }  
  29.     return convertedVert;  
  30. }  
没有使用任何算法,实现的就是模型的矩阵变换。

最后一步就是实现,首先要做的事情是选择需要变形的顶点或者区域,使用了函数如下所示:

  1. void moveSelectedCP(int endX, int endY)  
  2. {  
  3.       
  4.     /* Initialise movement variables */  
  5.     float moveX = 0;  
  6.     float moveY = 0;  
  7.     moveX = (endX-startX);  
  8.     moveY = (endY-startY);  
  9.   
  10.       
  11.     /* Set end point to start point to find next round of moved distance */  
  12.     startX = endX;  
  13.     startY = endY;  
  14.   
  15.     if(!(moveX == 0) || !(moveY == 0)) {  
  16.         //lattice2d[sel_i][sel_j][sel_k].x = endX;  
  17.         //lattice2d[sel_i][sel_j][sel_k].y = endY;  
  18.         lattice2d[sel_i][sel_j][sel_k].x  = moveX;  
  19.         lattice2d[sel_i][sel_j][sel_k].y  = moveY;  
  20.   
  21.         /* Convert to 3D to change position of 3D CP */  
  22.         <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,  
  23.                         modelView, projection, viewport,  
  24.                         &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>  
  25.         cout << "Move CP: [" << sel_i << "][" << sel_j << "][" << sel_k << "]\n";  
  26.         //drawLattice();  
  27.         //deformMesh();  
  28.     }  
  29. }  
在再main函数中调用封装的函数即可。其实做起来还是比较简单的,就是对变形的部分做了矩阵的运算。大家关注上面加粗的部分。

代码下载地址:链接:http://pan.baidu.com/s/1i5klXXV 密码:m1f5

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引