OpenGL实现3D自由变形

原创 2017年07月22日 19:05:04

笔者介绍:姜雪伟,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


版权声明:本文为博主原创文章,未经博主允许不得转载。

Unity3D 关于模型变形技术代码实现

本篇博客给读者介绍关于如何实现模型的变形,在项目开发中经常会涉及到模型的变形操作,比如如下效果图: 第一部分准备工作 首先在Unity中建立一个场景,在场景中放置一个球体,这个球体可以使用Max工具建...
  • jxw167
  • jxw167
  • 2017年09月06日 15:31
  • 1979

OpenGL之路(二)设置观测矩阵和避免图形拉伸变形

环境配置成功后,运行了示例代码,你一定发现随着窗口的拉伸变化,三角形变形了,下面就来解决这个问题 代码如下 #include #include #include #pragma commen...

OpenGL编程(四)改变窗口大小时保持图形的原形

前面的例子,当我们通过拖拉的方法改变窗口的长宽比例时,窗口里的图形的长宽也相应地伸缩,导致图形变形。如下图:正如上图所示,当我们把窗口宽度拉长后,图形就会显得比较胖。同样,当我们把窗口的高度拉长后,图...

OpenCV+OpenGL 双目立体视觉三维重建

0.绪论这篇文章主要为了研究双目立体视觉的最终目标——三维重建,系统的介绍了三维重建的整体步骤。双目立体视觉的整体流程包括:图像获取,摄像机标定,特征提取(稠密匹配中这一步可以省略),立体匹配,三维重...

[OpenGL]OpenGL中的三维变换

显示生活中我们如何去绘制一个三维的图形,如一个立方体。我们通常会确定出它的大小,从不同的角度观察它,移动或者旋转它,确定好的视角等等。这些我们再OpenGL中也都可以实现。一个简单的实例开始OpenG...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

体绘制(Volume Rendering)概述之2:体数据详解!!!(转载)

转载地址: http://blog.csdn.net/liu_lin_xm/article/details/4850593 摘抄“GPU Programming And Cg Language P...

使用Qt和Enginio开发云端程序

Enginio是随着Qt2012年的Qt开发者大会上提出的。目的是让Qt开发者对接目前正在蓬勃发展的云技术。在经过半年的发展后,Enginio已经迎来了第一个Alpha版本(2013年7月5日)。预计...
  • dj0379
  • dj0379
  • 2014年04月24日 12:54
  • 3923

OpenGL实现3D魔方游戏源代码

【转】http://blog.csdn.net/hackbuteer1/article/details/6679557 首先这个程序是建立的是Windows应用程序,建立控制台程序是不能运行的,另外...

OpenGL实现3D立体显示

http://blog.csdn.net/hackbuteer1/article/details/6679306  由于左眼和右眼观看显示器的角度不同,利用这一角度差遮住光线就可将图...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:OpenGL实现3D自由变形
举报原因:
原因补充:

(最多只允许输入30个字)