NeHe OpenGL教程 第二十五课:变形
前言
声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改。对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢。
NeHe OpenGL第二十五课:变形

变形和从文件中加载3D物体:
在这一课中,你将学会如何从文件加载3D模型,并且平滑的从一个模型变换为另一个模型。
欢迎来到这激动人心的一课,在这一课里,我们将介绍模型的变形。需要注意的是各个模型必须要有相同的顶点,才能一一对应,并应用变形。
在这一课里,我们同样要教会你如何从一个文件中读取模型数据。
文件开始的部分和前面一样,没有任何变化。
我们结下来添加几个旋转变量,用来记录旋转的信息。并使用cx,cy,cz设置物体在屏幕上的位置。
变量key用来记录当前的模型,step用来设置相邻变形之间的中间步骤。如step为200,则需要200次,才能把一个物体变为另一个物体。
最后我们用一个变量来设置是否使用变形。
GLfloat xrot,yrot,zrot, // X, Y & Z 轴的旋转角度
xspeed,yspeed,zspeed, // X, Y & Z 轴的旋转速度
cx,cy,cz=-15; // 物体的位置
int key=1; // 物体的标识符
int step=0,steps=200; // 变换的步数
bool morph=FALSE; // 是否使用变形
下面的结构定义一个三维顶点
typedef struct
{
float x, y, z;
} VERTEX;
下面的结构使用顶点来描述一个三维物体
typedef struct // 物体结构
{
int verts; // 物体中顶点的个数
VERTEX *points; // 包含顶点数据的指针
} OBJECT;
maxver用来记录各个物体中最大的顶点数,如一个物体使用5个顶点,另一个物体使用20个顶点,那么物体的顶点个数为20。
结下来定义了四个我们使用的模型物体,并把相邻模型变形的中间状态保存在helper中,sour保存原模型物体,dest保存将要变形的模型物体。
int maxver; // 最大的顶点数
OBJECT morph1,morph2,morph3,morph4, // 我们的四个物体
helper,*sour,*dest; // 帮助物体,原物体,目标物体
WndProc()函数没有变化
下面的函数用来为模型分配保存顶点数据的内存空间
void objallocate(OBJECT *k,int n)
{
k->points=(VERTEX*)malloc(sizeof(VERTEX)*n); // 分配n个顶点的内存空间
}
下面的函数用来释放为模型分配的内存空间
void objfree(OBJECT *k)
{
free(k->points);
}
下面的代码用来读取文件中的一行。
我们用一个循环来读取字符,最多读取255个字符,当遇到'\n'回车时,停止读取并立即返回。
void readstr(FILE *f,char *string) // 读取一行字符
{
do
{
fgets(string, 255, f); // 最多读取255个字符
} while ((string[0] == '/') || (string[0] == '\n')); // 遇到回车则停止读取
return; // 返回
}
下面的代码用来加载一个模型文件,并为模型分配内存,把数据存储进去。
void objload(char *name,OBJECT *k) // 从文件加载一个模型
{
int ver; // 保存顶点个数
float rx,ry,rz; // 保存模型位置
FILE *filein; // 打开的文件句柄
char oneline[255]; // 保存255个字符
filein = fopen(name, "rt"); // 打开文本文件,供读取
readstr(filein,oneline); // 读入一行文本
sscanf(oneline, "Vertices: %d\n", &ver); // 搜索字符串"Vertices: ",并把其后的顶点数保存在ver变量中
k->verts=ver; // 设置模型的顶点个数
objallocate(k,ver); // 为模型数据分配内存
下面的循环,读取每一行(即每个顶点)的数据,并把它保存到内存中?/td>
for (int i=0;i<ver;i++) // 循环所有的顶点
{
readstr(filein,oneline); // 读取一行数据
sscanf(oneline, "%f %f %f", &rx, &ry, &rz); // 把顶点数据保存在rx,ry,rz中
k->points[i].x = rx; // 保存当前顶点的x坐标
k->points[i].y = ry; // 保存当前顶点的y坐标
k->points[i].z = rz; // 保存当前顶点的z坐标
}
fclose(filein); // 关闭文件
if(ver>maxver) maxver=ver; // 记录最大的顶点数
}
下面的函数根据设定的间隔,计算第i个顶点每次变换的位移
VERTEX calculate(int i) // 计算第i个顶点每次变换的位移
{
VERTEX a;
a.x=(sour->points[i].x-dest->points[i].x)/steps;
a.y=(sour->points[i].y-dest->points[i].y)/steps;
a.z=(sour->points[i].z-dest->points[i].z)/steps;
return a;
}
ReSizeGLScene()函数没有变化
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
下面的函数完成初始化功能,它设置混合模式为半透明
int InitGL(GLvoid)
{
glBlendFunc(GL_SRC_ALPHA,GL_ONE); // 设置半透明混合模式
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 设置清除色为黑色
glClearDepth(1.0); // 设置深度缓存中值为1
glDepthFunc(GL_LESS); // 设置深度测试函数
glEnable(GL_DEPTH_TEST); // 启用深度测试
glShadeModel(GL_SMOOTH); // 设置着色模式为光滑着色
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
下面的代码用来加载我们的模型物体
maxver=0; // 初始化最大顶点数为0
objload("data/sphere.txt",&morph1); // 加载球模型
objload("data/torus.txt",&morph2); // 加载圆环模型
objload("data/tube.txt",&morph3); // 加载立方体模型
第四个模型不从文件读取,我们在(-7,-7,-7)-(7,7,7)之间随机生成模型点,它和我们载如的模型都一样具有486个顶点。
objallocate(&morph4,486); // 为第四个模型分配内存资源
for(int i=0;i<486;i++) // 随机设置486个顶点
{
morph4.points[i].x=((float)(rand()%14000)/1000)-7;
morph4.points[i].y=((float)(rand()%14000)/1000)-7;
morph4.points[i].z=((float)(rand()%14000)/1000)-7;
}
初始化中间模型为球体,并把原和目标模型都设置为球
objload("data/sphere.txt",&helper);
sour=dest=&morph1;
return TRUE; // 初始化完成,成功返回
}
下面是具体的绘制代码,向往常一样我们先设置模型变化,以便我们更好的观察。
void DrawGLScene(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清空缓存
glLoadIdentity(); // 重置模型变换矩阵
glTranslatef(cx,cy,cz); // 平移和旋转
glRotatef(xrot,1,0,0);
glRotatef(yrot,0,1,0);
glRotatef(zrot,0,0,1);
xrot+=xspeed; yrot+=yspeed; zrot+=zspeed; // 根据旋转速度,增加旋转角度
GLfloat tx,ty,tz; // 顶点临时变量
VERTEX q; // 保存中间计算的临时顶点
接下来我们来绘制模型中的点,如果启用了变形,则计算变形的中间过程点。
glBegin(GL_POINTS); // 点绘制开始
for(int i=0;i<morph1.verts;i++) // 循环绘制模型1中的每一个顶点
{
if(morph) q=calculate(i); else q.x=q.y=q.z=0; // 如果启用变形,则计算中间模型
helper.points[i].x-=q.x;
helper.points[i].y-=q.y;
helper.points[i].z-=q.z;
tx=helper.points[i].x; // 保存计算结果到x,y,z变量中
ty=helper.points[i].y;
tz=helper.points[i].z;
为了让动画开起来流畅,我们一共绘制了三个中间状态的点。让变形过程从蓝绿色向蓝色下一个状态变化。
glColor3f(0,1,1); // 设置颜色
glVertex3f(tx,ty,tz); // 绘制顶点
glColor3f(0,0.5f,1); // 把颜色变蓝一些
tx-=2*q.x; ty-=2*q.y; ty-=2*q.y; // 如果启用变形,则绘制2步后的顶点
glVertex3f(tx,ty,tz);
glColor3f(0,0,1); // 把颜色变蓝一些
tx-=2*q.x; ty-=2*q.y; ty-=2*q.y; // 如果启用变形,则绘制2步后的顶点
glVertex3f(tx,ty,tz);
}
glEnd(); // 绘制结束
最后如果启用了变形,则增加递增的步骤参数,然后绘制下一个点。
// 如果启用变形则把变形步数增加
if(morph && step<=steps)step++; else { morph=FALSE; sour=dest; step=0;}
return TRUE; // 一切OK
}
KillGLWindow() 函数基本没有变化,只是添加释放5个模型内存的代码
objfree(&morph1); // 释放模型1内存
objfree(&morph2); // 释放模型2内存
objfree(&morph3); // 释放模型3内存
objfree(&morph4); // 释放模型4内存
objfree(&helper); // 释放模型5内存
CreateGLWindow() 函数没有变化
BOOL CreateGLWindow()
LRESULT CALLBACK WndProc()
在WinMain()函数中,我们添加了一些键盘控制的函数
if(keys[VK_PRIOR]) // PageUp键是否被按下
zspeed+=0.01f; // 按下增加绕z轴旋转的速度
if(keys[VK_NEXT]) // PageDown键是否被按下
zspeed-=0.01f; // 按下减少绕z轴旋转的速度
if(keys[VK_DOWN]) // 下方向键是否被按下
xspeed+=0.01f; // 按下增加绕x轴旋转的速度
if(keys[VK_UP]) // 上方向键是否被按下
xspeed-=0.01f; // 按下减少绕x轴旋转的速度
if(keys[VK_RIGHT]) // 右方向键是否被按下
yspeed+=0.01f; // 按下增加沿y轴旋转的速度
if(keys[VK_LEFT]) // 左方向键是否被按下
yspeed-=0.01f; // 按下减少沿y轴旋转的速度
if (keys['Q']) // Q键是否被按下
cz-=0.01f; // 是则向屏幕里移动
if (keys['Z']) // Z键是否被按下
cz+=0.01f; // 是则向屏幕外移动
if (keys['W']) // W键是否被按下
cy+=0.01f; // 是则向上移动
if (keys['S']) // S键是否被按下
cy-=0.01f; // 是则向下移动
if (keys['D']) // D键是否被按下
cx+=0.01f; // 是则向右移动
if (keys['A']) // A键是否被按下
cx-=0.01f; // 是则向左移动
1,2,3,4键用来设置变形的目标模型
if (keys['1'] && (key!=1) && !morph) // 如果1被按下,则变形到模型1
{
key=1;
morph=TRUE;
dest=&morph1;
}
if (keys['2'] && (key!=2) && !morph) // 如果2被按下,则变形到模型1
{
key=2;
morph=TRUE;
dest=&morph2;
}
if (keys['3'] && (key!=3) && !morph) // 如果3被按下,则变形到模型1
{
key=3;
morph=TRUE;
dest=&morph3;
}
if (keys['4'] && (key!=4) && !morph) // 如果4被按下,则变形到模型1
{
key=4;
morph=TRUE;
dest=&morph4;
}
原文及其个版本源代码下载:
NeHe OpenGL教程 第二十五课:变形的更多相关文章
- NeHe OpenGL教程 第二十九课:Blt函数
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- NeHe OpenGL教程 第二十八课:贝塞尔曲面
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- NeHe OpenGL教程 第二十六课:反射
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- NeHe OpenGL教程 第二十四课:扩展
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- NeHe OpenGL教程 第二十二课:凹凸映射
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- NeHe OpenGL教程 第十五课:纹理图形字
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- NeHe OpenGL教程 第二十课:蒙板
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- NeHe OpenGL教程 第十九课:粒子系统
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- NeHe OpenGL教程 第十八课:二次几何体
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
随机推荐
- hihoCoder:#1079(线段树+离散化)
题目大意:给n个区间,有的区间可能覆盖掉其他区间,问没有完全被其他区间覆盖的区间有几个?区间依次给出,如果有两个区间完全一样,则视为后面的覆盖前面的. 题目分析:区间可能很长,所以要将其离散化.但离散 ...
- 排序算法总结(二)归并排序【Merge Sort】
一.归并排序原理(Wikipedia) 归并排序本质是分治思想的应用,并且各层分治递归可以同时进行 1.申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列 2.设定两个指针,最初位置 ...
- font-size单位换算
Points Pixels Ems Percent 6pt 8px 0.5em 50% 7pt 9px 0.55em 55% 7.5pt 10px 0.625em 62.5% 8pt 11px 0.7 ...
- Android sdk 镜像服务器资源
大连东软信息学院镜像服务器地址:- http://mirrors.neusoft.edu.cn 端口:80北京化工大学镜像服务器地址:- IPv4: http://ubuntu.buct.edu.cn ...
- ps调色技能
色相/饱和度: 色相->当你看到一种颜色,你会很快的辨别他为什么颜色,比如说红色,绿色,黄色.选中某种颜色然后调整色相. 饱和度->色彩艳丽程度. 明度->调整图片暗亮 曲线:暗亮调 ...
- network Driver , TDI(Transport Driver Interface) Drivers
https://msdn.microsoft.com/en-us/library/windows/hardware/ff565094(v=vs.85).aspx https://msdn.micros ...
- php特殊语法--模板引擎中比较常见
<?php $a=array(1,2,0); foreach ($a as $v): if($v>1): ?> 5 <?php endif; endforeach; ?> ...
- C#加载dll 创建类对象
//加载dll 创建类对象string sqlightAssembly = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "syst ...
- HTML兼容性设置
今天碰到了兼容性问题,页面显示空白,打开调试界面,显示信息 “Compatibility View because 'Display intranet sites in Compatibility V ...
- Oracle数据库——SQL基本查询
一.涉及内容 1.掌握SELECT 语句的基本语法. 2.掌握常用函数的使用. 二.具体操作 (一)使用Scott方案下的emp表和dept表,完成以下操作: 1.查询部门编号是20的员工信息. 2. ...