Shader學習筆記,通過GLSL實現(一)

carbonsunsu發表於2012-01-05

最近一直在專心研究利用GLSL編寫Shader,寫點東西將自己學的總結一下,把自己學習shader的經歷分享一下,希望能對有興趣學習shader的同學有些幫助,但這些玩意還算不上教程,很多都是我自己在學習中的問題以及如何解決的,有什麼不足還請各位指出,想要系統的學習GLSL的話還是推薦大家看《OpenGL Shading Language 3rd Edition》,但如果你對OpenGL也一無所知的話,那就請從《OpenGL Programming Guide 7th Edition》讀起了。

本次主要是給大家一個GLSL的簡單示例,對於一門新的語言或者技術,初學者入門總是要通過閱讀一些示例程式,我在初學GLSL的時候也是這樣,但是上網找了一圈發現,網上的大牛們所給的示例大部分都是僅僅有shader的程式碼,而沒有相應的OpenGL應用程式,好不容易找到一個完整的也只是簡簡單單的渲染一個2D三角形,但是如果如果是想渲染一個帶有光照的3D模型呢?如何在OpenGL程式中加入對shader的支援呢?如何向shader中傳遞資料呢?等等這些問題雖然書中會有答案,但是卻缺少一個完整的示例將他們系統的整合起來,下面我就給出一個利用GLSL+OpenGL實現最基本的Binn-phong+lamber光照的的程式,至於具體演算法就不細講了,有興趣的可以自己研究一下,很簡單的。

在本程式中,頂點著色器完成了幾乎所有任務,包括頂點由物體座標系到投影座標系的變換以及所有的光照計算,而片段著色器只是完成了顏色資料的傳遞,但在後面由於一些演算法的要求(例如bumpmap),我們還會將光照計算移到片段著色器中,這裡要注意,其實除了個別一些功能以外(例如頂點的空間變化或者紋理查詢),很多的計算均可以放在頂點著色器或者片段著色器裡,其區別只是計算速度與影像的質量。下面是著色器程式碼:

頂點著色器(Vertex Shader):

uniform vec3 lightposition;//光源位置
uniform vec3 eyeposition;//相機位置
uniform vec4 ambient;//環境光顏色
uniform vec4 lightcolor;//光源顏色
uniform float Ns;//高光係數
uniform float attenuation;//光線的衰減係數

varying vec4 color;//向片段著色其傳遞的引數

void main()
{
/*很多示例中都是利用uniform引數從應用程式中向shader裡傳遞當前模型檢視矩陣和模型檢視投影矩陣,其實對於初學者來說,我們大可以先用GLSL的內建變數:gl_ModelViewMatrix和gl_ModelViewProjectionMatrix代替,而頂點座標的變換則直接可以利用內建函式ftransform()實現。當然,如果你想自己傳遞這些引數也是可以的,後面會介紹一下。而gl_Vertex和gl_Normal則分別表示當前傳入的頂點的物體座標系座標和表面法向量,gl_Position則是用來傳輸投影座標系內頂點座標的內建變數。注意內建變數是不用宣告的,直接使用就行*/
vec3 ECPosition = vec3(gl_ModelViewMatrix * gl_Vertex);

vec3 N = normalize(gl_NormalMatrix * gl_Normal);
vec3 L = normalize(lightposition - ECPosition);
vec3 V = normalize(eyeposition - ECPosition);
vec3 H = normalize(V + L);

vec3 diffuse = lightcolor * max(dot(N , L) , 0);
vec3 specular = lightcolor * pow(max(dot(N , H) , 0) , Ns) * attenuation;

color = vec4(clamp((diffuse + specular) , 0.0 , 1.0) , 1.0);
color = color + ambient;

gl_Position = ftransform();
}

片段著色器(Fragment Shader):
//這裡的的varying變數名稱要與頂點著色器裡的一樣,否則會編譯錯誤
varying vec4 color;

void main()
{
//gl_FragColor是輸出片段顏色的內建變數,凡是以gl_開頭的變數都是內建變數
gl_FragColor = color;
}

其實shader程式不是很難寫,我學習的過程中主要是卡在了OpenGL程式和shader程式的關聯上,其實後來看看並不是太難,總結起來也就是讀取shader程式碼;建立、編譯shader;向shader中傳遞資料,其他的和一般的OpenGL程式差不多,下面給出OpenGL程式:

//標頭檔案是我自己封裝的工具包,功能包括:讀取shader原始檔;初始化shader和program等 #include

GLfloat ambient[4] = {1.0 , 0.0 , 0.0 , 1.0};
GLfloat lightcolor[4] = {1.0 , 1.0 , 1.0 , 1.0};
GLfloat eyeposition[3] = {0.0 , 10.0 , 30.0};
GLfloat skycolor[3] = {0.7 , 0.7 , 1.0};
GLfloat groundcolor[3] = {0.2 , 0.2 , 0.2};
GLfloat Ns = 8;
GLfloat attenuation = 0.1;
GLfloat objectSize = 15.0;
GLuint program;
GLuint vShader , fShader;

////////////////////////////////////////////////////////

void myInit()
{
glewInit();
glClearColor(0.0 , 0.0 , 0.0 , 1.0); glEnable(GL_DEPTH_TEST);

//讀取原始檔
const GLchar *vShaderSource = readShaderSource("GLSL_sample2.vert");
const GLchar *fShaderSource = readShaderSource("GLSL_sample2.frag");
//初始化shader和program
vShader = buildShader(&vShaderSource , GL_VERTEX_SHADER);
fShader = buildShader(&fShaderSource , GL_FRAGMENT_SHADER);
program = buildShaderProgram(vShader , fShader);
}

void myReshape(int w , int h)
{
glViewport(0 , 0 , (GLsizei)w , (GLsizei)h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(90 , 1 , 0.1 , 1000.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(eyeposition[0] , eyeposition1 , eyeposition[2] , 0.0 , 0.0 , 0.0 , 0.0 , 1.0 , 0.0);
}

void myDisplay()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(1.0 , 1.0 , 1.0);

/這裡是程式比較重要的地方:向給shader中的uniform變數賦值。注意賦值函式使用的位置,只有當一個program啟用的時候,裡面的uniform變數才會被分配索引,所以過早的對uniform變數賦值會長生錯誤。同時注意函式的格式/
glUseProgram(program);
glUniform3f(glGetUniformLocation(program , "lightposition") , 30.0 , 30.0 , 30.0);
glUniform3f(glGetUniformLocation(program , "eyeposition") , eyeposition[0] , eyeposition1 , eyeposition[2]);
glUniform4f(glGetUniformLocation(program , "ambient") , ambient[0] , ambient1 , ambient[2] , ambient[3]);
glUniform4f(glGetUniformLocation(program , "lightcolor") , lightcolor[0] , lightcolor1 , lightcolor[2] , lightcolor[3]);
glUniform1f(glGetUniformLocation(program , "Ns") , Ns); glUniform1f(glGetUniformLocation(program , "attenuation") , attenuation);
/剛開始我以為要利用shader進行渲染就要必須將模型變為一個一個頂點資訊向shader中傳入,但其實並不是這樣,我們同樣可以利用glut的內建模型或者自己動手載入模型,和平時一樣。同時注意一點,glut的一些內建模型只包含平面法線,沒有紋理座標,所以在後面我們給模型新增紋理的時候有些就無法顯示出紋理/
glutSolidTeapot(objectSize);
glutSwapBuffers();
}

void main(int argc , char** argv)
{
glutInit(&argc , argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowPosition(100 , 100);
glutInitWindowSize(600 , 600);
glutCreateWindow("GLSL_example2");
myInit();
glutReshapeFunc(myReshape);
glutDisplayFunc(myDisplay);
glutIdleFunc(myDisplay);
glutMainLoop();
}
程式效果:
enter image description here

前面說到了我們自己向shader中傳遞矩陣資料,這裡簡單說一下:

**在程式中向宣告個變數,例如**GLfloat modelViewMatrix[16];

**然後在完成檢視模型矩陣的設定後呼叫**glGetFloatv(GL_MODELVIEW_MATRIX ,modelViewMatrix);

**然後在program啟用後呼叫**glUniformMatrix4fv(glGetUniformLocation("modelViewMatrix"), 1 , GL_FALSE , modelViewMatrix);

這樣就完成了向shader中傳遞我們自己的矩陣資料。 總的說來整個程式並不是很難,但我是折騰了有一陣才搞成功,估計還是我太水了的緣故吧。。。

相關文章