C++博客 :: 首页 :: 联系 ::  :: 管理
  163 Posts :: 4 Stories :: 350 Comments :: 0 Trackbacks

常用链接

留言簿(48)

我参与的团队

搜索

  •  

积分与排名

  • 积分 - 397030
  • 排名 - 59

最新评论

阅读排行榜

评论排行榜

欢迎来到这激动人心的一课,在这一课里,我们将介绍模型的变形。需要注意的是各个模型必须要有相同的顶点,才能一一对应,并应用变形。
在这一课里,我们同样要教会你如何从一个文件中读取模型数据。
文件开始的部分和前面一样,没有任何变化。 
  
  
 我们结下来添加几个旋转变量,用来记录旋转的信息。并使用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;                   
                }

  
 我希望你能喜欢这个教程,相信你已经学会了变形动画。
Piotr Cieslak 的代码非常的新颖,希望通过这个教程你能知道如何从文件中加载三维模型。
这份教程化了我三天的时间,如果有什么错误请告诉我。

 


posted on 2007-12-19 11:46 sdfasdf 阅读(1400) 评论(1)  编辑 收藏 引用 所属分类: OPENGL

Feedback

# re: OpenGL教程 第24课 变形 2007-12-19 16:06 fanzhen
这个教程不错  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理