【OpenGL】第二篇 Hello OpenGL

-------------------------------------------------------------------------------------------------------------------------------

就像学习其他编程语言一样,为了顺利写下第一个OpenGL程序

我们必须不辞辛苦的先铺好砖块,搭建好环境……

所以接下来让我先把所需要的库的环境安置好,再开始coding。

-------------------------------------------------------------------------------------------------------------------------------

【安装环境】

本来想简单点直接用glut和glew的库包含就好,但是为了配合该书的例子还是决定用OpenGL编程指南第八版的环境来配置。

关于该配置,我发现了一个前辈已经把详细过程写在他的csdn博客上,大家可以去看看,非常简单,里面有源码的下载地址。

http://blog.csdn.net/qq821869798/article/details/45247241

另外为了不用每次新建工程都要配置一次,我直接把include和lib目录下的文件拷贝到vs2012的安装目录

例如我的目录是C:Program Files (x86)Microsoft Visual Studio 11.0VC

拷贝到该目录下对应的include以及lib下,就可以了。以后每次新建工程就不必都重复那些步骤了。

 

接下来,就写下OpenGL程序验证下。关于代码我会尽量注释,希望大家能看得清楚。

 ------------------------------------------------------------------------------------------------------------------------------

 其实我一直觉得的把代码先跑通然后再仔细研究代码会更加清楚,所以我先呈上该书第一个例子的运行结果。

 

--------------------------------------------------------------------------------------------------------------------------------

如果你的环境配置正确,代码也按照书上敲打的话,运行后可能会出现停止运行的情况

glutInitContextVersion(4, 3);将这一行代码注释 即可解决,暂时不知道为什么,看看后面能不能找到解释。

--------------------------------------------------------------------------------------------------------------------------------

正如书中所说的,创建窗口、接收鼠标和键盘输入等等这些功能并不属于OpenGL,只是利用了第三方软件库,

正如例子里面用到的glut、glew,这些工具简化了我们的开发,让我们可以更加专注于OpenGL的本质。

 

首先头文件以及一些变量定义

#include<iostream>
using namespace std;

#include "vgl.h"
#include "LoadShaders.h"

enum VA0_IDs { Triangles, NumVAOs };
enum Buffer_IDs { ArrayBuffer, NumBuffers };
enum Attrib_IDs { vPosition = 0, vColor = 1 };

GLuint VAOs[NumVAOs];
GLuint Buffers[NumBuffers];

const GLuint NumVertices = 6;

  

接着先进入main函数,看看主要做了什么。

int main(int argc, char** argv){

	/**
		该函数用来初始化GLUT库,之后会与当前窗口系统产生交互
	  关于命令行参数,可以指定窗口大小、位置或颜色类型等
	  因此glutInitWindowSize、glutInitDisplayMode或glutInitWindowPosition等可以在glutInit之前运行
	  但是当命令行有参数时,glutInit会移除之前的操作,比如设置窗口大小等等。
	*/
	glutInit(&argc, argv);
	/**
	   该函数指定了窗口颜色类型
	   除了GLUT_RGBA(指定 RGBA 颜色模式的窗口)外,还有其他类型,比如
	   GLUT_RGB(指定 RGB 颜色模式的窗口)、
	   GLUT_DEPTH(窗口使用深度缓存)、
	   GLUT_STENCIL(窗口使用模板缓存)等等
	   可查阅GLUT文档
	   https://www.opengl.org/documentation/specs/glut/spec3/node12.html#SECTION00033000000000000000
	*/
	glutInitDisplayMode(GLUT_RGBA);
	/**
		接下来这两个函数看名字就已经知道其用途
		分别是设置窗口大小以及窗口位置
		不过我们还是根据实际设备的尺寸动态进行设置比较好
	*/
			

	glutInitWindowSize(256, 256);
	glutInitWindowPosition(300, 300);
	/**
		从名字可以得知,使用某个指定版本的OpenGL
		但是我使用4.3的时候运行会崩溃
		换成3.0之后便可以,原因待查。
	*/
	glutInitContextVersion(3, 0);
	/**
		OpenGL3.2后提出两种模式
		兼容模式compatibility profile,该模式保证1.0以后的所有特性都可以使用。
		核心模式core profile,该模式可以确保使用的只是其最新特性
	*/
	//glutInitContextProfile(GLUT_CORE_PROFILE);
	//glutInitContextProfile(GLUT_COMPATIBILITY_PROFILE);
	/**
		创建一个窗口,参数为窗口标题
		创建完的窗口会关联OpenGL的上下文环境
		该函数API官方说明:
		The display state of a window is initially for the window to be shown. 
		But the window's display state is not actually acted upon until glutMainLoop is entered. 
		This means until glutMainLoop is called, rendering to a created window is ineffective 
		because the window can not yet be displayed.
		简单的说,就是在执行了glutMainLoop函数之后,对窗口的渲染才起作用。
		如果不调用glutMainLoop,窗口不会显示。
	*/
	glutCreateWindow(argv[0]);

	if(!GL_ARB_vertex_array_object)
		std::cout << "GLEW_ARB_vertex_array_object not available." << std::endl;
	/**
		该函数属于另外一个辅助库GLEW(OpenGL Extension Wrangler),该库是C/C++的扩展库,方便我们获取OpenGL扩展的各种函数。
		而这里说明下openGL在Windows下的情况。
		万恶的微软为了推自己的D3D,所以默认对openGL的支持是很有限的。
		从openGL1.1版本开始就再也没有升级了,差不多都十多年了。
		所以现在Windows下对于openGL的支持,全靠显卡厂商。
		正因为此,更新到最新的显卡驱动也是非常必须的。
		对于不一样的显卡,支持openGL1的版本也是不一样的,具体需要上各家网站查看。
		譬如我的GT750,就支持openGL4.3
		虽然安装完驱动后就支持最新的openGL了,但是微软并没有提供直接的openGL API,导致使用起来比较繁琐。
		于是,GLEW得用处就来了,他其实就是对这些繁琐的事情进行的封装,使得程序员可以很方便的调用glxxx的openGL函数。
		所以,GLEW简化获取函数地址的过程,减少了我们的工作量!
		因此此函数初始化后,我们就可以在之后的代码里面方便地使用相关的gl函数。
	*/
	if (glewInit()){
		cerr << "Unable to initialize GLEW ... exiting" << endl;
		exit(EXIT_FAILURE);
	}
	/**
		这些函数是我自己加进去的,可以通过以下方法获取自己系统中的OpenGL信息:
	*/
	const char* version = (const char*)glGetString(GL_VERSION);
	printf("OpenGL 版本:%s
", version);
	const char* extensions = (const char*)glGetString(GL_EXTENSIONS);
	//printf("OpenGL 扩展:%s
", extensions);
	const char* renderer = (const char*)glGetString(GL_RENDERER);
	printf("OpenGL 显卡:%s
", renderer);
	const char* vendor = (const char*)glGetString(GL_VENDOR);
	printf("OpenGL 开发商:%s
", vendor);

	/**
		该函数初始化OpenGL的相关数据,为之后的渲染做准备。
		接下来会详细解析
	*/
	init();
	/**
		该函数为窗口设置了回调函数,即在窗口有更新时,该回调函数就会执行。
		参数是指函数的地址,一个函数指针。
	*/
	glutDisplayFunc(display);
	/**
		该函数是个无限循环函数,即死循环。
		它会一直处理已经被注册的回调函数,以及用户输入等操作。
	*/
	glutMainLoop();

	return 0;
}

 

之后我们进去初始化函数init看看

void init(void){
	/**
		该函数原型为void glGenVertexArrays(GLsizei n, GLuint *arrays);
		作用是返回n个未使用的对象名保存到数组arrays中, 作为顶点数组对象(Vertex Array Object),常用简写VAO替代。
		在OpenGL中,VAO负责管理和顶点(Vertices)集合相关联的各种数据。
		但是这些数据我们是保存到顶点缓存对象中(Vertex Buffer Object),简称VBO。之后我们会详细介绍
		现在我们只需知道VAO是一个对象,其中包含一个或者更多的VBOs。
	*/
	glGenVertexArrays(NumVAOs, VAOs);	
	/**
		该函数原型为void glBindVertexArray(GLuint array);
		作用简单来说,就是绑定对象(Bind An Object)
		对于这个函数来说,VAOs[Triangles]里面保存着glGenVertexArrays执行完后返回的对象名,而它作为参数传入该函数。
		第一次调用时OpenGL内部会分配这个对象所需的内存并且将它作为当前对象。

		书上关于这个绑定对象的定义用设置铁路的道岔开关来描述,我觉得还是挺好理解的:
		【一旦设置了开关,从这条线路通过的所有列车都会驶向对应的轨道,如果设置到另外一个状态,
		那么之后经过的立车都会驶向另外一条轨道】
		OpenGL也是如此,当前绑定了哪个对象,之后的操作都是针对于这个对象,除非重新绑定了其他对象。
		
		一般来说,有两种情况需要我们绑定对象:
				1、创建对象并初始化它所对应的数据时
				2、下次我们准备使用这个对象而它并不是当前所绑定的对象时
	*/
	glBindVertexArray(VAOs[Triangles]);

	/**
		定义了两个三角形的坐标
		此处需要了解,当前的坐标系是以屏幕为中心的,x轴正方向向右,y轴正方向向上。
		范围分别为[-1,1]、[-1,1]
	*/
	GLfloat vertices[NumVertices][2] = {
		{ -0.90, -0.90 },	// Triangle 1
		{  0.85, -0.90 },
		{ -0.90,  0.85 },
		{  0.90, -0.85 },	// Triangle 2
		{  0.90,  0.90 },
		{ -0.85,  0.90 }
	};

	/**
		该函数原型为void glGenBuffers(GLsizei n, GLuint *buffers);
		作用是返回n个当前未使用的对象名保存到buffers数组中,作为顶点缓存对象(Vertex Buffer Objects),简称VBO。
		VBO是指OpenGL服务端(OpenGL server)分配和管理的一块内存区域。几乎所有传入OpenGL的数据都是存储在VBO当中。
	*/
	glGenBuffers(NumBuffers, Buffers);
	/**
		该函数原型为void glBindBuffer(GLenum target, GLuint buffer);
		作用是为当前已分配的名称绑定不同类型的缓存对象。简单的说,就是初始化顶点缓存对象。

		由于OpenGL里有很多种不同类型的缓存对象,因此绑定缓存对象时需要指定对应类型,
		用参数target表示。在这个例子中,是将顶点数据保存到缓存当中,因此使用GL_ARRAY_BUFFER类型来表示。
		目前缓存对象的类型共有8种,分别用于不同的OpenGL功能实现。以后介绍到VBO的时候再详细解释。
	*/
	glBindBuffer(GL_ARRAY_BUFFER, Buffers[ArrayBuffer]);
	/**
		上面的glBindBuffer中只是初始化了VBO,而下面这个函数则是把顶点数据(vertex data)传递到缓存对象中。
		它主要做了两件事
			1、分配顶点数据所需的存储空间
			2、将数据从应用程序的数组中拷贝到OpenGL服务端的内存中
		
		函数原型为 void glBufferData(GLenum target, GLsizeptr size, const GLvoid* data, GLenum usage);
		target用于表示缓存的对象的类型
		size用于表示要存储数据的大小,也即是OpenGL在分服务端内存分配的内存大小
		data用于表示要存储的数据的指针
		usage用于设置分配数据之后的OpenGL的读取和写入方式

	*/
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	//---------------------------------------------------------------------------------
	/**
		上面的代码
		我们创建并绑定了VAO
		以及定义了顶点数据,并将之传送到VBO中。
		那么这个时候VAO还没有和VBO产生联系,在该函数末尾我们再详细说明。
		接下来我们先看看着色器。
	*/
	//---------------------------------------------------------------------------------

	/**
		ShaderInfo该结构体的定义位于头文件LoadShaders.h中,如果有兴趣可以先看看,这里暂时不介绍
		我们先只需知道
		1、对于每一个OpenGL程序,当它所使用的OpenGL版本高于或等于3.1时,都需要指定至少两个着色器:
		   顶点着色器以及片段着色器,下面通过LoadShaders函数来完成这个任务。
		2、我们需要额外编写两个文件,就下面例子来说,一个是triangles.vert顶点着色器
		   另外一个是triangles.frag片段着色器。两个文件在末尾会贴出来。
		3、着色器所采用的语言是OpenGL着色语言GLSL,与C++非常类似。
		关于着色器的详细信息,下一篇再详细介绍。
	*/
	ShaderInfo shaders[] = {
		{ GL_VERTEX_SHADER, "triangles.vert" },
		{ GL_FRAGMENT_SHADER, "triangles.frag"},
		{ GL_NONE, NULL}
	};
	GLuint program = LoadShaders(shaders);
	glUseProgram(program);


	/**
		函数原型:void glVertexAttribPointer(GLuint index, GLint size, GLenum type, 
		GLboolean normalized, GLsizei stride, const GLvoid* pointer);
		之前我们调用了glBindData所传递给缓存的只是数据,之后我们要使用它,还必须指定数据类型。
		所以该函数完成的主要任务是:
		1、告诉OpenGL,该存储数据的格式
		2、因为我们使用着色器来编程,因此在顶点着色器阶段,我们会使用该函数来给着色器中的in类型的属性变量传递数据。
		   那么它们是怎么联系起来的呢?
		   便是通过第一个参数index,指明了着色器程序中变量的下标的作用。
		   例如:layout( location=index ) in vec4 position;
		   如果这个index和glVertexAttribPointer的第一个参数一样,那么相关缓存区的数据就会传递到这个position变量中去。
		3、该函数执行之后,会影响改变VAO的状态,VBO会被复制保存到VAO中。之后如果改变了当前所绑定的缓存对象,也不会改变到VAO里的对象。
	*/
	glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));
	/**
		因为默认情况下,顶点属性数组是不可访问的,所以我们需要调用以下函数激活它。
		范围为0到GL_MAX_VERTEX_ATTRIBS-1
	*/
	glEnableVertexAttribArray(vPosition);
}

  

再之后我们进入渲染函数display

void display(void){
	/**
		清除指定的缓存数据并且重新设置为当前的清除值
		参数可以通过逻辑或操作来指定多个数值参数。
		GL_COLOR_BUFFER_BIT	颜色缓存
		GL_DEPTH_BUFFER_BIT 深度缓存
		GL_STENCIL_BUFFER_BIT 模板缓存
		
		而OpenGL默认的清除颜色是黑色
		如果要指定其他颜色,可以调用glClearColor

		另外我们还需要知道,因为OpenGL是一种状态机,因此对它的所有设定OpenGL都会保留。
		所以对于glClearColor,最好的调用方法是放在初始化方法中,因为这样它只会被调用一次
		如果放在display中,OpenGL这样每一帧都会调用它重复设置清除颜色值,会降低运行效率。
	*/
	glClear(GL_COLOR_BUFFER_BIT);
	/**
		该函数我们在初始化函数见过,不过这次的功能不太一样
		之前是初始化,现在是激活该顶点数组对象。
		现在使用它作为顶点数据所使用的顶点数组。
	*/
	glBindVertexArray(VAOs[Triangles]);
	/**
		函数原型void glDrawArrays(GLenum mode, GLint first, GLsizei count);
		设置了渲染模式为GL_TRIANGLES,起始位置位于缓存的0偏移位置,一共渲染NumVertices个元素。
		以后我们会继续学习各种图元
	*/
	glDrawArrays(GL_TRIANGLES, 0, NumVertices);
	/**
		该函数强制吧所有进行中的OpenGL命令立即完成并传输到OpenGL服务端处理。
	*/
	glFlush();
}

接下来是关于着色器部分的代码,由于内容过长,决定一分为二

地址在【第二篇 Hello OpenGL 续】http://www.cnblogs.com/MyGameAndYOU/p/4681710.html

如果有发现我的内容有错误,恳请告诉我,我会改正,谢谢!

2015.07.26

    广州

原文地址:https://www.cnblogs.com/MyGameAndYOU/p/4609203.html