图像处理
图像处理是一种独立于顶点着色器的特殊处理程序。在不使用片段着色器的情况下绘制场景之后,可以按照各种方式应用卷积核。
为了保持着色器的简洁,使用硬件加速,我们限制总卷积的大小为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://my.lmcjl.com/post/16631.html
4 评论