圖像處理是一種獨立於頂點著色器的特殊處理程序。在不使用片段著色器的情況下繪制場景之後,可以按照各種方式應用卷積核。
為了保持著色器的簡潔,使用硬件加速,我們限制總卷積的大小為3X3.
在示例程序中,調用glCopyTexIamge2D把幀緩沖區拷貝到紋理中。紋理的大下為小於窗口的2的最大N次方值(在2.0中則沒有這個限制)。然後在窗口的中間繪制一個片段著色的四邊形,大小與這個紋理相同,其紋理坐標從左下角(0,0)到右上角(1,1)。
片段著色器基於紋理坐標,在以其為核心的相鄰的3X3紋理中進行采樣,然後進行過濾,得到這個中心點的顏色。
模糊可能是最常見的過濾器。它能夠平滑一些高頻率的特性,例如物體邊緣的鋸齒。它也叫做低通濾波器。它允許低頻率的特性通過,而截留高頻率的特性。
如果我們只用3X3的卷積核,那麼在單次采樣時不會有太明顯的變化。我們可以進行多次采樣。
下面是著色器代碼:
//blur.fs #version 120 //采樣的紋理 uniform sampler2D sampler0; //采樣的偏移 uniform vec2 tc_offset[9]; void main(void) { vec4 sampler[9]; for (int i = 0; i < 9; ++i) { //獲得采樣數據 sampler[i] = texture2D(sampler0, gl_TexCoord[0].st + tc_offset[i]); } //1 2 1 //2 1 2 /13 //1 2 1 //計算結果 gl_FragColor = (sampler[0] + sampler[1] * 2.0 + sampler[2] + sampler[3] * 2.0 + sampler[4] + sampler[5] * 2.0 + sampler[6] + sampler[7] * 2.0 + sampler[8])/ 13.0; }
在這個過程中,首先我們先不使用著色器繪制好圖形,然後啟用著色器程序,設置好sampler0和tc_offse,把幀緩沖拷貝到紋理中。再設置好紋理坐標,繪制一個正方形,使用著色器處理紋理。下面是部分關建代碼:
void ChangeSize() { ... windowWidth = textureWidth = w; windowHeight = textureHeight = h; //不支持非2的n次紋理 if (!GLEE_ARB_texture_non_power_of_two) { int n = 1; while ((1 << n) < windowWidth) { n++; } textureWidth = (1 << (n-1)); n = 1; while ((1 << n) < windowHeight) { n++; } textureHeight = (1 << (n-1)); } glViewport(0 ,0, w, h); TwWindowSize(w, h); GLfloat xIn = 1.0f/textureWidth; GLfloat yIn = 1.0f/textureHeight; //構造偏移數組 for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { tc_offset[3 * i + j][0] = (i - 1.0f) * xIn; tc_offset[3 * i + j][1] = (j - 1.0f) * yIn; } } }
void RenderScene() { .... //不使用著色器,繪制圖形到幀緩沖區 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram(0); glLightfv(GL_LIGHT0, GL_POSITION, g_lightPos); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(35.0, (GLfloat)windowWidth/(GLfloat)windowHeight, 1.0, 100.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); gluLookAt(cameraPos[0], cameraPos[1], cameraPos[2], 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); glTranslatef(xTrans, yTrans, zTrans); float mat[4*4]; ConvertQuaternionToMatrix(g_Rotate, mat); glMultMatrixf(mat); DrawGround(); DrawObjects(); glPopMatrix(); //開啟片段著色器,進行模糊處理 glDisable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glUseProgram(program[whichShader]); uniformLoc = glGetUniformLocation(program[whichShader], "sampler0"); if (uniformLoc != -1) { glUniform1i(uniformLoc, sampler); } uniformLoc = glGetUniformLocation(program[whichShader], "tc_offset"); if (uniformLoc != -1) { glUniform2fv(uniformLoc, 9, &tc_offset[0][0]); } //通過著色器的次數 for (int i = 0; i < numPass; ++i) { //從幀緩沖區中讀取數據到紋理中 glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, windowWidth-textureWidth, windowHeight-textureHeight, textureWidth, textureHeight, 0); //清空幀緩沖區 glClear(GL_COLOR_BUFFER_BIT); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f((-(GLfloat)textureWidth/(GLfloat)windowWidth), -((GLfloat)textureHeight/(GLfloat)windowHeight)); glTexCoord2f(1.0f, 0.0f); glVertex2f((GLfloat)textureWidth/(GLfloat)windowWidth, -((GLfloat)textureHeight/(GLfloat)windowHeight)); glTexCoord2f(1.0f, 1.0f); glVertex2f((GLfloat)textureWidth/(GLfloat)windowWidth, (GLfloat)textureHeight/(GLfloat)windowHeight); glTexCoord2f(0.0f, 1.0f); glVertex2f(-(GLfloat)textureWidth/(GLfloat)windowWidth, (GLfloat)textureHeight/(GLfloat)windowHeight); glEnd(); } glEnable(GL_DEPTH_TEST); glutSwapBuffers(); }
只進行一次采樣:
5次采樣:
銳化與模糊相反,它是使得物體的邊緣更加明顯和文字容易閱讀。
銳化的著色器代碼:
//sharpen.fs/ #version 120 uniform sampler2D sampler0; uniform vec2 tc_offset[9]; void main(void) { vec4 sampler[9]; for (int i = 0; i < 9; ++i) { sampler[i] = texture2D(sampler0, gl_TexCoord[0].st + tc_offset[i]); } //-1 -1 -1 //-1 9 -1 //銳化的卷積 和為1 //-1 -1 -1 gl_FragColor = (-sampler[0] - sampler[1] - sampler[2] - sampler[3] + 9 * sampler[4] -sampler[5] - sampler[6] - sampler[7] - sampler[8]); }
注意這個卷積核相加的結果為1,這和模糊過濾器相同。這個操作保證了這種過濾器不會增強或減弱亮度。
銳化效果圖
膨脹只是簡單的找到相鄰的最大值。
//dilation.fs #version 120 uniform sampler2D sampler0; uniform vec2 tc_offset[9]; void main(void) { vec4 sampler[9]; //find the max value vec4 maxValue = vec4(0.0); for (int i = 0; i < 9; ++i) { sampler[i] = texture2D(sampler0, gl_TexCoord[0].st + tc_offset[i]); maxValue = max(sampler[i], maxValue); } gl_FragColor = maxValue; }
腐蝕取周圍相鄰的最小值。
//erosion.fs #version 120 uniform sampler2D sampler0; uniform vec2 tc_offset[9]; void main(void) { vec4 sampler[9]; vec4 minValue = vec4(1.0); for (int i = 0; i < 9; ++i) { sampler[i] = texture2D(sampler0, gl_TexCoord[0].st + tc_offset[i]); minValue = min(minValue, sampler[i]); } gl_FragColor = minValue; }
比較有價值的過濾器是邊緣檢測。圖像的邊緣是顏色變化快的地方,而邊緣檢測則是選取這部分顏色急劇變化的地方並高亮它們。
有三種邊緣檢測器Laplacian,Sobel和Prewitt. Sobel和Prewitt梯度過濾器,它們檢測每個通道強度的一階導數的變化,只是在單個方向上進行。Laplacian則檢測二階導數的零值,也就是顏色的強度梯度從暗變亮的地方(或相反)。它可以用於所有的邊緣。
下面的代碼使用Laplacian過濾器。
//edgedetetion.fs #version 120 uniform sampler2D sampler0; uniform vec2 tc_offset[9]; void main(void) { vec4 sampler[9]; for (int i = 0; i < 9; ++i) { sampler[i] = texture2D(sampler0, gl_TexCoord[0].st + tc_offset[i]); } //-1 -1 -1 //-1 8 -1 //-1 -1 -1 gl_FragColor = (8.0 * sampler[4]) - (sampler[0] + sampler[1] + sampler[2] + sampler[3] + sampler[5] + sampler[6] + sampler[7] + sampler[8]); }
它和銳化過濾器的區別就是中間的那個值是8不是9,這樣系數之和就是0。這也說明了為何圖形中間是黑的。因為圖元中間的顏色相近,通過卷積核過濾之後就接近於0了。只有在圖元邊緣顏色變化劇烈的地方,才有較大的顏色值。
在此之前,我們討論過逐頂點的光照。還討論了通過分離鏡面光和使用紋理查找的方式來提升光照效果。在這裡我們使用片段著色器的方式來處理光照。算法是一樣的。
在這裡我們結合頂點著色器和片段著色器來實現。頂點著色器對法線、光照向量沿著線和三角形進行插值。然後,片段著色器處理頂點著色器產生的值得到最終的結果。
公式:
Cdiff = max{N • L, 0} * Cmat * Cli
// diffuse.vs // uniform vec3 lightPos[1]; varying vec3 N, L; void main(void) { // vertex MVP transform gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; // eye-space normal N = gl_NormalMatrix * gl_Normal; // eye-space light vector vec4 V = gl_ModelViewMatrix * gl_Vertex; L = lightPos[0] - V.xyz; // Copy the primary color gl_FrontColor = gl_Color; }
這與之前只用頂點著色器不同的是,這裡用varyings修飾的標識符N和L作為輸出,在片段著色器中用一樣的名稱就可以訪問到N,L。這種方式比之前使用紋理坐標作為輸出的方式更容易理解,也不容易出錯(試想不小心把L輸出到textureCoord[1]中,但實際使用的是textureCoord[0], 不會產生編譯錯誤,但得不到想要的結果)。
下面是片段著色器代碼:
//diffuse.fs #version 120 varying vec3 N, L; void main(void) { float intensity = max(0.0, dot(normalize(N), normalize(L))); gl_FragColor = gl_Color; gl_FragColor.rgb *= intensity; }
鏡面光公式:
Cspec = max{N • H, 0}Sexp * Cmat * Cli
VS有多個L的輸出
//3light.vs #version 120 uniform vec3 lightPos[3]; varying vec3 N, L[3]; void main(void) { //MVP transform gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; //eye-space vec4 V = gl_ModelViewMatrix * gl_Vertex; //eye-space noraml vector N = gl_NormalMatrix * gl_Normal; for (int i = 0; i < 3; ++i) { L[i] = lightPos[i] - V.xyz; } //primary vector gl_FrontColor = gl_Color; }
//3light.fs #version 120 varying vec3 N, L1[3]; void main(void) { vec3 NN = normalize(N); gl_FragColor = vec4(0.0); //3個光的顏色 vec3 lightCol[3]; lightCol[0] = vec3(0.5, 0.5, 1.0); lightCol[1] = vec3(0.2, 0.3, 0.5); lightCol[2] = vec3(0.8, 0.4, 0.8); const float expose = 128.0f; for (int i = 0; i < 3; ++i) { vec3 NL = normalize(L1[i]); vec3 H = normalize(NL + vec3(0.0, 0.0, 1.0)); float NdotL = max(0.0, dot(NN, NL)); //diffuse gl_FragColor.rgb += gl_Color.rgb * lightCol[i] * NdotL; //specular if (NdotL > 0.0) { gl_FragColor.rgb += lightCol[i] * pow(max(0.0, dot(NN, H)), expose); } } gl_FragColor.a = gl_Color.a; }
源碼:https://github.com/sweetdark/openglex
OpenGL超級寶典 第4版 中文版PDF+英文版+源代碼 見 http://www.linuxidc.com/Linux/2013-10/91413.htm
OpenGL編程指南(原書第7版)中文掃描版PDF 下載 http://www.linuxidc.com/Linux/2012-08/67925.htm
OpenGL 渲染篇 http://www.linuxidc.com/Linux/2011-10/45756.htm
Ubuntu 13.04 安裝 OpenGL http://www.linuxidc.com/Linux/2013-05/84815.htm
OpenGL三維球體數據生成與繪制【附源碼】 http://www.linuxidc.com/Linux/2013-04/83235.htm
Ubuntu下OpenGL編程基礎解析 http://www.linuxidc.com/Linux/2013-03/81675.htm
如何在Ubuntu使用eclipse for c++配置OpenGL http://www.linuxidc.com/Linux/2012-11/74191.htm
更多《OpenGL超級寶典學習筆記》相關知識 見 http://www.linuxidc.com/search.aspx?where=nkey&keyword=34581