歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> OpenGL超級寶典學習筆記——片段著色器(二)

OpenGL超級寶典學習筆記——片段著色器(二)

日期:2017/3/1 9:32:09   编辑:Linux編程

圖像處理

圖像處理是一種獨立於頂點著色器的特殊處理程序。在不使用片段著色器的情況下繪制場景之後,可以按照各種方式應用卷積核。

為了保持著色器的簡潔,使用硬件加速,我們限制總卷積的大小為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

Copyright © Linux教程網 All Rights Reserved