圖像處理是一種獨立於頂點著色器的特殊處理程序。在不使用片段著色器的情況下繪制場景之後,可以按照各種方式應用卷積核。
為了保持著色器的簡潔,使用硬件加速,我們限制總卷積的大小為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