首页 / 操作系统 / Linux / 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();}只进行一次采样:5次采样:锐化
锐化与模糊相反,它是使得物体的边缘更加明显和文字容易阅读。锐化的着色器代码://sharpen.fs/#version 120uniform 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//-19 -1 //锐化的卷积 和为1//-1 -1 -1gl_FragColor = (-sampler[0] - sampler[1] - sampler[2] - sampler[3] + 9 * sampler[4]-sampler[5] - sampler[6] - sampler[7] - sampler[8]);}注意这个卷积核相加的结果为1,这和模糊过滤器相同。这个操作保证了这种过滤器不会增强或减弱亮度。锐化效果图膨胀和腐蚀
膨胀和腐蚀都属于形态过滤器,这意味着它们会改变物体的形态。膨胀扩大明亮物体的大小,而腐蚀则缩小明亮物体的大小。(对于暗的物体则是相反的)。
膨胀只是简单的找到相邻的最大值。//dilation.fs#version 120uniform sampler2D sampler0;uniform vec2 tc_offset[9];void main(void){vec4 sampler[9];//find the max valuevec4 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 120uniform 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 120uniform 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//-18 -1//-1 -1 -1gl_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 transformgl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;// eye-space normalN = gl_NormalMatrix * gl_Normal;// eye-space light vectorvec4 V = gl_ModelViewMatrix * gl_Vertex;L = lightPos[0] - V.xyz;// Copy the primary colorgl_FrontColor = gl_Color;}这与之前只用顶点着色器不同的是,这里用varyings修饰的标识符N和L作为输出,在片段着色器中用一样的名称就可以访问到N,L。这种方式比之前使用纹理坐标作为输出的方式更容易理解,也不容易出错(试想不小心把L输出到textureCoord[1]中,但实际使用的是textureCoord[0], 不会产生编译错误,但得不到想要的结果)。下面是片段着色器代码://diffuse.fs#version 120varying 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 * CliVS有多个L的输出//3light.vs#version 120uniform vec3 lightPos[3];varying vec3 N, L[3];void main(void){//MVP transformgl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;//eye-space vec4 V = gl_ModelViewMatrix * gl_Vertex;//eye-space noraml vectorN = gl_NormalMatrix * gl_Normal;for (int i = 0; i < 3; ++i){L[i] = lightPos[i] - V.xyz;}//primary vectorgl_FrontColor = gl_Color;}//3light.fs#version 120varying 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));//diffusegl_FragColor.rgb += gl_Color.rgb * lightCol[i] * NdotL;//specularif (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/openglexOpenGL超级宝典 第4版 中文版PDF+英文版+源代码 见 http://www.linuxidc.com/Linux/2013-10/91413.htmOpenGL编程指南(原书第7版)中文扫描版PDF 下载 http://www.linuxidc.com/Linux/2012-08/67925.htmOpenGL 渲染篇 http://www.linuxidc.com/Linux/2011-10/45756.htmUbuntu 13.04 安装 OpenGL http://www.linuxidc.com/Linux/2013-05/84815.htmOpenGL三维球体数据生成与绘制【附源码】 http://www.linuxidc.com/Linux/2013-04/83235.htmUbuntu下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本文永久更新链接地址:http://www.linuxidc.com/Linux/2015-02/114041.htm