前言
在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;
}
工程源码下载地址