【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

    廣州

相關文章
相關標籤/搜索