1. 程式人生 > >GLSL基礎概念和用法

GLSL基礎概念和用法

圖形影象頂點和片段畫素的渲染,只能用固定管線或可程式設計著色器任何一種進行處理。無論是頂點還是片段都是隻能處理固定管線中整個頂點或片段中的可以豐富自定義的部分,其它頂點或畫素處理還是用硬體固定管線進行。

一、頂點著色器

頂點著色器,根據應用程式的設計,只是選擇處理: 1.視覺空間變換(模型,法線,紋理). 2.主顏色和輔助顏色的計算生成(光照在攝像機座標系中進行實時光照計算). 3.紋理座標計算。 4.霧座標設定和處理。 5.點大小。 新的頂點著色器或者支援更多的處理。並不是頂點管線的所有操作都可以用頂點著色器替代,下面的一些操作仍然是由固定管線處理: 1)透視除法。硬體實現。 2)視口對映變換。硬體實現。 3)圖元裝配,在4D裁剪空間中,進行視錐裁剪之前進行。 4)平截頭體(視景體)和使用者裁剪。 5)背面剔除,視口變換後,光柵化之前進行。 6)雙面光照選擇。 7)多邊形模式處理。(可能是指凹凸模式,著色模式,三角帶三角網格模式). 8)多邊形偏移,在圖元裝配階段進行偏移。 9)深度範圍擷取,glDepthRange設定後,固定管線內的實現程式碼進行設定。 頂點著色器,引入的更多實時光照計算,可控制的圖形頂點變換效果(從CPU端移動到GPU端),紋理變換,霧,點大小設定可以有效的控制GPU Shader來實現;且其它的OpenGL狀態設定,例如背面剔除,多次渲染Pass狀態等更加方便的統一到Shader中進行設定。

二、片斷著色器(畫素著色器)

片段著色器之前,還進行了光柵化插值操作,也是硬體實現的。 片段著色器可以處理的操作是: 1.提取紋理單元,用於紋理貼圖。 2.紋理應用。 3.霧. 4.主顏色和輔助顏色匯合。 不論是否使用片斷著色器,OGL固定管線都要執行下面的操作: 1)單調或平滑著色(控制片斷之間的插值,還需要一層內部過渡處理的)。 2)畫素覆蓋計算。根據圓形畫素點大小,覆蓋到的方形畫素格子的大小。 3)畫素所有權測試. 畫素位置是不是當前OGL context例項所有,被其它OGL例項視窗遮擋了則不是。 4)裁剪操作。畫素級別還是要進行裁剪的,使用者設定的scissor裁剪。 5)點畫模式應用(實線虛線,OGL 3.0後已經廢除了,OGL 3.1刪除了)。 6)scissor test,裁剪測試。 7)alpha測試(OGL 3.1後刪除了,並用片段著色器替代)。 8)模板測試。 9)深度測試,是基於畫素上才能應用深度測試,視口變換後寫入glDepthRange是頂點級別還不能確定) 10)alpha混合,blend操作,當前drawcall得到的顏色和原來在後臺顏色快取區中的顏色混合,混合後會在臨時快取會再寫入後臺顏色快取,預設寫入時GL_COPY。 11)對畫素進行邏輯操作,glLogicOP預設是GL_COPY. 12)顏色值的抖動,混合周邊的顏色,使得顏色更加豐富些,一般現代硬體都不需要,OGLES上還是需要的 預設開啟。 13)顏色掩碼操作glColorMask, 模板和深度快取也是有Mask的。 片段著色器通過程式碼控制紋理提取和複雜的紋理動畫,多重紋理對映,顏色組合,霧設定,顏色組合(紋理著色後和光照輔助顏色匯合),模板操作,alpha測試等很多都可以在片段著色器中處理了,和各種狀態的統一設定。片段著色器承載的更多影象效果的實現。

三、GLSL程式邏輯

Shader 原始碼可以共用 Shader Object物件實現的功能,故需要連結。 Shader Program連結後是動態的可執行指令集,執行中可以更新如果有效那麼會馬上看到效果(shader object detach 後再編譯後attach),刪除也不是馬上的到不再執行才刪除。

1.Shader Object物件

//(1) 建立物件控制代碼,eShaderType是GL_VERTEX_SHADER或GL_FRAGMENT_SHADER GLuint shader = glCreateShader(eShaderType); //(2) 繫結shader字串指令碼原始碼, count是shader程式碼段個數,可以連線多個程式碼段 glShaderSource(shader, 1, &strFileData, NULL); //(3) 編譯shader,得到靜態的著色器程式,應該是GPU彙編指令,繫結到shader標示符地址 glCompileShader(shader); // (4) 檢查編譯狀態 GLint status; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); // 獲取log長度 GLint infoLogLength; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength); // 獲取出錯資訊 GLchar *strInfoLog = new GLchar[infoLogLength + 1]; glGetShaderInfoLog(shader, infoLogLength, NULL, strInfoLog);

2.Shader Program程式

//(1) 建立program GLuint programId = glCreateProgram(); //(2) 繫結shader物件,一個著色器程式可以繫結多個著色器物件控制代碼 for (std::vector<GLuint>::size_type iLoop = 0; iLoop < shaderList.size(); iLoop++) glAttachShader(programId, shaderList[iLoop]); //(3) 連結shader,得到可執行的機器碼 glLinkProgram(programId); //(4) 檢查program狀態,如果有錯誤打印出錯資訊 GLint status; glGetProgramiv(programId, GL_LINK_STATUS, &status); GLint infoLogLength; glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &infoLogLength); GLchar *strInfoLog = new GLchar[infoLogLength + 1]; glGetProgramInfoLog(programId, infoLogLength, NULL, strInfoLog); 使用著色器: // 使用著色器程式
glBindVertexArray(vaoId);// glBindVertexArray和glUseProgram先後順序,在glDrawXXX之前呼叫就行,不分先後的
glUseProgram(programId);
glUniform2f(offsetLocationId, fXOffset, fYOffset);//偏移量傳送到頂點著色器
drawcall呼叫驅動一次渲染管線流程: glDrawArrays(GL_TRIANGLES, 0, 3);

四、Shader資料訪問型別,以及app和Shader資料的互動

GLSL 1.3版本以前,也就是OpenglES2.0下頂點著色器的輸入變數用attribute關鍵字來限定,attribute不能作為片段著色器的輸入,app中用glBindAttribLocation()得到變數欄位,glVertexAttribPointer()為每個attribute變數賦值。片段著色器的輸入用varying關鍵字限定,頂點著色器修改varying變數的值,片段著色器使用varying欄位的值。 GLSL 1.4版本中attribute和varying欄位都刪除了,都統一使用in out或inout關鍵字。 用:
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)48);指定輸入。
用glGetUniformLocation(programId, "offset");獲取unitform變數。
glUniform2f(offsetLocationId, fXOffset, fYOffset);設定app中設定的值。
Shader全域性變數用uniform關鍵字修飾,uniform只能在app中修改,vertex和fragment shader只可以使用,GLSL1.3和1.4都一樣,如果uniform變數在vertex和fragment兩者之間宣告方式完全一樣,則它可以在vertex和fragment共享使用,uniform變數一般用來表示:變換矩陣,材質各種光照顏色,顏色等資訊。頂點著色器的輸入用: // 輸入引數對應頂點輸入的型別下標,預設下標從0開始 glEnableVertexAttribArray(0); // 說明陣列型別下標,頂點屬性成員數量,成員型別,法向量規範化,成員之間的跨度,資料塊中該屬性位元組首地址 void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid * pointer);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); 例如: //建立vertex array object物件 glGenVertexArrays(1, &vaoId); glBindVertexArray(vaoId); //建立vertex buffer object物件 glGenBuffers(1, &vboId); glBindBuffer(GL_ARRAY_BUFFER, vboId); glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW); // 指定VAO如何去解析VBO資料塊中的資料。 // 在Shader建立之前或建立之後指定都是可以的,因為glDrawXXX時候才真正去處理Opengl狀態機中的設定。 //啟用頂點位置屬性索引,要在display中指定的(vao包裝了切換vao即可),因為需要開啟頂點屬性索引才能繪製,特別是繪製物體多的時候,需要切換才能正確繪製。 // 也可以封裝在VAO中,只負責啟用glEnableVertexAttribArray不關閉即可。 glEnableVertexAttribArray(0); // 啟用頂點屬性陣列 glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); //指定Position頂點屬性資料格式,大小會根據glDrawArrays截斷。 //啟用頂點顏色屬性索引 glEnableVertexAttribArray(1); glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)48);// 指定Color頂點屬性資料格式,大小會根據glDrawArrays截斷。 glBindBuffer(GL_ARRAY_BUFFER, 0); // 結束一個vao的包裝。 glBindVertexArray(NULL);

五、例項程式碼

Shader.h:
#ifndef _SHADER_H_
#define _SHADER_H_
#include <vector>
#include <string>
#include <cstring>
#include <GL/glew.h>
class Shader {
public:
	static GLuint createShader(GLenum eShaderType, const std::string &strShaderFile);
	static GLuint createShader(GLenum eShaderType, const char* fileName);
	static GLuint createProgram(const std::vector<GLuint> &shaderList);
};

#endif

Shader.cpp
#include <fstream>
#include <sstream>
#include "Shader.h"
//從字串流構造著色器物件
GLuint Shader::createShader(GLenum eShaderType, const std::string &strShaderFile)
{
	GLuint shader = glCreateShader(eShaderType);//根據型別建立shader,頂點或者片段著色器
	const char * strFileData = strShaderFile.c_str();
	glShaderSource(shader, 1, &strFileData, NULL);//繫結shader字串, count是shader程式碼段個數,可以連線多個程式碼段
	glCompileShader(shader);//編譯shader,得到靜態的彙編指令,繫結到shader標示符地址
	//檢查shader狀態
	GLint status;
	glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
	if (status == GL_FALSE)
	{
		GLint infoLogLength;
		glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
		GLchar *strInfoLog = new GLchar[infoLogLength + 1];
		glGetShaderInfoLog(shader, infoLogLength, NULL, strInfoLog);
		const char * strShaderType = NULL;
		switch (eShaderType)
		{
		case GL_VERTEX_SHADER: strShaderType = "vertex"; break;
		case GL_GEOMETRY_SHADER: strShaderType = "geometry"; break;
		case GL_FRAGMENT_SHADER: strShaderType = "fragment"; break;
		}
		fprintf(stderr, "Compile failure in %s shader:\n%s\n", strShaderType, strInfoLog);
		delete[] strInfoLog;
	}
	return shader;
}
//從檔案構造著色器物件
GLuint Shader::createShader(GLenum eShaderType, const char* fileName)
{
	std::ifstream infile(fileName);
	if (!infile)
	{
		fprintf(stderr, "Could not open file : %s for reading.", fileName);
		return 0;
	}
	std::stringstream  buffer;
	buffer << infile.rdbuf();
	infile.close();
	return Shader::createShader(eShaderType, buffer.str());
}
//構造著色器程式物件
GLuint Shader::createProgram(const std::vector<GLuint> &shaderList)
{
	GLuint programId = glCreateProgram();//建立program
	for (std::vector<GLuint>::size_type iLoop = 0; iLoop < shaderList.size(); iLoop++)
		glAttachShader(programId, shaderList[iLoop]);//繫結shader,一個著色器程式可以繫結多個著色器程式

	glLinkProgram(programId);//連結shader,得到可執行的機器碼
	//檢查program狀態,如果有錯誤打印出錯資訊
	GLint status;
	glGetProgramiv(programId, GL_LINK_STATUS, &status);
	if (status == GL_FALSE)
	{
		GLint infoLogLength;
		glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &infoLogLength);

		GLchar *strInfoLog = new GLchar[infoLogLength + 1];
		glGetProgramInfoLog(programId, infoLogLength, NULL, strInfoLog);
		fprintf(stderr, "Linker failure: %s\n", strInfoLog);
		delete[] strInfoLog;
	}

	/*for (size_t iLoop = 0; iLoop < shaderList.size(); iLoop++)
		glDetachShader(programId, shaderList[iLoop]);*/
	return programId;
}

VAO_VBO_Shader.cpp:
//依賴庫glew32.lib freeglut.lib
//使用著色器顏色插值繪製三角形
#include <string>
#include <vector>
#include <GL/glew.h>
#include <GL/freeglut.h>
#pragma  comment(lib, "glew32d.lib")
#include "shader.h"
using namespace std;

void userInit();
void reshape(int w, int h);
void display(void);
void keyboardAction(unsigned char key, int x, int y);


GLuint vboId;//vertex buffer object控制代碼
GLuint vaoId;//vertext array object控制代碼
GLuint programId;//shader program 控制代碼
GLuint offsetLocationId;

int main(int argc, char **argv)
{
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
	glutInitWindowPosition(100, 100);
	glutInitWindowSize(512, 512);
	glutCreateWindow("Triangle demo");

	glewInit();
	userInit();
	glutReshapeFunc(reshape);
	glutDisplayFunc(display);
	glutKeyboardFunc(keyboardAction);
	glutMainLoop();
	return 0;
}
//自定義初始化函式
void userInit()
{
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glEnable(GL_DITHER);
	glDisable(GL_DITHER);
	//頂點位置和顏色資料
	const GLfloat vertexData[] = {
		-0.5f, 0.0f, 0.0f, 1.0f,
		0.5f, 0.0f, 0.0f, 1.0f,
		0.0f, 0.5f, 0.0f, 1.0f,
		1.0f, 0.0f, 0.0f, 1.0f,
		0.0f, 1.0f, 0.0f, 1.0f,
		0.0f, 0.0f, 1.0f, 1.0f
	};
	//建立vertex array object物件
	glGenVertexArrays(1, &vaoId);
	glBindVertexArray(vaoId);
	//建立vertex buffer object物件
	glGenBuffers(1, &vboId);
	glBindBuffer(GL_ARRAY_BUFFER, vboId);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
	// 指定VAO如何去解析VBO資料塊中的資料。
	// 在Shader建立之前或建立之後指定都是可以的,因為glDrawXXX時候才真正去處理Opengl狀態機中的設定。
	//啟用頂點位置屬性索引,要在display中指定的(vao包裝了切換vao即可),因為需要開啟頂點屬性索引才能繪製,特別是繪製物體多的時候,需要切換才能正確繪製。
	// 也可以封裝在VAO中,只負責啟用glEnableVertexAttribArray不關閉即可。
	glEnableVertexAttribArray(0); // 啟用頂點屬性陣列
	glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0); //指定Position頂點屬性資料格式,大小會根據glDrawArrays截斷。
	//啟用頂點顏色屬性索引
	glEnableVertexAttribArray(1);
	glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)48);// 指定Color頂點屬性資料格式,大小會根據glDrawArrays截斷。

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	// 結束一個vao的包裝。
	glBindVertexArray(NULL);

	// 這裡不能關閉,否則Shader取不到資料
	glDisableVertexAttribArray(0); // 去啟用VAO的頂點屬性
	glDisableVertexAttribArray(1);

	//從檔案建立著色器
	/*std::vector<GLuint> idVector;
	string strPre = "E:\\OpenGL\\OpenGl7thEdition-master\\OpenGl7thEdition-master\\OpenGL_MyProject\\hello\\data";
	idVector.push_back(Shader::createShader(GL_VERTEX_SHADER, strPre + "\\vertex.glsl"));
	idVector.push_back(Shader::createShader(GL_FRAGMENT_SHADER, strPre + "\\fragment.glsl"));
	programId = Shader::createProgram(idVector);*/
	//從檔案建立著色器
	std::vector<GLuint> idVector;
	const std::string vertexStr(
		"#version 330\n"
		"in vec4 pos;\n"
		"in vec4 incolor;\n"
		"uniform vec2 offset;\n"
		"smooth out vec4 thecolor;\n"
		"void main()\n"
		"{\n"
		"vec4 totalOffset = vec4(offset.x, offset.y, 0.0, 0.0);\n"
		"gl_Position = pos + totalOffset;\n"
		"thecolor = incolor;}\n"
		);
	const std::string fragmentStr(
		"#version 330\n"
		"smooth in vec4 thecolor;\n"
		"out vec4 outputColor;\n"
		"void main()\n"
		"{outputColor = thecolor;}\n"
		);

	idVector.push_back(Shader::createShader(GL_VERTEX_SHADER, vertexStr));// "data\\vertex.glsl"));
	idVector.push_back(Shader::createShader(GL_FRAGMENT_SHADER, fragmentStr));// "data\\fragment.glsl"));
	programId = Shader::createProgram(idVector);
	offsetLocationId = glGetUniformLocation(programId, "offset");
	//int nStereoSupport = 0;
	//glGetIntegerv(GL_STEREO, &nStereoSupport); // Win7 OGL 3.1不支援
	//int nDoubleFrameBufferSupport = 0;
	//glGetIntegerv(GL_DOUBLEBUFFER, &nDoubleFrameBufferSupport);// Win7 OGL 3.1支援
	//int nAluColorBuffer = 0;
	//glGetIntegerv(GL_AUX_BUFFERS, &nAluColorBuffer);// Win7 OGL 3.1不支援,只有0個顏色輔助快取
}
//調整視窗大小回調函式
void reshape(int w, int h)
{
	glViewport(0, 0, (GLsizei)w, (GLsizei)h);
}

//根據時間計算偏移量
void ComputePositionOffsets(GLfloat &fXOffset, GLfloat &fYOffset)
{
	const GLfloat fLoopDuration = 5.0f;
	const GLfloat fScale = 3.14159f * 2.0f / fLoopDuration;

	GLfloat fElapsedTime = glutGet(GLUT_ELAPSED_TIME) / 1000.0f;

	GLfloat fCurrTimeThroughLoop = fmodf(fElapsedTime, fLoopDuration);

	fXOffset = cosf(fCurrTimeThroughLoop * fScale) * 0.5f;
	fYOffset = sinf(fCurrTimeThroughLoop * fScale) * 0.5f;
}

////繪製回撥函式
//void display(void)
//{
//	glClear(GL_COLOR_BUFFER_BIT);
//	// 繫結到VAO狀態,也就是封裝了通過VAO 的glEnableVertexAttribArray,glVertexAttribPointer關聯起來可以解釋的VBO資料作為輸入
//	// 這樣通過VAO的切換,就可以在輕鬆的切換VBO資料來源,且正確的解釋VBO資料來源作為Shader的輸入,能夠方便的進行繪製切換。
//	glBindVertexArray(vaoId); 
//	glUseProgram(programId);// 啟用GPU中的Shader機器碼程式
//	GLfloat fXOffset = 0.0f, fYOffset = 0.0f;
//	ComputePositionOffsets(fXOffset, fYOffset);
//	glUniform2f(offsetLocationId, fXOffset, fYOffset);//偏移量傳送到頂點著色器
//
//	//繪製三角形,用glDrawElemenets不能正確繪製,因為這裡需要連續的
//	glDrawArrays(GL_TRIANGLES, 0, 3);
//	glUseProgram(0);
//	// 關閉GL_ARRAY_BUFFER,glDisableVertexAttribArray,也是可以正確繪製的,
//	// 說明glBindVertexArray(vaoId)是正確封裝了需要關聯了啟用狀態和索引關係的集合,直接glBindVertexArray切換繪製即可。
//	//glBindBuffer(GL_ARRAY_BUFFER, 0); // 去啟用GPU中的該VBO
//	//glDisableVertexAttribArray(0); // 去啟用VAO的頂點屬性
//	//glDisableVertexAttribArray(1);
//	glutSwapBuffers();
//}

//繪製回撥函式
void display(void)
{

	glClear(GL_COLOR_BUFFER_BIT);
	//計算偏移量
	GLfloat fXOffset = 0.0f, fYOffset = 0.0f;
	ComputePositionOffsets(fXOffset, fYOffset);

	// 使用著色器程式
	glBindVertexArray(vaoId);// glBindVertexArray和glUseProgram先後順序,在glDrawXXX之前呼叫就行,不分先後的
	glUseProgram(programId);
	glUniform2f(offsetLocationId, fXOffset, fYOffset);//偏移量傳送到頂點著色器
	
	//glBindBuffer(GL_ARRAY_BUFFER, vboId);
	// Shader從app到GPU中的輸入資訊,可以在vbo,vao中指定,也可以在display時候修改資料後再指定。
	//啟用頂點位置屬性索引,一組屬性代表了頂點,顏色,法向量,uv等,下標是從0開始
	//glEnableVertexAttribArray(0);
	//為頂點著色器位置資訊賦值,positionSlot表示頂點著色器位置屬性(即,Position);
	// 4表示每一個頂點資訊由幾個值組成,這個值必須位1,2,3或4;
	// GL_FLOAT表示頂點資訊的資料型別;GL_FALSE表示不要將資料型別標準化(即fixed-point);
	// stride表示陣列中每個元素的長度;pCoords表示陣列的首地址
	//glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
	//繪製三角形,真正根據設定執行著色器程式
	glDrawArrays(GL_TRIANGLES, 0, 3);

	//glDisableVertexAttribArray(NULL);
	//glBindBuffer(GL_ARRAY_BUFFER, NULL);
	// 終止著色器程式執行繫結
	glUseProgram(NULL);
	
	glutSwapBuffers();

	glutPostRedisplay();//不斷重新整理
}

//鍵盤按鍵回撥函式
void keyboardAction(unsigned char key, int x, int y)
{
	switch (key)
	{
	case 033:  // Escape key
		exit(EXIT_SUCCESS);
		break;
	}
}