openGL控制FPS (每秒传输的帧数)

前言

           在openGL中渲染场景,经常用到单缓冲、双缓冲技术,提高或者降低FPS,以达到某种特效。比方说:

          场景1:场景中有一团烟雾,在微风的情况下,烟雾袅袅升起,随风慢慢飘摇

          场景2:场景中有一团烟雾,在大风的情况下,烟雾喷涌而出,随风瞬间飘散

如果这两种情况都用同样的FPS,效果就是一样的,无法区分。所有我们需要控制FPS

详解

   1.现象

GLUT_SINGLE  指定单缓存窗口

GLUT_DOUBLE  指定双缓存窗口

 应用程序使用单缓冲绘图时可能会存在图像闪烁的问题。 这是因为生成的图像不是一下子被绘制出来的,而是按照从左到右,由上而下逐像素地绘制而成的。最终图像不是在瞬间显示给用户,而是通过一步一步生成的,这会导致渲染的结果很不真实。为了规避这些问题,我们应用双缓冲渲染窗口应用程序。缓冲保存着最终输出的图像,它会在屏幕上显示;而所有的的渲染指令都会在缓冲上绘制。当所有的渲染指令执行完毕后,我们交换(Swap)前缓冲和后缓冲,这样图像就立即呈显出来,之前提到的不真实感就消除了。

2、原理

GLUT_SINGLE单缓冲,屏幕显示调用glFlush(),将图像在当前显示缓存中直接渲染,会有图形跳动(闪烁)问题

GLUT_DOUBLE双缓冲,屏幕显示调用glutSwapBuffers(),将图像先绘制在另外的缓存中,渲染完毕之后,将其整个缓存贴到当前的窗口,能消除闪烁,一般动画要用双缓冲.

如果缓冲与函数不对应的话,则会出错。

 所谓双缓冲技术,是指两个缓冲区:前台缓冲和后台缓冲。前台缓冲即我们看到的屏幕,

后台缓冲则在内存当中,对我们来说是不可见的。每次我们绘图都在后台缓冲中进行的,

当绘图完成时,就必须把绘制的最终结果复制到屏幕上。在opengl中glutSwapBuffers函数就

可以实现双缓冲技术的一个重要函数。该函数的作用就是交换两个缓冲区的指针,从而把绘制

结果图复制到屏幕上,从而使用户可见。否则在后台缓冲中,使得绘图结果不可见。

那么在程序中怎么使用双缓冲呢?

       一般在main函数中开启双缓冲,主要是调用glutInitDisplayMode函数,该函数的功能是设

置初始显示模式。函数原型:void glutInitDisplayMode(unsigned int mode);里面的

mode可以取一下值或其组合:


其中里面就有一个双缓存窗口。 

开启双缓冲之后就要用函数glutSwapBuffers()函数以及来交换两个缓冲区 

指针。此函数一般用于绘制操作完成后。在main函数中用glutDisplayFunc

函数注册一个绘图函数就可以调用绘图函数,从而就可以调用双缓冲交换函数。

编码

         使用glutSwapBuffers()函数可以实现双缓冲效果,但是并不能控制FPS,依然不能满足我们的需求,这时就需要用到glutPostRedisplay();和glutIdleFunc(idelFunc);以及glutTimerFunc(16, OnTimer, 1);

先看下烟雾效果 

 

 

//添加定时器
	glutTimerFunc(16, OnTimer, 1);
    glutIdleFunc(idelFunc);
    glutSwapBuffers();  // 交换缓存 
    glutPostRedisplay();

 主要代码

// SmokeSimulate.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
// 包含有关OpenGL函数的头文件
//#include "GL/GL.H"
//#include "GL/GLU.h"
//#include "GL/GLAUX.H"
//#include "GL/glut.h"
//#include <iostream>

//#include <iostream>
//#include <Windows.h>  
#include "tools.h"
#include "Grids.h"
#include "3dmap.h"
#include "xFreeTypeLib.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtc/type_ptr.hpp"

#define SMOKEX 32
#define SMOKEY 32

typedef struct {
	float r;
	float g;
	float b;
} COL;


GLuint mainWindow = 0;
GLuint alpha = 0;
GLuint nearPlane = -1000;
GLuint farPlane = 1920;

float deltaTime = 0.0f;
float lastFrame = 0.0f; 
const float fps = 60.0f;
float msPerFrame = 1.0f / fps;

/
bool RenderScene();
int InitGL(GLvoid);
void Fuoco(void);
void ShowSmoke(float x, float y, float z, float dim);
COL Colore(float k);
void OnTimer(int value);
void idelFunc();    //空闲函数

bool    freeze;
int     frame;
GLuint	Texture[1];
unsigned char Bsmoke[SMOKEX][SMOKEY];

int xFar = 0.0f, yFar = 0.0f, zFar = 0.0f;
int wWidth = 1366, wHeight = 768;
int oldX, oldY;
bool gIsStartTrackBall = false;
bool gIsMoveMap = false;
TrackBall trackball;
_3dMap map;

xFreeTypeLib g_FreeTypeLib;
float ratio;
//wchar_t g_UnicodeString[]=L"aaabb/x4E2D/x6587/x0031/x0032/x0033";    
const char g_UnicodeString[] = "0     1      2      3     4      5     6  时间(天)\" ";
const char g_UnicodeStringScript[] = "-10   0     10      20      30     40      50  \" ";
const char g_UnicodeStringScriptHz[] = "0       1000        2000         3000       4000        5000  频率(MHz)\" ";
const char g_UnicodeStringScriptLevel[] = "0    1000    2000    3000   4000   5000  能量电平(Db)\" ";


extern stuxCharTexture g_TexID[65536];


LPWSTR AnsiToUnicode(LPCSTR lpcstr);
void drawText(wchar_t* _strText, int x, int y, int maxW, int h);




void displayEvent()
{
	//烟雾相关
	RenderScene();
	
	//glFlush();
	glutSwapBuffers();  // 交换缓存 
}

void mouseMoveEvent(int x, int y)
{
	if (gIsStartTrackBall)
	{
		trackball.MouseMove(x, y);
		//glutPostRedisplay();
		glutSetWindow(mainWindow);
		//glutPostRedisplay();
	}
	if (gIsMoveMap)
	{
		xFar -= oldX - x;
		yFar += oldY - y;
		oldX = x;
		oldY = y;
		//glutPostRedisplay();
	}
}


void idelFunc()
{
	float currentFrame = GetTickCount() * 0.1f;
	deltaTime = currentFrame - lastFrame;
	lastFrame = currentFrame;
	//checkCollision();
	if (deltaTime > msPerFrame)
	{
		displayEvent();
	}
}

// 鼠标事件函数 
void mouseEvent(int button, int state, int x, int y)
{
	if (state == GLUT_UP && button == GLUT_WHEEL_UP)  //鼠标滚轮
	{
		zFar -= 80;
		//glutPostRedisplay();
	}
	if (state == GLUT_UP && button == GLUT_WHEEL_DOWN)
	{
		zFar += 80;
		//glutPostRedisplay();
	}


	if (button == GLUT_LEFT_BUTTON)
	{
		if (state == GLUT_DOWN)
		{
			oldX = x;
			oldY = y;
			trackball.setXY(x, y);
			gIsStartTrackBall = true;
		}
		else if (state == GLUT_UP)
		{
			oldX = x;
			oldY = y;
			gIsStartTrackBall = false;
		}
		//glutPostRedisplay();
	}
	else if (button == GLUT_RIGHT_BUTTON)
	{
		if (state == GLUT_DOWN)
		{
			oldX = x;
			oldY = y;
			gIsMoveMap = true;
		}
		else if (state == GLUT_UP)
		{
			oldX = x;
			oldY = y;
			gIsMoveMap = false;
		}
	}
}
// 窗体尺寸变化事件 
void resizeEvent(int w, int h)
{
	/
//添加窗口缩放时的图形变换函数
	glViewport(0, 0, w, h);
	/
	glMatrixMode(GL_PROJECTION);	// 选择投影矩阵
	glLoadIdentity();				// 设置投影矩阵
	// 根据窗口的比例设置变换
	gluPerspective(45.0f, (GLfloat)w / (GLfloat)h, 0.01f, 1000.0f);

	//glOrtho(0, 0, w, h, nearPlane, farPlane);

	glMatrixMode(GL_MODELVIEW);		// 选择模型矩阵
	glLoadIdentity();				// 设置模型矩阵

	gluLookAt(0.0f, 0.0f, 10.0f, 0.0f, -10.0f, 0.0f, 0.0f, 1.0f, 0.0f);    //为什么没效果

	//glutPostRedisplay();

}
void processSpecialKeys(int key, int x, int y) {
	if (key == 101)
	{
		zFar += 4;
		//glutPostRedisplay();
	}
	if (key == 103)
	{//
		zFar -= 4;
		//glutPostRedisplay();
	}
	printf("key:%d\n", key);
}

void MenuFunc(int data)
{
	switch (data)
	{
	case 1:
		map.setLineOrFill(); break;
	default:break;
	}
	//glutPostRedisplay();
}
void glInit()
{
	glShadeModel(GL_FLAT);//SMOOTH//GL_FLAT
	glClearColor(1.0f, 1.0f, 1.0f, 0.5f);
	glClearDepth(1.0f);

	glEnable(GL_NORMALIZE);

	glEnable(GL_DEPTH_TEST);
	glAlphaFunc(GL_GREATER, 0);
	glDepthFunc(GL_LEQUAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	/*glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);*/

	glEnable(GL_POINT_SMOOTH);
	glEnable(GL_LINE_SMOOTH);
	glEnable(GL_POLYGON_SMOOTH);

	glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); // Make round points, not square points
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);  // Antialias the lines
	glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);


	//glClearColor(1.0f,1.0f,1.0f,0.5f);  //窗口背景设置为白色
	glMatrixMode(GL_MODELVIEW); //设置投影参数

	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);

	//g_FreeTypeLib.load("simhei.ttf",14,14);  
	// g_FreeTypeLib.load("c://windows//fonts//simhei.ttf",14,14);  
	g_FreeTypeLib.load("c://windows//fonts//simhei.ttf", 12, 12);

	//烟雾
	InitGL();

	//glDisable(GL_CULL_FACE);

	//glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  

}

xCharTexture* getTextChar(wchar_t ch)
{
	g_FreeTypeLib.loadChar(ch);
	return &g_TexID[ch];
}

LPWSTR AnsiToUnicode(LPCSTR lpcstr)   //参数lpcstr类型也可是char*  
{
	LPWSTR Pwstr;
	int  i;
	i = MultiByteToWideChar(CP_ACP, 0, lpcstr, -1, NULL, 0);
	Pwstr = new WCHAR[i];
	MultiByteToWideChar(CP_ACP, 0, lpcstr, -1, Pwstr, i);

	return (Pwstr);
}




void drawText(wchar_t* _strText, int x, int y, int maxW, int h)
{
	int sx = x;
	int sy = y;
	int maxH = h;
	size_t nLen = wcslen(_strText);

	for (int i = 0; i < nLen; i++)
	{
		if (_strText[i] == '/n')
		{
			sx = x; sy += maxH + 12;
			continue;
		}
		xCharTexture* pCharTex = getTextChar(_strText[i]);
		glBindTexture(GL_TEXTURE_2D, pCharTex->m_texID);                          //绑定到目标纹理  
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glEnable(GL_BLEND);
		glDepthMask(GL_TRUE);//打开或关闭OpenGL的特殊功能  
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);  //特殊的像素算法  
		//glBlendFunc(GL_ONE, GL_ZERO);
		glEnable(GL_TEXTURE_2D);    //去透明
		int w = pCharTex->m_Width;
		int h = pCharTex->m_Height;

		int ch_x = sx + pCharTex->m_delta_x;
		int ch_y = sy - h - pCharTex->m_delta_y;

		if (maxH < h) maxH = h;
		glBegin(GL_QUADS);                                                    // 定义一个或一组原始的顶点  
		{
			glTexCoord2f(0.0f, 1.0f); glVertex3f(ch_x, ch_y, 1.0f);
			glTexCoord2f(1.0f, 1.0f); glVertex3f(ch_x + w, ch_y, 1.0f);
			glTexCoord2f(1.0f, 0.0f); glVertex3f(ch_x + w, ch_y + h, 1.0f);
			glTexCoord2f(0.0f, 0.0f); glVertex3f(ch_x, ch_y + h, 1.0f);
		}
		glEnd();
		sx += pCharTex->m_adv_x;
		if (sx > x + maxW)
		{
			sx = x; sy += maxH + 12;
		}
	}
}


//
//						场景绘制与渲染
//
bool RenderScene()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	frame++;
	frame = frame % 2;
	if (frame == 0)
	{
		if (freeze == false)
			Fuoco();	// 生成烟雾
	}
	glTranslatef(0.1f, 0.0f, -2.7f);
	ShowSmoke(0, 0, 0, (float)0.2);		// 显示烟雾

	//::SwapBuffers(m_pDC->GetSafeHdc());		// 交互缓冲区
	//glFlush();
	//glutPostRedisplay();
	//glutSwapBuffers();  // 交换缓存 

	/*glutSetWindow(mainWindow);
	glutPostRedisplay();*/

	//glutPostRedisplay();
	//glFlush();
	//glutSwapBuffers();  // 交换缓存 
	

	
	return true;
}


COL Colore(float k)
{
	COL c;
	//	if( k<64 )
	//	{
	//		c.r=k/64;
	//		c.g=k/64;
	//		c.b=k/64;
	//	}
	//	else
	//	{
	if (k < 128)
	{
		c.r = k / 128;
		c.g = k / 128;
		c.b = k / 128;
	}
	else
	{
		if (k < 192)
		{
			c.r = k / 256;
			c.g = k / 256;
			c.b = k / 256;
		}
		else
		{
			c.r = 1;
			c.g = 1;
			c.b = 1;
		}
	}
	//	}
	return(c);
}

void ShowSmoke(float x, float y, float z, float dim)
{
	float xi, yi;
	float ka, kb, kc, kd;
	COL col;
	int xd, yd;

	yi = y + dim * SMOKEY / 2;

	for (yd = 0; yd < SMOKEY - 1; yd++)
	{
		xi = x - dim * SMOKEX / 2;
		for (xd = 1; xd < SMOKEX - 1; xd++)
		{
			ka = (float)Bsmoke[xd][yd];
			kb = (float)Bsmoke[xd + 1][yd];
			kc = (float)Bsmoke[xd + 1][yd + 1];
			kd = (float)Bsmoke[xd][yd + 1];

			glBegin(GL_QUADS);		// 绘制四边形
			col = Colore(kd);
			glColor3f(col.r, col.g, col.b);
			glVertex3f(xi, yi, z);
			col = Colore(kc);
			glColor3f(col.r, col.g, col.b);
			glVertex3f(xi + dim, yi, z);
			col = Colore(kb);
			glColor3f(col.r, col.g, col.b);
			glVertex3f(xi + dim, yi + dim, z);
			col = Colore(ka);
			glColor3f(col.r, col.g, col.b);
			glVertex3f(xi, yi + dim, z);
			glEnd();
			xi += dim;
		}
		yi -= dim;
	}
}

void Fuoco(void)
{
	int x, y;
	int k;

	for (x = 8; x < SMOKEX - 8; x++)
		Bsmoke[x][SMOKEY - 1] = rand() % 192;

	for (x = 0; x < 5; x++)
		Bsmoke[rand() % SMOKEX][SMOKEY - 1] = 0;

	for (y = 0; y < SMOKEY - 1; y++)
	{
		for (x = 1; x < SMOKEX - 1; x++)
		{
			k = Bsmoke[x][y] + Bsmoke[x - 1][y + 1] + Bsmoke[x + 1][y + 1] + Bsmoke[x][y + 1];
			k = k / 4 - 2;
			if (k < 0)
				k = 0;
			Bsmoke[x][y] = (unsigned char)k;
		}
	}
}

int InitGL(GLvoid)
{
	glShadeModel(GL_SMOOTH);
	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
	glClearDepth(1.0f);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	for (int y = 0; y < SMOKEY; y++)
	{
		for (int x = 0; x < SMOKEX; x++)
			Bsmoke[x][y] = 0;
	}

	freeze = false;
	frame = 0;
	return TRUE;
}

void OnTimer(int value)
{
	/*alpha++;
	alpha = (alpha % 256);*/
	//displayEvent();
	glutPostRedisplay();
	glutTimerFunc(16, OnTimer, 1);
}

int main(int argc, char* argv[])
{
	//ANSI字符串,内容长度7字节     
	char sz[20] = "中文123";

	//UNICODE字符串,内容长度5个wchar_t(10字节)     
	wchar_t   wsz[100] = L"/x4E2D/x6587/x0031/x0032/x0033";
	//运行时设定当前ANSI编码,VC格式     
	setlocale(LC_ALL, ".936");

	//GCC中格式     
	setlocale(LC_ALL, "zh_CN.GBK");

	//VisualC++中使用小写%s,按照setlocale指定编码输出到文件     
	//GCC中使用大写%S     


	//把UNICODE字符串按照setlocale指定的编码转换成字节     
	wcstombs(sz, wsz, 20);
	//把字节串按照setlocale指定的编码转换成UNICODE字符串     
	mbstowcs(wsz, sz, 20);

	map.initMap();

	glutInit(&argc, argv);                                             //初始化GLUT
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULTISAMPLE);  //设置显示模式
	glutInitWindowPosition(0, 0);  //设置显示窗口的左上角位置
	glutInitWindowSize(wWidth, wHeight);         //设置窗口的长和高
	mainWindow = glutCreateWindow("3DMap");     //创造显示窗口

	glInit();                                       //开始初始化过程

	int main_version, sub_version, release_version;
	const char* version = (const char*)glGetString(GL_VERSION);
	sscanf(version, "%d.%d.%d", &main_version, &sub_version, &release_version);
	printf("OpenGL 版本:%s\n", version);
	printf("主版本号:%d\n", main_version);
	printf("次版本号:%d\n", sub_version);
	printf("发行版本号:%d\n", release_version);
	
	glutReshapeFunc(resizeEvent);   //当窗口尺寸改变时,图形比例不发生变化
	glutDisplayFunc(displayEvent);
	glutIdleFunc(idelFunc);
	glutMouseFunc(mouseEvent);
	glutSpecialFunc(processSpecialKeys);
	glutMotionFunc(mouseMoveEvent);

	//添加定时器
	glutTimerFunc(16, OnTimer, 1);
	glutCreateMenu(MenuFunc);
	glutAddMenuEntry("填充/网格", 1);
	glutAttachMenu(GLUT_MIDDLE_BUTTON);

	glutMainLoop();    //显示所有并等候

	getchar();
	return 0;
}

工程源码下载地址
    

 

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OpenGL 中,动画的流畅度通常是由速率(FPS)来控制的。速率是指每秒钟渲染的。如果速率太低,动画将会看起来不平滑,如果速率太高,那么就会浪费计算资源。 为了调节速率,你可以使用计时器来测量每一的渲染时间,并根据需要来调整下一的渲染时间。 以下是一个简单的示例代码,展示了如何使用计时器来控制速率: ``` #include <GL/glut.h> #include <ctime> // 计时器回调函 void timer(int value) { // 获取当前时间 static clock_t last_time = clock(); clock_t current_time = clock(); // 计算时间差 float elapsed_time = (current_time - last_time) / (float)CLOCKS_PER_SEC; // 设置下一的渲染时间 float target_fps = 60.0f; // 目标速率 float target_elapsed_time = 1.0f / target_fps; float sleep_time = target_elapsed_time - elapsed_time; if (sleep_time > 0) { // 等待剩余时间 glutTimerFunc((int)(sleep_time * 1000), timer, 0); } else { // 不等待,立即渲染下一 glutPostRedisplay(); glutTimerFunc(0, timer, 0); } // 更新上一次的时间 last_time = current_time; } // OpenGL 渲染函 void display() { // 渲染代码 // ... } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA); glutInitWindowSize(640, 480); glutCreateWindow("OpenGL Animation"); glutDisplayFunc(display); glutTimerFunc(0, timer, 0); glutMainLoop(); return 0; } ``` 在上面的示例中,计时器回调函 `timer` 每都会被调用一次。它首先获取当前时间,并计算与上一的时间差。然后它会根据目标速率和时间差来计算下一的渲染时间。如果时间差较大,则会等待剩余时间;否则,它将立即调用 `glutPostRedisplay` 函来触发下一的渲染,同时也会计算下一的渲染时间。 在主函中,我们使用 `glutTimerFunc` 函来注册计时器回调函。我们将计时器的初始延迟时间设置为 0,这样它会立即被调用。然后我们使用 `glutMainLoop` 函来进入 OpenGL 的主循环,这样程序就可以一直运行下去,直到用户关闭窗口。 注意,这只是一个简单的示例代码,实际上你可能需要根据你的程序需要来进行更复杂的调节。例如,你可能需要考虑动态调节目标速率,或者在渲染复杂场景时使用更高的速率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值