OpenGL超级宝典学习笔记——片段着色器(二)


图像处理

图像处理是一种独立于顶点着色器的特殊处理程序。在不使用片段着色器的情况下绘制场景之后,可以按照各种方式应用卷积核。

为了保持着色器的简洁,使用硬件加速,我们限制总卷积的大小为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();
}

只进行一次采样:

image

5次采样:

image

锐化

锐化与模糊相反,它是使得物体的边缘更加明显和文字容易阅读。

锐化的着色器代码:

//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,这和模糊过滤器相同。这个操作保证了这种过滤器不会增强或减弱亮度。

锐化效果图

image

膨胀和腐蚀

膨胀和腐蚀都属于形态过滤器,这意味着它们会改变物体的形态。膨胀扩大明亮物体的大小,而腐蚀则缩小明亮物体的大小。(对于暗的物体则是相反的)。

膨胀只是简单的找到相邻的最大值。

//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;
}

image

腐蚀取周围相邻的最小值。

//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;
}

image

边缘检测

比较有价值的过滤器是边缘检测。图像的边缘是颜色变化快的地方,而边缘检测则是选取这部分颜色急剧变化的地方并高亮它们。

有三种边缘检测器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]);

}

image

它和锐化过滤器的区别就是中间的那个值是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;
}

image
多个镜面光

镜面光公式:

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;
}

image

 

源码:https://github.com/sweetdark/openglex

OpenGL超级宝典 第4版 中文版PDF+英文版+源代码 见 

OpenGL编程指南(原书第7版)中文扫描版PDF 下载

OpenGL 渲染篇

Ubuntu 13.04 安装 OpenGL

OpenGL三维球体数据生成与绘制【附源码】

Ubuntu下OpenGL编程基础解析

如何在Ubuntu使用eclipse for c++配置OpenGL  

更多《OpenGL超级宝典学习笔记》相关知识 见 

相关内容

    暂无相关文章