精华内容
下载资源
问答
  • opengl动画效果源代码

    2009-05-20 13:19:02
    5GD框架,有源码吗,有exe,可以编译,效果很真实。
  • OPENGL动画

    2014-01-03 15:53:46
    OPENGL动画效果,3d效果,动画
  • OpenGL动画

    千次阅读 2019-09-24 13:30:14
    今天要讲的是动画制作——可能是各位都很喜欢的。除了讲授知识外,我们还会让昨天那个“太阳、地球和月亮”天体图画动起来。缓和一下枯燥的气氛。 本次课程,我们将进入激动人心的计算机动画世界。 想必大家都知道...

    本文依然属转载。

    终于到本博主想要学习的核心内容了,顿时精神抖擞。


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

    本次课程,我们将进入激动人心的计算机动画世界。

    想必大家都知道电影和动画的工作原理吧?是的,快速的把看似连续的画面一幅幅的呈现在人们面前。一旦每秒钟呈现的画面超过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();
    }

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

    2、实现连续动画
    似乎没有任何疑问,我们应该把绘制动画的代码写成下面这个样子:

    for(i=0; i<n; ++i)
    {
          DrawScene(i);
          glutSwapBuffers();
          Wait();
    }

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

    到现在,我们已经可以初步开始制作动画了。好的,就拿上次那个“太阳、地球和月亮”的程序开刀,让地球和月亮自己动起来。

    Code:

    #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;
    }

    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
    如果绘制一幅图画的时间更长,则下降的趋势就是阶梯状的:60FPS30FPS20FPS,……(60/160/260/3,……)
    如 果每一幅图画的复杂程度是不一致的,且绘制它们需要的时间都在1/60上下。则在1/60时间内画完时,帧速为60FPS,在1/60时间未完成时,帧速 为30FPS,这就造成了帧速的跳动。这是很麻烦的事情,需要避免它——要么想办法简化每一画面的绘制时间,要么都延迟一小段时间,以作到统一。

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

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


    4、计算帧速
    不知道大家玩过3D Mark这个软件没有,它可以运行各种场景,测出帧速,并且为你的系统给出评分。这里我也介绍一个计算帧速的方法。
    根据定义,帧速就是一秒钟内播放的画面数目(FPS)。我们可以先测量绘制两幅画面之间时间t,然后求它的倒数即可。假如t=0.05s,则FPS的值就是1/0.05=20
    理论上是如此了,可是如何得到这个时间呢?通常C语言的time函数精确度一般只到一秒,肯定是不行了。clock函数也就到十毫秒左右,还是有点不够。因为FPS60FPS100的时候,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);

    最后的一步,也被我们解决了——虽然做法不太雅观,没关系,以后我们还会改善它的。


    时间过得太久,每次给的程序都只是一小段,一些朋友难免会出问题。
    现在,我给出一个比较完整的程序,供大家参考。

     

    #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;
    }

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

     

     

    展开全文
  • 本程序主要通过不断改变视口位置来模仿动画效果,同时介绍了在进行opengl绘图时所做的一些不可或缺的准备工作
  • z.h-android openGL 翻折动画效果
  • OpenGL ES实现动画效果

    2012-02-29 10:41:16
    实现动画效果ES2Renderer,PaintingView.m
  • OpenGL编写3D动画效果

    2010-05-01 12:31:38
    不错的资料 对OPENGL 3D编程有所帮助 可以下载下来学习一下
  • OpenGL动画演示VC代码

    2008-11-21 21:40:16
    基于VC平台,OpenGL动画演示源代码,魔幻效果
  • opengl实现的 Olympic
  • 使用opengl生成简单的动画效果。这是本章的最后一个实例,接下来学习opengl的基本绘图。
  • opengl动画源代码

    2009-05-20 13:20:08
    和上一个动画效果不一样,这个主要趋势与电影效果,有源码,有exe
  • openGL特效动画集合

    2017-07-02 14:52:22
    openGL特效动画包,包含7个动画的工程文件,其中包含水波流动,粒子特效,魔幻特效等效果动画
  • Android用opengl实现各种逼真折纸效果.rar,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。
  • openGL动画效果(三)

    千次阅读 2013-12-28 14:41:13
    // Move.cpp // Move a Block based on arrow key movements ...#include // OpenGL toolkit #include #ifdef __APPLE__ #include #else #define FREEGLUT_STATIC #include #endif GLBatch squareBatch; GLShade
    // Move.cpp
    // Move a Block based on arrow key movements
    
    #include <GLTools.h>	// OpenGL toolkit
    #include <GLShaderManager.h>
    
    #ifdef __APPLE__
    #include <glut/glut.h>
    #else
    #define FREEGLUT_STATIC
    #include <GL/glut.h>
    #endif
    
    GLBatch	squareBatch;
    GLShaderManager	shaderManager;
    
    
    GLfloat blockSize = 0.1f;
    GLfloat vVerts[] = { -blockSize, -blockSize, 0.0f, 
    	                  blockSize, -blockSize, 0.0f,
    					  blockSize,  blockSize, 0.0f,
    					 -blockSize,  blockSize, 0.0f};
    
    ///
    // This function does any needed initialization on the rendering context. 
    // This is the first opportunity to do any OpenGL related tasks.
    void SetupRC()
    	{
    	// Black background
    	glClearColor(0.0f, 0.0f, 1.0f, 1.0f );
        
    	shaderManager.InitializeStockShaders();
    
    	// Load up a triangle
    	squareBatch.Begin(GL_TRIANGLE_FAN, 4);
    	squareBatch.CopyVertexData3f(vVerts);
    	squareBatch.End();
    	}
    
    // Respond to arrow keys by moving the camera frame of reference
    void SpecialKeys(int key, int x, int y)
        {
    	GLfloat stepSize = 0.025f;
    
    	GLfloat blockX = vVerts[0];   // Upper left X
    	GLfloat blockY = vVerts[7];  // Upper left Y
    
    	if(key == GLUT_KEY_UP)
    		blockY += stepSize;
    
    	if(key == GLUT_KEY_DOWN)
    		blockY -= stepSize;
    	
    	if(key == GLUT_KEY_LEFT)
    		blockX -= stepSize;
    
    	if(key == GLUT_KEY_RIGHT)
    		blockX += stepSize;
    
    	// Collision detection
    	if(blockX < -1.0f) blockX = -1.0f;
    	if(blockX > (1.0f - blockSize * 2)) blockX = 1.0f - blockSize * 2;;
    	if(blockY < -1.0f + blockSize * 2)  blockY = -1.0f + blockSize * 2;
    	if(blockY > 1.0f) blockY = 1.0f;
    
    	// Recalculate vertex positions
    	vVerts[0] = blockX;
    	vVerts[1] = blockY - blockSize*2;
    	
    	vVerts[3] = blockX + blockSize*2;
    	vVerts[4] = blockY - blockSize*2;
    	
    	vVerts[6] = blockX + blockSize*2;
    	vVerts[7] = blockY;
    
    	vVerts[9] = blockX;
    	vVerts[10] = blockY;
    
    	squareBatch.CopyVertexData3f(vVerts);
    
    	glutPostRedisplay();
    	}
    
    
    
    
    
    ///
    // Called to draw scene
    void RenderScene(void)
    	{
    	// Clear the window with current clearing color
    	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    	GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    	shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
    	squareBatch.Draw();
    
    	// Flush drawing commands
    	glutSwapBuffers();
    	}
    
    
    
    ///
    // Window has changed size, or has just been created. In either case, we need
    // to use the window dimensions to set the viewport and the projection matrix.
    void ChangeSize(int w, int h)
    	{
    	glViewport(0, 0, w, h);
    	}
    
    ///
    // Main entry point for GLUT based programs
    int main(int argc, char* argv[])
    	{
    	gltSetWorkingDirectory(argv[0]);
    	
    	glutInit(&argc, argv);
    	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    	glutInitWindowSize(800, 600);
    	glutCreateWindow("Move Block with Arrow Keys");
    	
    	GLenum err = glewInit();
    	if (GLEW_OK != err)
    		{
    		// Problem: glewInit failed, something is seriously wrong.
    		fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
    		return 1;
    		}
    	
    	glutReshapeFunc(ChangeSize);
    	glutDisplayFunc(RenderScene);
        glutSpecialFunc(SpecialKeys);
    
    	SetupRC();
    
    	glutMainLoop();
    	return 0;
    	}
    

    展开全文
  • OpenGL实现多边形扫描转换的扫描线算法,带动画效果 实验作业,LAB3. 绘制的是五边形。
  • OriSim3D-Android--opengl 实现各种逼真折纸效果.zip,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。
  • 本文主要讨论两个骨骼动画过渡时的混合效果。 切换动作效果演示 在游戏中,动画往往被切分成多个片段(clip),通过组合拼接来构建最终的表现效果。为了确保切换动作时的平滑过渡,使得整体动作更加流畅,需要对...

            本文主要讨论两个骨骼动画过渡时的混合效果。

    切换动作效果演示

            在游戏中,动画往往被切分成多个片段(clip),通过组合拼接来构建最终的表现效果。为了确保切换动作时的平滑过渡,使得整体动作更加流畅,需要对前后动作通过插值进行混合。

            概念引入

            在讨论具体的混合计算之前,我想依然有必要明确一下整个动画系统体系的一些细节。

            其中一个需要探讨的问题在于:动画运行时应该维护哪些数据,并以怎样的形式换算进入最终的渲染流程。在原先的骨骼动画demo中,我实际上是按帧存储了每帧每个骨骼的蒙皮矩阵,作为骨骼动画演示已经绰绰有余了。

            但对于实际应用而言,这里起码存在了两个问题:一是逐帧的动画数据一般是烘焙的结果,在dcc工具中动画数据一般为关键帧+曲线类型存储,实时插值本身并不耗时,并且可以压缩带宽,所以动画往往都会进行压缩存储;

            二是我们除了播放动画外,有时候还需要对动作做一些后处理——比如本篇文章讨论的动画混合,又比如反向动力学效果,此时仅有蒙皮矩阵是不利于运算的,我们应该根据自己的实际需求,存储为局部变换矩阵或全局(模型空间)变换矩阵。特别地,如果存储为局部变换矩阵,在解算的过程中存在不断查找父节点连乘矩阵的步骤,此处合理地安排骨骼的排序可以避免重复的运算。

            另外一个需要考虑的问题是,混合是否应该影响到动作的播放长度?我们假设动作A长度为ta帧,B为tb帧,混合帧数为tc,那么动作A,B混合后,总帧长应该为ta+tb,还是ta+tb+tc,又或是介于两者之间?

            针对这一问题,我认为比较友好的设计为:混合时间不应该影响原播放长度,混合这一功能本身仅仅是为了更好的表现效果,它不应当破坏原有的体系。那么此处就必然有一个动作“牺牲”一部分姿态。一个比较常见的思路是让目标动作的前tc帧转换为过渡帧,每一帧的矩阵与源动作最后一帧按照当前时间进行权重插值。

            具体实现

            为了更好地进行混合操作,我们可以将动画存储的数据修改为模型空间下的transform值,而不是最终的蒙皮矩阵。所谓的transform也就是分别存储平移旋转缩放,之后再根据RST进行矩阵构造:

    struct STransform
    {
        QVector3D position     = QVector3D(0, 0, 0);
        QVector3D scale        = QVector3D(1, 1, 1);
        QQuaternion rotation   = QQuaternion(0, 0, 0, 1);
    };

            在实际的插值运算中,我们也是针对每个骨骼模型空间的平移、旋转、缩放分别进行插值(由于项目中骨骼不存在缩放,实际的工程中并没有计算缩放插值)

            在切换到新动作,当我们检测到新动作需要混合时,我们根据当前动作localtime,采样动作的transform值,作为缓存:

    
    void CAnimationEngine::PlayAnimation(Object* obj, const string& path)
    {
        if(m_animators.find(path) == m_animators.end())
        {
            return;
        }
        int frame = -1;
        string oldPath;
        // check need blend, save cache pose
        if(m_events.find(obj) != m_events.end() && m_animators.find(m_events[obj].m_path) != m_animators.end())
        {
            if(g_animParam.m_nBlendFrame)
            {
                oldPath = m_events[obj].m_path;
                frame = min(m_animators[oldPath].GetFrameNum(), static_cast<int>(m_events[obj].m_time * FRAME_PER_MS));
            }
        }
        m_events[obj] = SEvent(path, g_animParam.m_bLoop, g_animParam.m_nBlendFrame, g_animParam.m_eBlendCurve, g_animParam.m_fSpeed);
        if(!oldPath.empty())
        {
            CAnimator& animator = m_animators[oldPath];
            m_events[obj].m_cachePose = animator.GetTransform(frame);
        }
    }

            接下来,在更新骨骼动画的代码中,我们对当前帧数下的采样动作和缓存动作的平移、旋转分别进行插值,混合权重以时间t单位,包含线性混合(t),以及非线性混合(3 * t * t - 2 * t * t)。

            插值结束后,我们重新构造模型空间的全局变换矩阵,并乘以绑定矩阵逆矩阵构造蒙皮矩阵,传递给着色器。

    bool CAnimationEngine::UpdateAnimation(Object* obj, QOpenGLShaderProgram* program)
    {
        // ...
    
        if (event.m_blendFrame > 0 && frame <= event.m_blendFrame && event.m_cachePose.size() > 0)
        {
            vector<QMatrix4x4> final;
            float ratio = static_cast<float>(frame + 1) / (event.m_blendFrame + 1);
            if (event.m_eBlendCurve == EBlendCurve::Smooth)
            {
                ratio = ratio * ratio * (-2 * ratio + 3);
            }
    
            for(int i = 0;i < size; i++)
            {
                STransform& transform = animator.GetTransform(frame, i);
    
                QQuaternion quat = QQuaternion::slerp(event.m_cachePose[i].rotation, transform.rotation, ratio);
                QVector3D trans = Lerp(event.m_cachePose[i].position, transform.position, ratio);
    
                QMatrix4x4& invBindPose = CAnimationEngine::Inst()->GetBone(i)->m_invBindPose;
                QMatrix4x4 mat;
                mat.translate(trans);
                mat.rotate(quat);
                mat = mat * invBindPose;
    
                final.push_back(mat);
            }
            program->setUniformValueArray(location,final.data(), size);
        }
        else
        {
            // ...
        }
    
    
        // ...
        return true;
    }

     

    展开全文
  • OpenGL 动画的制作

    2012-12-22 17:33:55
    本次课程,我们将进入激动人心的计算机动画世界。...但对于计算机来说,它可以播放更多的画面,以达到更平滑的效果。如果速度过慢,画面不够平滑。如果速度过快,则人眼未必就能反应得过来。对于一个正常人来说,

    本次课程,我们将进入激动人心的计算机动画世界。

    想必大家都知道电影和动画的工作原理吧?是的,快速的把看似连续的画面一幅幅的呈现在人们面前。一旦每秒钟呈现的画面超过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();
    }
    1、双缓冲技术
    在计算机上的动画与实际的动画有些不同:实际的动画都是先画好了,播放的时候直接拿出来显示就行。计算机动画则是画一张,就拿出来一张,再画下一张,再拿出来。如果所需要绘制的图形很简单,那么这样也没什么问题。但一旦图形比较复杂,绘制需要的时间较长,问题就会变得突出。
    让我们把计算机想象成一个画图比较快的人,假如他直接在屏幕上画图,而图形比较复杂,则有可能在他只画了某幅图的一半的时候就被观众看到。而后面虽然他把画补全了,但观众的眼睛却又没有反应过来,还停留在原来那个残缺的画面上。也就是说,有时候观众看到完整的图象,有时却又只看到残缺的图象,这样就造成了屏幕的闪烁。
    如何解决这一问题呢?我们设想有两块画板,画图的人在旁边画,画好以后把他手里的画板与挂在屏幕上的画板相交换。这样以来,观众就不会看到残缺的画了。这一技术被应用到计算机图形中,称为双缓冲技术。即:在存储器(很有可能是显存)中开辟两块区域,一块作为发送到显示器的数据,一块作为绘画的区域,在适当的时候交换它们。由于交换两块内存区域实际上只需要交换两个指针,这一方法效率非常高,所以被广泛的采用。
    注意:虽然绝大多数平台都支持双缓冲技术,但这一技术并不是OpenGL标准中的内容。OpenGL为了保证更好的可移植性,允许在实现时不使用双缓冲技术。当然,我们常用的PC都是支持双缓冲技术的。
    要启动双缓冲功能,最简单的办法就是使用GLUT工具包。我们以前在main函数里面写:
    glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
    其中GLUT_SINGLE表示单缓冲,如果改成GLUT_DOUBLE就是双缓冲了。
    当然还有需要更改的地方——每次绘制完成时,我们需要交换两个缓冲区,把绘制好的信息用于屏幕显示(否则无论怎么绘制,还是什么都看不到)。如果使用GLUT工具包,也可以很轻松的完成这一工作,只要在绘制完成时简单的调用glutSwapBuffers函数就可以了。
    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还提供了一些别的函数,例如“在键盘按下时做某事”等。
    到现在,我们已经可以初步开始制作动画了。好的,就拿上次那个“太阳、地球和月亮”的程序开刀,让地球和月亮自己动起来。

    #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;
    }

    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关系也不太大。这里就不做介绍了。
    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);
    最后的一步,也被我们解决了——虽然做法不太雅观,没关系,以后我们还会改善它的。
    现在,我给出一个比较完整的程序,供大家参考。

    #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;
    }

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

    展开全文
  • 本程序主要通过不断改变视口位置来模仿动画效果,同时介绍了在进行opengl绘图时所做的一些不可或缺的准备工作。一、首先新建一个 “Win32 Application” 的空工程。二、然后将 “glu32.lib glaux.lib opengl32.lib”...
  • OpenGL代码实现动画

    2012-06-11 23:57:59
    动画 动画 动画动画OpenGL代码实现动画
  • 加载出来的scene场景里bones为null,但是动画信息都有,应该是帧动画不是骨骼动画,想问下这种情况怎么把动画信息提取出来啊,我是照着learnOpenGL-cn里的教程把静态的模型加载出来了,现在帧动画的信息没加载出来,...
  • 利用glut生成简单的动画效果。 最近学习中遇到的几点困惑: 1.OpenGL到底是什么? 以SGI的GL三维图形库为基础制定的一个通用共享的开放式三维图形标准。定义上来看,OpenGL只是一个标准。 2.OpenGL如何工作? 3....
  • [OpenGL]用OpenGL制作动画

    千次阅读 2014-01-05 23:09:08
    /*我们通常还可以用OpenGL程序创建动画效果,这里我们利用前面的例子, 绘制正方形,并使这个正方形在窗口的边框反弹。 这里需要创建一个循环,在每次调用显示回调函数之前改变正方形的位置, 使其看起来像在窗口中...
  • Opengl es 精灵动画 实现旋转放大的地球动画
  • 利用OpenGL基于vs2012的c++实现模拟太阳系的动画效果
  • opengl实现角色动画

    热门讨论 2011-11-27 15:31:26
    本Demo用OpenGL实现渲染的例子,技术包括模型渲染,角色动画,GLSL, 顶点着色器,片段着色器,还有体积阴影。是学习opengl渲染不可多得的资料。实现的效果非常好,拿出来和大家分享。
  • 第四个程序opengl简单的动画效果

    千次阅读 2013-11-17 22:24:12
    先上代码,稍后再解释; // lesson6.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include #include #include static int day = 200; double CalFrequency() ... static clock

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 19,827
精华内容 7,930
关键字:

opengl动画效果