OpenGL实现3D自由变形
笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。
CSDN视频网址:http://edu.csdn.net/lecturer/144
以前有朋友问我,关于变形动画是如何实现的,实现方式主要有两种,一种是通过美术人员利用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; // Halfedge structures struct HE_edge; struct HE_vert; struct HE_face; struct HE_line; struct HE_edge { int edgeID; HE_vert* vertS; // Vertex at the start of halfedge HE_vert* vertE; // Vertex at the end of halfedge HE_edge* pair; // Oppositely oriented halfedge HE_face* face; // Incident face HE_edge* next; // Next halfedge around the face }; struct HE_vert { int vertID; // Vertex ID float x, y, z; // Vertex coordinates float nx, ny, nz; // Vertex normals bool selected; // Selected for FFD HE_edge* edge; // Halfedge emanating from vertex }; struct HE_face { int faceID; // Face ID int v1, v2, v3; // Vertex IDs float nx, ny, nz; // Face normals HE_edge* edge; // Halfedge bordering the face }; 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
第三步,需要加载模型,并计算顶点和面的法线以及设置包围盒,头文件的内容给读者展示如下:
#ifndef mesh_h #define mesh_h #include <cstdlib> #include <GL\glui.h> //#include <GL\freeglut.h> #include <map> #include <Windows.h> #include <WTypes.h> using namespace std;
// Mesh functions 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); // Draw functions double editLineWidth(int width); void drawMeshWire(void); void drawMeshSolid(void); void drawMeshPoint(void); void drawBbox(void); void drawGrid(void); void drawAxes(void); // Dialog functions void createFileDialog(void); int createMsgbox(int msgboxID); enum { ERR_no_mesh = 0, ERR_no_pt }; #endif
具体实现会在博客的末尾给读者展示。
第四步,开始对模型的变形处理,在这里并没有使用什么高深的算法,其实就是对它们的顶点进行矩阵变换。首先进行的是世界变换,投影变换,最后是视口变换。
变形的函数如下所示:
void deformMesh() { copyVertices = new Vertex[numOfVert](); for(int count = 0; count < numOfVert; count++) { /* Create copy of vertex position */ copyVertices[count].x = v0[count].x; copyVertices[count].y = v0[count].y; copyVertices[count].z = v0[count].z; /* Recalculate new minimum/maximum vertices */ if(vertices[count].selected) { findSelectedMinMax(vertices[count].x, vertices[count].y, vertices[count].z); } } for(int count = 0; count < numOfVert; count++) { /* Only calculate new position of vertex if selected */ if(vertices[count].selected) { /* Highlights selected vertex */ 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; //vertices[count].z = 0; /* Calculate the FFD position and update vertex position */ Vertex tempResult = trivariateBernstein(copyVertices[count]); vertices[count].x = tempResult.x; vertices[count].y = tempResult.y; vertices[count].z = tempResult.z; //cout << "Vertex (old): " << vertices[count].x << ", " << vertices[count].y << ", " << vertices[count].z << "\n"; //cout << "Vertex (new): " << vertices[count].x << ", " << vertices[count].y << ", " << vertices[count].z << "\n"; } } }
当你看到代码后,第一印象就是太简单了,确实是这样,变形其实就是将需要改变的顶点进行一系列矩阵变换,在这里使用了OpenGL的库函数接口
glPushMatrix(); glTranslatef(copyVertices[count].x, copyVertices[count].y, copyVertices[count].z); glutSolidSphere(0.1, 20, 20); glPopMatrix();
进行顶点的移动,同时调用了函数
trivariateBernstein
进行顶点的变换以及权值计算,代码如下所示:
Vertex trivariateBernstein(Vertex vert) { Vertex stuVert; stuVert.x = 0; stuVert.y = 0; stuVert.z = 0; stuVert = convertToSTU(vert); /*cout << "Vertex (XYZ): " << vert.x << ", " << vert.y << ", " << vert.z << "\n"; cout << "Vertex (STU): " << stuVert.x << ", " << stuVert.y << ", " << stuVert.z << "\n";*/ double weight = 0.0; Vertex convertedVert; convertedVert.x = 0; convertedVert.y = 0; convertedVert.z = 0; /* Performing summations using for loops */ 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) { /* Initialise movement variables */ float moveX = 0; float moveY = 0; moveX = (endX-startX); moveY = (endY-startY); /* Set end point to start point to find next round of moved distance */ startX = endX; startY = endY; if(!(moveX == 0) || !(moveY == 0)) { //lattice2d[sel_i][sel_j][sel_k].x = endX; //lattice2d[sel_i][sel_j][sel_k].y = endY; lattice2d[sel_i][sel_j][sel_k].x += moveX; lattice2d[sel_i][sel_j][sel_k].y += moveY; /* Convert to 3D to change position of 3D CP */ 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); cout << "Move CP: [" << sel_i << "][" << sel_j << "][" << sel_k << "]\n"; //drawLattice(); //deformMesh(); } }
在再main函数中调用封装的函数即可。其实做起来还是比较简单的,就是对变形的部分做了矩阵的运算。大家关注上面加粗的部分。
代码下载地址:链接:http://pan.baidu.com/s/1i5klXXV 密码:m1f5
OpenGL实现3D自由变形的更多相关文章
- 自由变形技术(Free-Form Deformation)
自由变形技术Free-Form Deformation是编辑几何模型的重要手段,它于80年代由Sederberg等人提出,目前许多三维建模软件中都有这种变形算法.自由变形方法在变形过程中并不是直接操作 ...
- KTL 一个支持C++14编辑公式的K线技术工具平台 - 第六版,支持OpenGL,3D上帝视角俯视K线概貌。
K,K线,Candle蜡烛图. T,技术分析,工具平台 L,公式Language语言使用c++14,Lite小巧简易. 项目仓库:https://github.com/bbqz007/KTL 国内仓库 ...
- OpenGL实现3D漫游的理解
这篇文章主要参考以下两篇博客: 推导相机变换矩阵 OpenGL系列教程之五:OpenGL矩阵类 上面的第一篇是理论的讲解,第二篇有实例代码,我在后面会给出自己写的主函数,依赖的类可以从第二篇参考中下载 ...
- 最新广商小助手 项目进展 OpenGL ES 3D在我项目中引用 代码太多只好选重要部分出来
package com.example.home; import java.io.IOException; import java.io.InputStream; import javax.micro ...
- OpenGL ES 3D空间中自定义显示空间
在Android中,我们所熟知的是在ES管线中,其在图元装配时,会进行图元组装与图元分配,这样就回剪裁出来视景体中的物体.但是如果我想在3D场景中规定一个区域,凡是在这个区域中的物体就能显示出来,非这 ...
- JAVA智能设备基于OpenGL的3D开发技术 之AABB碰撞检测算法论述
摘要:无论是PC机的3D还是智能设备应用上,碰撞检测始终是程序开发的难点,甚至可以用碰撞检测作为衡量3D引擎是否完善的标准.现有许多3D碰撞检测算法,其中AABB碰撞检测是一种卓有成效而又经典的检测算 ...
- 3ds max学习笔记(十四)-- (FFD自由变形)
FFD长方体,FFD圆柱体: 栗子2:通过对长方体进行自由编辑,松弛,和涡轮平滑的操作实现抱枕模型,抱枕表面的凹凸效果,可以通过贴图的方式来实现:
- bullet物理引擎与OpenGL结合 导入3D模型进行碰撞检测 以及画三角网格的坑
原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/11681069.html 一.初始化世界以及模型 /// 冲突配置包含内存的默认设置,冲突设置. ...
- 深入理解CSS变形transform(3d)
× 目录 [1]坐标轴 [2]透视 [3]变形函数 [4]透视函数 [5]变形原点 [6]背景可见 [7]变形风格 前面的话 本文将详细介绍关于transform变形3D的内容,但需以了解transf ...
随机推荐
- docker使用Mesos
https://github.com/PyreneGitHub/mesos_use/tree/master
- getJson同步
$.ajaxSettings.async = false;//在执行之前加$.ajaxSettings.async = false; (同步执行) function get_no_order_ar ...
- PyTorch源码解读之torch.utils.data.DataLoader(转)
原文链接 https://blog.csdn.net/u014380165/article/details/79058479 写得特别好!最近正好在学习pytorch,学习一下! PyTorch中数据 ...
- Java高级面试题
一.基础知识: 1)集合类:List和Set比较,各自的子类比较(ArrayList,Vector,LinkedList:HashSet,TreeSet): 2)HashMap的底层实现,之后会问Co ...
- HTML中table的td宽度无法固定问题
设置了 width="10%" 依然会被内容撑大, 加了 style="word-break:break-all;" 属性就好了.效果是内容自动回车. 此属性不 ...
- DataTable扩展:转化实体ToList
直接上代码: 根据属性进行映射:DataTable转化成实体List public static class DataTableExtension { public static List<T& ...
- 使用SoupUI进行简单的WebService接口测试
1.工具介绍 SoapUI是一个开源测试工具,通过soap/http来检查.调用.实现Web Service的功能/负载/符合性测试.该工具既可作为一个单独的测试软件使用,也可利用插件集成到Eclip ...
- web页面中 将几个字段post提交
思路 自己在html中构建form 先根据传入的action构建form的action 然后根据要提交的字段构建form中的元素 最后通过调用form中的按钮提交from表单 方法:var jsP ...
- HTML子页面保存关闭并刷新父页面
1.思路是子页面保存后,后台传递成功的js到前台. 2.js的原理是——子页面调用父页面的刷新 子页面 function Refresh() { window.parent.Re ...
- hdu4347The Closest M Points kdtree
kdtree讲解: https://blog.csdn.net/qing101hua/article/details/53228668 https://blog.csdn.net/acdreamers ...