请登陆或者注册新用户 用户名 密码 记住密码 注册新用户 忘记密码了

回 帖 快速回帖 发 新 帖 刷新版面
主题:[原创]OpenGL入门学习——第六课

作者:eastcowboy

专家分:22560

级别:113级别:113级别:113级别:113级别:113级别:113级别:113级别:113

发表时间:2006-10-8 18:38:00    [回复] 
楼主
趁着国庆这把火,再写一课吧。要不过两天,事情就又多起来了。
今后一段时间内我极有可能暂停更新,希望大家做好心理准备 。学习是不能停止的,大家加油了。

下面回顾一下我们已经学习的课程:
第一课,编写第一个OpenGL程序
第二课,绘制几何图形
第三课,绘制几何图形的一些细节问题
第四课,颜色的选择
第五课,三维的空间变换
第六课,动画的制作  ——→  本次课程的内容

今天要讲的是动画制作——可能是各位都很喜欢的。除了讲授知识外,我们还会让昨天那个“太阳、地球和月亮”天体图画动起来。缓和一下枯燥的气氛。

=====================未完,请勿跟帖=====================

 

作者:eastcowboy

专家分:22560

级别:113级别:113级别:113级别:113级别:113级别:113级别:113级别:113

发表时间:2006-10-8 18:40:00    [回复]  [引用]
1楼
本次课程,我们将进入激动人心的计算机动画世界。

想必大家都知道电影和动画的工作原理吧?是的,快速的把看似连续的画面一幅幅的呈现在人们面前。一旦每秒钟呈现的画面超过24幅,人们就会错以为它是连续的。
我们通常观看的电视,每秒播放25或30幅画面。但对于计算机来说,它可以播放更多的画面,以达到更平滑的效果。如果速度过慢,画面不够平滑。如果速度过快,则人眼未必就能反应得过来。对于一个正常人来说,每秒60~120幅图画是比较合适的。具体的数值因人而异。

假设某动画一共有n幅画面,则它的工作步骤就是:
显示第1幅画面,然后等待一小段时间,直到下一个1/24秒
显示第2幅画面,然后等待一小段时间,直到下一个1/24秒
……
显示第n幅画面,然后等待一小段时间,直到下一个1/24秒
结束
如果用C语言伪代码来描述这一过程,就是:
for(i=0; i<n; ++i)
{
    DrawScene(i);
    Wait();
}

=====================未完,请勿跟帖=====================

  最后修改于2006-10-8 18:41:00

作者:eastcowboy

专家分:22560

级别:113级别:113级别:113级别:113级别:113级别:113级别:113级别:113

发表时间:2006-10-8 18:42:00    [回复]  [引用]
2楼
1、双缓冲技术
在计算机上的动画与实际的动画有些不同:实际的动画都是先画好了,播放的时候直接拿出来显示就行。计算机动画则是画一张,就拿出来一张,再画下一张,再拿出来。如果所需要绘制的图形很简单,那么这样也没什么问题。但一旦图形比较复杂,绘制需要的时间较长,问题就会变得突出。
让我们把计算机想象成一个画图比较快的人,假如他直接在屏幕上画图,而图形比较复杂,则有可能在他只画了某幅图的一半的时候就被观众看到。而后面虽然他把画补全了,但观众的眼睛却又没有反应过来,还停留在原来那个残缺的画面上。也就是说,有时候观众看到完整的图象,有时却又只看到残缺的图象,这样就造成了屏幕的闪烁。
如何解决这一问题呢?我们设想有两块画板,画图的人在旁边画,画好以后把他手里的画板与挂在屏幕上的画板相交换。这样以来,观众就不会看到残缺的画了。这一技术被应用到计算机图形中,称为双缓冲技术。即:在存储器(很有可能是显存)中开辟两块区域,一块作为发送到显示器的数据,一块作为绘画的区域,在适当的时候交换它们。由于交换两块内存区域实际上只需要交换两个指针,这一方法效率非常高,所以被广泛的采用。
注意:虽然绝大多数平台都支持双缓冲技术,但这一技术并不是OpenGL标准中的内容。OpenGL为了保证更好的可移植性,允许在实现时不使用双缓冲技术。当然,我们常用的PC都是支持双缓冲技术的。
要启动双缓冲功能,最简单的办法就是使用GLUT工具包。我们以前在main函数里面写:
glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
其中GLUT_SINGLE表示单缓冲,如果改成GLUT_DOUBLE就是双缓冲了。
当然还有需要更改的地方——每次绘制完成时,我们需要交换两个缓冲区,把绘制好的信息用于屏幕显示(否则无论怎么绘制,还是什么都看不到)。如果使用GLUT工具包,也可以很轻松的完成这一工作,只要在绘制完成时简单的调用glutSwapBuffers函数就可以了。

=====================未完,请勿跟帖=====================

 

作者:eastcowboy

专家分:22560

级别:113级别:113级别:113级别:113级别:113级别:113级别:113级别:113

发表时间:2006-10-8 18:43:00    [回复]  [引用]
3楼
2、实现连续动画
似乎没有任何疑问,我们应该把绘制动画的代码写成下面这个样子:
for(i=0; i<n; ++i)
{
    DrawScene(i);
    glutSwapBuffers();
    Wait();
}
但事实上,这样做不太符合窗口系统的程序设计思路。还记得我们的第一个OpenGL程序吗?我们在main函数里写:glutDisplayFunc(&myDisplay);
意思是对系统说:如果你需要绘制窗口了,请调用myDisplay这个函数。为什么我们不直接调用myDisplay,而要采用这种看似“舍近求远”的做法呢?原因在于——我们自己的程序无法掌握究竟什么时候该绘制窗口。因为一般的窗口系统——拿我们熟悉一点的来说——Windows和X窗口系统,都是支持同时显示多个窗口的。假如你的程序窗口碰巧被别的窗口遮住了,后来用户又把原来遮住的窗口移开,这时你的窗口需要重新绘制。很不幸的,你无法知道这一事件发生的具体时间。因此这一切只好委托操作系统来办了。
现在我们再看上面那个循环。既然DrawScene都可以交给操作系统来代办了,那让整个循环运行起来的工作是否也可以交给操作系统呢?答案是肯定的。我们先前的思路是:绘制,然后等待一段时间;再绘制,再等待一段时间。但如果去掉等待的时间,就变成了绘制,绘制,……,不停的绘制。——当然了,资源是公用的嘛,杀毒软件总要工作吧?我的下载不能停下来吧?我的mp3播放还不能给耽搁了。总不能因为我们的动画,让其他的工作都停下来。因此,我们需要在CPU空闲的时间绘制。
这里的“在CPU空闲的时间绘制”和我们在第一课讲的“在需要绘制的时候绘制”有些共通,都是“在XX时间做XX事”,GLUT工具包也提供了一个比较类似的函数:glutIdleFunc,表示在CPU空闲的时间调用某一函数。其实GLUT还提供了一些别的函数,例如“在键盘按下时做某事”等。

=====================未完,请勿跟帖=====================

  最后修改于2006-10-8 19:01:00

作者:eastcowboy

专家分:22560

级别:113级别:113级别:113级别:113级别:113级别:113级别:113级别:113

发表时间:2006-10-8 18:45:00    [回复]  [引用]
4楼
到现在,我们已经可以初步开始制作动画了。好的,就拿上次那个“太阳、地球和月亮”的程序开刀,让地球和月亮自己动起来。
#include <GL/glut.h>

// 太阳、地球和月亮
// 假设每个月都是30天
// 一年12个月,共是360天
static int day = 200; // day的变化:从0到359
void myDisplay(void)
{
    /****************************************************
     这里的内容照搬上一课的,只因为使用了双缓冲,补上最后这句
    *****************************************************/
    glutSwapBuffers();
}

void myIdle(void)
{
    /* 新的函数,在空闲时调用,作用是把日期往后移动一天并重新绘制,达到动画效果 */
    ++day;
    if( day >= 360 )
        day = 0;
    myDisplay();
}

int main(int argc, char *argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE); // 修改了参数为GLUT_DOUBLE
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(400, 400);
    glutCreateWindow("太阳,地球和月亮");   // 改了窗口标题
    glutDisplayFunc(&myDisplay);
    glutIdleFunc(&myIdle);               // 新加入了这句
    glutMainLoop();
    return 0;
}


=====================未完,请勿跟帖=====================

  最后修改于2006-10-8 19:03:00

作者:eastcowboy

专家分:22560

级别:113级别:113级别:113级别:113级别:113级别:113级别:113级别:113

发表时间:2006-10-8 18:45:00    [回复]  [引用]
5楼
3、关于垂直同步
代码是写好了,但相信大家还有疑问。某些朋友可能在运行时发现,虽然CPU几乎都用上了,但运动速度很快,根本看不清楚,另一些朋友在运行时发现CPU使用率很低,根本就没有把空闲时间完全利用起来。但对于上面那段代码来说,这些现象都是合理的。这里就牵涉到关于垂直同步的问题。

大家知道显示器的刷新率是比较有限的,一般为60~120Hz,也就是一秒钟刷新60~120次。但如果叫计算机绘制一个简单的画面,例如只有一个三角形,则一秒钟可以绘制成千上万次。因此,如果最大限度的利用计算机的处理能力,绘制很多幅画面,但显示器的刷新速度却跟不上,这不仅造成性能的浪费,还可能带来一些负面影响(例如,显示器只刷新到一半时,需要绘制的内容却变化了,由于显示器是逐行刷新的,于是显示器上半部分和下半部分实际上是来自两幅画面)。采用垂直同步技术可以解决这一问题。即,只有在显示器刷新时,才把绘制好的图象传输出去供显示。这样一来,计算机就不必去绘制大量的根本就用不到的图象了。如果显示器的刷新率为85Hz,则计算机一秒钟只需要绘制85幅图象就足够,如果场景足够简单,就会造成比较多的CPU空闲。
几乎所有的显卡都支持“垂直同步”这一功能。
垂直同步也有它的问题。如果刷新频率为60Hz,则在绘制比较简单的场景时,绘制一幅图画需要的时间很段,帧速可以恒定在60FPS(即60帧/秒)。如果场景变得复杂,绘制一幅图画的时间超过了1/60秒,则帧速将急剧下降。
如果绘制一幅图画的时间为1/50,则在第一个1/60秒时,显示器需要刷新了,但由于新的图画没有画好,所以只能显示原来的图画,等到下一个1/60秒时才显示新的图画。于是显示一幅图画实际上用了1/30秒,帧速为30FPS。(如果不采用垂直同步,则帧速应该是50FPS)
如果绘制一幅图画的时间更长,则下降的趋势就是阶梯状的:60FPS,30FPS,20FPS,……(60/1,60/2,60/3,……)
如果每一幅图画的复杂程度是不一致的,且绘制它们需要的时间都在1/60上下。则在1/60时间内画完时,帧速为60FPS,在1/60时间未完成时,帧速为30FPS,这就造成了帧速的跳动。这是很麻烦的事情,需要避免它——要么想办法简化每一画面的绘制时间,要么都延迟一小段时间,以作到统一。

回过头来看前面的问题。如果使用了大量的CPU而且速度很快无法看清,则打开垂直同步可以解决该问题。当然如果你认为垂直同步有这样那样的缺点,也可以关闭它。——至于如何打开和关闭,因操作系统而异了。具体步骤请自己搜索之。

当然,也有其它办法可以控制动画的帧速,或者尽量让动画的速度尽量和帧速无关。不过这里面很多内容都是与操作系统比较紧密的,况且它们跟OpenGL关系也不太大。这里就不做介绍了。

=====================未完,请勿跟帖=====================

  最后修改于2006-10-8 19:05:00

作者:eastcowboy

专家分:22560

级别:113级别:113级别:113级别:113级别:113级别:113级别:113级别:113

发表时间:2006-10-8 18:46:00    [回复]  [引用]
6楼
4、计算帧速
不知道大家玩过3D Mark这个软件没有,它可以运行各种场景,测出帧速,并且为你的系统给出评分。这里我也介绍一个计算帧速的方法。
根据定义,帧速就是一秒钟内播放的画面数目(FPS)。我们可以先测量绘制两幅画面之间时间t,然后求它的倒数即可。假如t=0.05s,则FPS的值就是1/0.05=20。
理论上是如此了,可是如何得到这个时间呢?通常C语言的time函数精确度一般只到一秒,肯定是不行了。clock函数也就到十毫秒左右,还是有点不够。因为FPS为60和FPS为100的时候,t的值都是十几毫秒。
你知道如何测量一张纸的厚度吗?一个粗略的办法就是:用很多张纸叠在一起测厚度,计算平均值就可以了。我们这里也可以这样办。测量绘制50幅画面(包括垂直同步等因素的等待时间)需要的时间t',由t'=t*50很容易的得到FPS=1/t=50/t'
下面这段代码可以统计该函数自身的调用频率,(原理就像上面说的那样),程序并不复杂,并且这并不属于OpenGL的内容,所以我不打算详细讲述它。
#include <time.h>
double CalFrequency()
{
    static int count;
    static double save;
    static clock_t last, current;
    double timegap;

    ++count;
    if( count <= 50 )
        return save;
    count = 0;
    last = current;
    current = clock();
    timegap = (current-last)/(double)CLK_TCK;
    save = 50.0/timegap;
    return save;
}

最后,要把计算的帧速显示出来,但我们并没有学习如何使用OpenGL把文字显示到屏幕上。——但不要忘了,在我们的图形窗口背后,还有一个命令行窗口~使用printf函数就可以轻易的输出文字了。
#include <stdio.h>

double FPS = CalFrequency();
printf("FPS = %f\n", FPS);
最后的一步,也被我们解决了——虽然做法不太雅观,没关系,以后我们还会改善它的。

=====================未完,请勿跟帖=====================

  最后修改于2006-10-8 18:51:00

作者:eastcowboy

专家分:22560

级别:113级别:113级别:113级别:113级别:113级别:113级别:113级别:113

发表时间:2006-10-8 18:48:00    [回复]  [引用]
7楼
时间过得太久,每次给的程序都只是一小段,一些朋友难免会出问题。
现在,我给出一个比较完整的程序,供大家参考。
#include <GL/glut.h>
#include <stdio.h>
#include <time.h>

// 太阳、地球和月亮
// 假设每个月都是12天
// 一年12个月,共是360天
static int day = 200; // day的变化:从0到359

double CalFrequency()
{
    static int count;
    static double save;
    static clock_t last, current;
    double timegap;

    ++count;
    if( count <= 50 )
        return save;
    count = 0;
    last = current;
    current = clock();
    timegap = (current-last)/(double)CLK_TCK;
    save = 50.0/timegap;
    return save;
}

void myDisplay(void)
{
    double FPS = CalFrequency();
    printf("FPS = %f\n", FPS);

    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(75, 1, 1, 400000000);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(0, -200000000, 200000000, 0, 0, 0, 0, 0, 1);

    // 绘制红色的“太阳”
    glColor3f(1.0f, 0.0f, 0.0f);
    glutSolidSphere(69600000, 20, 20);
    // 绘制蓝色的“地球”
    glColor3f(0.0f, 0.0f, 1.0f);
    glRotatef(day/360.0*360.0, 0.0f, 0.0f, -1.0f);
    glTranslatef(150000000, 0.0f, 0.0f);
    glutSolidSphere(15945000, 20, 20);
    // 绘制黄色的“月亮”
    glColor3f(1.0f, 1.0f, 0.0f);
    glRotatef(day/30.0*360.0 - day/360.0*360.0, 0.0f, 0.0f, -1.0f);
    glTranslatef(38000000, 0.0f, 0.0f);
    glutSolidSphere(4345000, 20, 20);

    glFlush();
    glutSwapBuffers();
}

void myIdle(void)
{
    ++day;
    if( day >= 360 )
        day = 0;
    myDisplay();
}

int main(int argc, char *argv[])
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(400, 400);
    glutCreateWindow("太阳,地球和月亮");
    glutDisplayFunc(&myDisplay);
    glutIdleFunc(&myIdle);
    glutMainLoop();
    return 0;
}


=====================未完,请勿跟帖=====================

  最后修改于2006-10-8 18:58:00

作者:eastcowboy

专家分:22560

级别:113级别:113级别:113级别:113级别:113级别:113级别:113级别:113

发表时间:2006-10-8 18:49:00    [回复]  [引用]
8楼
小结:
OpenGL动画和传统意义上的动画相似,都是把画面一幅一幅的呈现在观众面前。一旦画面变换的速度快了,观众就会认为画面是连续的。
双缓冲技术是一种在计算机图形中普遍采用的技术,绝大多数OpenGL实现都支持双缓冲技术。
通常都是利用CPU空闲的时候绘制动画,但也可以有其它的选择。
介绍了垂直同步的相关知识。
介绍了一种简单的计算帧速(FPS)的方法。
最后,我们列出了一份完整的天体动画程序清单。

=====================   第六课 完   =====================
=====================TO BE CONTINUED=====================

 

作者:天边蓝

专家分:1810

级别:10级别:10级别:10级别:10级别:10

发表时间:2006-10-8 20:25:00    [回复]  [引用]
9楼
up!

 

作者:overfly

专家分:3230

级别:17级别:17级别:17级别:17

发表时间:2006-10-8 22:17:00    [回复]  [引用]
10楼

 

作者:sarrow

专家分:35450

级别:178级别:178级别:178级别:178级别:178

发表时间:2006-10-8 22:33:00    [回复]  [引用]
11楼
Up!

 

作者:godtd

专家分:0

级别:1

发表时间:2006-10-15 13:20:00    [回复]  [引用]
12楼
支持

 

作者:xylgg

专家分:800

级别:5级别:5级别:5级别:5

发表时间:2006-10-15 20:41:00    [回复]  [引用]
13楼
我以前的毕业设计作的就是基于OPENGL的三维地理模型的建立,
用的也是标准C语言,

但是,后来发现,
     假如能够加入面向对向的概念,这个地理模型将更加的实用,
   因此,我现在狂学C++.我想问一下,

  哪里有C++写的OPENGL代码.

 

作者:yyykason

专家分:450

级别:3级别:3

发表时间:2006-10-23 10:51:00    [回复]  [引用]
14楼
非常不错!果真是精品!!!学习中!期待第7课!!!

 

作者:yangquan3

专家分:0

级别:1

发表时间:2006-10-29 14:01:00    [回复]  [引用]
15楼

您好帮我吗?

 

作者:freeeerf

专家分:5440

级别:28级别:28级别:28

发表时间:2006-10-31 19:42:00    [回复]  [引用]
16楼
我想问一下:既然是
    typedef float GLfloat;
那么用GLfloat的时候何不直接就用float呢,它毕竟是我们用熟悉了的,编译器在编译的时候是先认得GLfloat还是先得float呢?既然GLfloat是typedef出来的,那么编译器似乎应该先认得float,这样两个方面还讲,用float都比GLfloat好一点.诸位认为呢?

 

作者:freeeerf

专家分:5440

级别:28级别:28级别:28

发表时间:2006-10-31 19:57:00    [回复]  [引用]
17楼
期待eastcowboy的指点中...............................

 

作者:eastcowboy

专家分:22560

级别:113级别:113级别:113级别:113级别:113级别:113级别:113级别:113

发表时间:2006-10-31 23:27:00    [回复]  [引用]
18楼
OpenGL定义了自己的一套变量类型。如GLint,GLuint等。
虽然在目前的绝大多数系统中,这些类型的等效C语言类型与我们所想象的相同,但这并不表示今后一直都是这样。例如,在64位系统中,一个int可能是8个字节的大小,但根据OpenGL的规定,一个GLint是4字节。这一差异可能引起性能或其它方面的问题。
定义这些GL类型是为了减少在代码移植时不必要的麻烦。

 

作者:freeeerf

专家分:5440

级别:28级别:28级别:28

发表时间:2006-11-1 12:15:00    [回复]  [引用]
19楼
噢~~~~~~懂了.
就是,将来int变了的时候,只要改变#define int GLint; 中间的那个int 就行了!

 

作者:丁香奶茶

专家分:1460

级别:8级别:8级别:8

发表时间:2006-11-4 11:08:00    [回复]  [引用]
20楼
我顶!
学习中!
相见恨晚啊!!

 

[首页] [上页] [下页] [尾页]     共有 40 回帖 当前第 1 页(共2页 20帖/页)     跳转至第
回 帖 快速回帖 发 新 帖 刷新版面

版主管理:  删除此帖   转贴   置顶   取消精华   强制结帖   >>>进入管理页面