【OpenGL】第二篇 Hello OpenGL

-------------------------------------------------------------------------------------------------------------------------------html

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

咱们必须任劳任怨的先铺好砖块,搭建好环境……ios

因此接下来让我先把所须要的库的环境安置好,再开始coding。程序员

-------------------------------------------------------------------------------------------------------------------------------编程

【安装环境】数组

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

关于该配置,我发现了一个前辈已经把详细过程写在他的csdn博客上,你们能够去看看,很是简单,里面有源码的下载地址。编程语言

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

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

例如个人目录是C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC

拷贝到该目录下对应的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\n", version);
	const char* extensions = (const char*)glGetString(GL_EXTENSIONS);
	//printf("OpenGL 扩展:%s\n", extensions);
	const char* renderer = (const char*)glGetString(GL_RENDERER);
	printf("OpenGL 显卡:%s\n", renderer);
	const char* vendor = (const char*)glGetString(GL_VENDOR);
	printf("OpenGL 开发商:%s\n", 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也是如此,当前绑定了哪一个对象,以后的操做都是针对于这个对象,除非从新绑定了其余对象。
		
		通常来讲,有两种状况须要咱们绑定对象:
				一、建立对象并初始化它所对应的数据时
				二、下次咱们准备使用这个对象而它并非当前所绑定的对象时
	*/
	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)传递到缓存对象中。
		它主要作了两件事
			一、分配顶点数据所需的存储空间
			二、将数据从应用程序的数组中拷贝到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中,若是有兴趣能够先看看,这里暂时不介绍
		咱们先只需知道
		一、对于每个OpenGL程序,当它所使用的OpenGL版本高于或等于3.1时,都须要指定至少两个着色器:
		   顶点着色器以及片断着色器,下面经过LoadShaders函数来完成这个任务。
		二、咱们须要额外编写两个文件,就下面例子来讲,一个是triangles.vert顶点着色器
		   另一个是triangles.frag片断着色器。两个文件在末尾会贴出来。
		三、着色器所采用的语言是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所传递给缓存的只是数据,以后咱们要使用它,还必须指定数据类型。
		因此该函数完成的主要任务是:
		一、告诉OpenGL,该存储数据的格式
		二、由于咱们使用着色器来编程,所以在顶点着色器阶段,咱们会使用该函数来给着色器中的in类型的属性变量传递数据。
		   那么它们是怎么联系起来的呢?
		   即是经过第一个参数index,指明了着色器程序中变量的下标的做用。
		   例如:layout( location=index ) in vec4 position;
		   若是这个index和glVertexAttribPointer的第一个参数同样,那么相关缓存区的数据就会传递到这个position变量中去。
		三、该函数执行以后,会影响改变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

    广州

相关文章
相关标签/搜索