在VS2010下创建一个OpenGL的程序框架

因为做虚拟现实,图形学的基本知识和OpenGL的编程都要熟悉。图形学这些理论的还好,但编程的具体问题就比较麻烦了。最近半个月都在看OpenGL相关的东西,买了第8版红宝书《OpenGL编程指南》,看了前两张,熟悉了Buffer的用法,以及OpenGL到Shader中的参数传递。其实还没怎么看熟,主要是用的少。

在网上看了一个UCB的图形学导论的学堂公开课,一共0-3四份作业,前两份都还好做些。第三份作业就有些麻烦了,不像作业0截一下图,或者作业1有了理论基础后填补空缺代码就可以;作业3涉及到了具体的实现细节,所以最近看书,在网上找资料,总算在VS2010上搭起了一个最最基本的OpenGL运行框架。


其中一共用到了OpenGL,GLUT和、Glew和GLM这四个与OpenGL有关的库和一个FreeImage的库(FreeImage仅用来截图)

使用OpenGL之前建议升级显卡驱动到最新版,这样使用的OpenGL版本就是最新的。

配置的时候可以参考这个链接OpenGL基本配置

OpenGL是核心库,可以做一些图形运算和绘制操作。因为OpenGL是跨平台的,所以没有固定的输入输出操作,不能和任一种GDI扯上关系。所以就有了下面的GLUT库。

GLUT相当于一个跨平台的虚拟层,作为OpenGL和程序员之间的接口,为程序员提供输入输出的环境,配置各种各样的回调函数,使程序员用简单的几行/十几行/几十行代码就可以实现键盘、鼠标响应和窗口重绘等操作。

GLM库主要实现了一些向量和矩阵的类和他们之间的运算,使我们使用起来更加方便。

GLEW库是为了OpenGL拓展用的。简单来说,GLEW可以提供最新的OpenGL版本中的函数,让你的机器即使没有或不能安装OpenGL的最新驱动,仍然可以使用最新版本的OpenGL的函数。但仅是提供这样的借口和软件实现,性能上当然会比真正的OpenGL核心函数要差一些。


其实还有glu库,是OpenGL的一个实用库,它的功能主要是封装了一些OpenGL中常用的函数,让程序员调用起来更方便。比如gluLookAt()函数,如果用OpenGL的核心库(就是以gl开头的函数)的话,要写好几行代码来定义ModelViewMatrix,而经过glu封装之后,只要一个gluLookAt函数就够了。

但我没有用glu库,因为最开始学习时,希望了解OpenGL更多细节上的东西。


在VS2010中配置好之后,就可以建立第一个OpenGL程序了。


然而... 老式的OpenGL程序比较直白,画点,画圆什么的,基本上看着函数名就明白是怎么回事。但现在的OpenGL为了提高效率和程序员的自主性,代码的风格和以前很不一样。所以第一次接触OpenGL的程序还是很头疼的。

像我看红宝书的第一个程序,在平面上画一个三角形,都用了将近200行代码(100行是书上列出来的主要绘制代码,还有100行LoadShader的相关函数)


然而,这段可以略过了,因为我现在已经可以写出一个新式OpenGL的基本框架了。

记录一下我建立这个框架的足迹吧。毕竟写了500行代码只画出了一个茶壶 =,= 请允许我做一个悲伤的表情。


代码很多,一次写不完。

在VS2010的工程中,自己添加的代码文件有:

variables.h // 进行全局变量的定义及声明

functions.h // 进行全局函数的定义及声明

Transform.h // Transform类声明

main.cpp // 主函数

init.cpp // 初始化的函数

shader.cpp // 加载shader的函数

display.cpp // 显示函数

keyboard.cpp // 键盘响应

mouse.cpp // 鼠标响应

screenshot.cpp // 截屏

Transform.cpp // Transform类实现

vertex.glsl // 顶点着色器

fragment.glsl // 片元着色器


先贴出两个头文件,variables.h和functions.h,这两个文件主要存放了全局的变量和函数,以供不同代码文件共享。

variables.h:

#ifndef VARIABLES_H
#define VARIABLES_H

#include <glm/glm.hpp>

// 对不需要初始化的变量,用EXTERN修饰
// 使其具有“主函数文件中定义,其余文件中声明”的作用
#ifdef MAINPROGRAM
#define EXTERN
#else
#define EXTERN extern
#endif

// 对需要初始化的变量,在主函数文件中定义,其余文件中声明
#ifdef MAINPROGRAM
extern int MainWindow = 0;
extern int WindowWidth = 450;
extern int WindowHeight = 450;
extern GLint EnableLighting = 1;
#else
extern int MainWindow;
extern int WindowWidth;
extern int WindowHeight;
extern GLint EnableLighting;
#endif

// 常量正常定义即可
const int WindowPosX = 0;
const int WindowPosY = 0;
const float DegreeGapInit = 5.0f;
const glm::vec3 Center(0.0f, 0.0f, 0.0f);
const glm::vec3 EyeInit(0.0f, 0.0f,5.0f);
const glm::vec3 UpInit(0.0f, 1.0f, 0.0f);

EXTERN glm::vec3 Eye;
EXTERN glm::vec3 Up;
EXTERN float DegreeGap;
EXTERN GLint EnableLightingLoc;
EXTERN GLint ModelViewMatrixLoc;
EXTERN GLint PorjectionMatrixLoc;
EXTERN GLint Light0PositionLoc;
EXTERN GLint Light1PositionLoc;
EXTERN GLint Light0ColorLoc;
EXTERN GLint Light1ColorLoc;
EXTERN GLint AmbientLoc;
EXTERN GLint DiffuseLoc;
EXTERN GLint SpecularLoc;
EXTERN GLint ShininessLoc;

#endif

对const变量,正常定义在variables.h中并初始化即可,这样在每个包含variables.h的文件中都有同名但独立的const变量。

对暂不需要初始化的全局变量,需要在一个文件中定义,并在其他所有使用它的文件中声明。这里采用了一个宏定义EXTERN,当在main.cpp中定义了MAINPROGRAM时,EXTERN便定义为空。而在其他文件中,没有定义MAINPROGRAM,EXTERN便被替换为extern,。此时相当于在main.cpp中进行了定义,而其他包含variables.h的文件中进行了声明。

对需要初始化的全局变量,用预处理#ifdef直接分两种情况(MAINPROGRAM和非MAINPROGRAM)进行声明/定义并初始化。


functions.h:

#ifndef FUNCTIONS_H
#define FUNCTIONS_H

#include <iostream>
using std::string;

void init();
void display();
void idle();
void keyboard(unsigned char key, int x, int y);
void specialKeyboard(int key, int x, int y);
void mouse(int button, int state, int x, int y);
void mouseMove(int x, int y);
void quitProgram(int quitStatus);

GLuint initShader(const GLenum shaderType, const char *shaderPath);
GLuint initProgram(GLuint vertexShader, GLuint fragmentShader);

void saveScreenShot(string fname);
void quitProgram();


#endif

这里就没什么好说的了。函数声明。


main.cpp:

#include <iostream>
#include <string>
using std::cout;
using std::cerr;
using std::endl;
using std::string;

#include <GL/glew.h>
#include <GL/glut.h>
#include <FreeImage.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#define MAINPROGRAM
#include "Transform.h"
#include "variables.h"
#include "functions.h"
#undef MAINPROGRAM


void reshape(int w, int h)
{
	WindowWidth = w;
	WindowHeight = h;
	// set render area
	glViewport(0,0,w,h);
	// set projection matrix
	glMatrixMode(GL_PROJECTION);
	glm::mat4 projectionMatrix = Transform::perspective(90.0f, (float)w / (float)h, 0.1f, 99.0f);
	glLoadMatrixf(&projectionMatrix[0][0]);
}


void quitProgram(int quitStatus)
{
	glutDestroyWindow(MainWindow);
	FreeImage_DeInitialise();
	exit(quitStatus);
}

int main(int argc, char *argv[])
{
	// 初始化FreeImage
	FreeImage_Initialise();
	// 初始化glut
	glutInit(&argc, argv);
	// 设置glut显示方式
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
	// 创建窗口
	string windowName = argv[0];
	windowName = windowName.substr(windowName.find_last_of("/\\") + 1, windowName.length() - windowName.find_last_of("/\\") - 1);
	// 窗口创建方式1
	{
		glutInitWindowPosition(WindowPosX, WindowPosY);
		glutInitWindowSize(WindowWidth, WindowHeight);
		MainWindow = glutCreateWindow(windowName.c_str());
	}
	// 窗口创建方式2
	/*{
		MainWindow = glutCreateWindow(windowName.c_str());
		glutPositionWindow(WindowPosX, WindowPosY);
		glutReshapeWindow(WindowWidth, WindowHeight);
	}*/
	// 初始化Glew
	GLenum err = glewInit();
	if(GLEW_OK != err)
	{
		cerr << "Error: " << glewGetString(err) << endl;
	}
	// 绑定显示函数
	glutDisplayFunc(display);
	// 绑定空闲函数
	glutIdleFunc(idle);
	// 绑定键盘响应函数
	glutKeyboardFunc(keyboard);
	// 绑定键盘Special响应函数
	glutSpecialFunc(specialKeyboard);
	// 绑定鼠标响应函数
	glutMouseFunc(mouse);
	// 绑定鼠标移动函数
	glutMotionFunc(mouseMove);
	// 绑定Reshape函数
	glutReshapeFunc(reshape);
	
	// 初始化
	init();

	// 开始主循环
	glutMainLoop();

	// never come here
	glutDestroyWindow(MainWindow);
	FreeImage_DeInitialise();

	return 0;
}


可以看到,main.cpp主要由main函数组成。其余的还有reshape()和quitProgram()这两个函数,因为这两个函数体量小,而且和程序的主要控制流也相关,所以保留在main.cpp中。

先讲main函数。 main函数主要干了三件事。第一,初始化FreeImage库、Glut库和Glew库;第二,初始化并创建一个窗口用来显示图像;第三,绑定各种回调函数用来处理键盘、鼠标等的事件响应。

在做完这些初始化动作之后,main函数调用glutMainLoop(),开始了无限的循环。并在这样的循环中显示图片与响应事件。因为是无限循环,所以glutMainLoop()函数后面的两行是永远不会到达的。写出来只是表达在形式上进行了资源释放。


简单说一下这几个回调函数。

键盘鼠标的就不说了,一看就明白,强调一下glutSpecialFunc(),用来绑定特殊按键的回调函数,特殊按键就是指小键盘的上下左右、F1-F12,INSERT等这样的功能键。glutDisplayFunc()函数是用来显示图片的,是最重要的函数;glutIdleFunc()用来绑定当CPU空闲时的工作函数;glutReshapeFunc()用来绑定当用户改变了窗口的大小时的响应函数。


quitProgram是我自己定义的一个函数,当按ESC键的时候,会激活keyboard函数,此时调用quitProgram,释放资源,退出程序。

reshape函数体现在还不能讲,大致的意思就是,使窗口中显示的内容要怎样随窗体大小变化而跟随变化。就比方说你拉长了窗口,那么里面的物体是也相应地拉长呢,还是保持原有比例呢。就酱。


明天再放其他的代码文件 =,= 好累,累觉不爱了。



待续...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值