首页 > 代码库 > GLSL/C++ 实现滤镜效果

GLSL/C++ 实现滤镜效果

入门效果之浮雕

"浮雕"图象效果是指图像的前景前向凸出背景。常见于一些纪念碑的雕刻上。要实现浮雕事实上很easy。我们把图象的一个象素和左上方的象素进行求差运算。并加上一个灰度。这个灰度就是表示背景颜色。这里我们设置这个插值为128 (图象RGB的值是0-255)。同一时候,我们还应该把这两个颜色的差值转换为亮度信息.否则浮雕图像会出现彩色。

    "precision mediump float;      \n"
	"varying vec2 v_texCoord;      \n"
	"uniform sampler2D s_baseMap;     \n"
	"uniform vec2 TexSize;            \n"
	"void main()                 \n"
	"{                             \n"
	"   vec2 tex =v_texCoord;   \n"
	"	vec2 upLeftUV = vec2(tex.x-1.0/TexSize.x,tex.y-1.0/TexSize.y);           \n"
	"	vec4 curColor = texture2D(s_baseMap,v_texCoord);                           \n"
	"	vec4 upLeftColor = texture2D(s_baseMap,upLeftUV);                  \n"
	"	vec4 delColor = curColor - upLeftColor;                           \n"
	"	float h = 0.3*delColor.x + 0.59*delColor.y + 0.11*delColor.z;                  \n"
	"   vec4 bkColor = vec4(0.5, 0.5, 0.5, 1.0);                   \n"
	"	gl_FragColor = vec4(h,h,h,0.0) +bkColor;                             \n"
	"}                           \n";

C++版

void relief(int x,int y,BYTE *pre,BYTE *now,int Stride,int index){
	int upperleft;
	upperleft=(y-1)*Stride+4*(x-1);
	Vec4 Pixel,NowPixel;
	NowPixel.SetPixel(pre,index);
	Pixel.SetPixel(pre,upperleft);
	Pixel=NowPixel-Pixel;
	BeGray(Pixel);
	Pixel.GetPixelToNow(now,index);
}

技术分享   技术分享


入门效果之马赛克

接下来我们完毕一个更加常见的效果—马赛克.图片的马赛克就是把图片的一个相当大小的区域用同一个点的颜色来表示.能够觉得是大规模的减少图像的分辨率,而让图像的一些细节隐藏起来, 比方电视中要秀一下某个罪犯的身材,却又不能展示他的脸,这个时候我们就能够给他的脸加一个马赛克.

用HLSL代码实现马赛克是很easy的,可是相同的,我们须要一些额外的步骤,第一步就是先把纹理坐标转换成图像实际大小的整数坐标.接下来,我们要把图像这个坐标量化---比方马赛克块的大小是8x8象素。那么我们能够用下列方法来得到马赛克后的图像採样值,如果[x.y]为图像的整数坐标:

[x,y]mosaic = [ int(x/8)*8 , int(y/8)*8].

    "precision mediump float;    \n"
	"varying vec2 v_texCoord;    \n"
	"uniform sampler2D s_baseMap;\n"
	"uniform vec2 TexSize;       \n"
	"vec2 mosaicSize = vec2(8,8);\n"
	"void main()                 \n"
	"{                           \n"
	"	vec2 intXY = vec2(v_texCoord.x*TexSize.x, v_texCoord.y*TexSize.y);   \n"
	"	vec2 XYMosaic = vec2(floor(intXY.x/mosaicSize.x)*mosaicSize.x,floor(intXY.y/mosaicSize.y)*mosaicSize.y);  \n"
	"	vec2 UVMosaic = vec2(XYMosaic.x/TexSize.x,XYMosaic.y/TexSize.y);     \n"
	"	vec4 baseMap = texture2D(s_baseMap,UVMosaic);                        \n"
	"	gl_FragColor = baseMap;                                              \n"
	"}                                                                       \n";

C++ 版

void Mosaic(int x,int y,BYTE *pre,BYTE *now,int Stride,int index){
	Vec4 Pixel,Pixel_UpperLeft;
	Pixel.SetPixel(pre,index);
	if (x%8==0&&y%8==0)
	{
		Pixel.GetPixelToNow(now,index);
	} 
	else
	{
		int tmpX,tmpY;
		tmpX=x/8*8;
		tmpY=y/8*8;
		int index_UpperLeft;
		index_UpperLeft=tmpY*Stride+4*(tmpX);
		Pixel_UpperLeft.SetPixel(pre,index_UpperLeft);
		Pixel_UpperLeft.GetPixelToNow(now,index);
	}
}


技术分享


读者可能会发现这个马赛克太普通了,确实它不够新颖,以下我们来改良一下。我们希望达到这样一个效果:马赛克区域不是方的,而是圆的,圆形区域以外,我们用图像原来的颜色覆盖。这样我们须要改变一下代码。

首先求出原来马赛克区域的正中心(原来是左上角):然后计算图像採样点到这个中心的距离,假设在马赛克圆内。就用区域的中心颜色,否则就用原来的颜色。

改良后的代码例如以下。这里我们把马赛克区域大小调节成16x16。这样效果更明显。

    "precision highp float;            \n"
	"varying vec2 v_texCoord;            \n"
	"uniform sampler2D s_baseMap;        \n"
	"uniform vec2 TexSize;               \n"
	"vec2 mosaicSize = vec2(8,8);      \n"
	"void main()                         \n"
	"{                                   \n"
	"	vec2 intXY = vec2(v_texCoord.x*TexSize.x, v_texCoord.y*TexSize.y);    \n"
	"	vec2 XYMosaic = vec2(floor(intXY.x/mosaicSize.x)*mosaicSize.x,floor(intXY.y/mosaicSize.y)*mosaicSize.y) + 0.5*mosaicSize; \n"
	"	vec2 delXY = XYMosaic - intXY;   \n"
	"	float delL = length(delXY);      \n"
	"	vec2 UVMosaic = vec2(XYMosaic.x/TexSize.x,XYMosaic.y/TexSize.y); \n"
	"	vec4 _finalColor;                \n"
	"	if(delL< 0.5*mosaicSize.x)       \n"
	"		_finalColor = texture2D(s_baseMap,UVMosaic);  \n"
	"	else                             \n"
	"		_finalColor = texture2D(s_baseMap,v_texCoord);  \n"
	"	gl_FragColor = _finalColor;      \n"
	"}                                   \n"  ;

C++版

void Mosaic_Point(int x,int y,BYTE *pre,BYTE *now,int Stride,int index){
	Vec4 Pixel,Pixel_UpperLeft;
	Pixel.SetPixel(pre,index);
	int i,j;
	i=x%8;
	j=y%8;
	double dist=sqrt(double((4-i)*(4-i)+(4-j)*(4-j)));
	if (dist>4)
	{
		Pixel.GetPixelToNow(now,index);
	} 
	else
	{
		int tmpX,tmpY;
		tmpX=x/8*8;
		tmpY=y/8*8;
		int index_UpperLeft;
		index_UpperLeft=tmpY*Stride+4*(tmpX);
		Pixel_UpperLeft.SetPixel(pre,index_UpperLeft);
		Pixel_UpperLeft.GetPixelToNow(now,index);
	}
}


技术分享

图:  改良后的马赛克效果

进阶效果之锐化模糊

以上两个效果相对照较简单,姑且称之为入门效果。 它并没实用到太多数字图像处理或者信号处理方面的知识。

接下来我们要介绍略微复杂一点的效果。第一个就是图像的模糊和锐化。

图像的模糊又成为图像的平滑(smoothing),我们知道人眼对高频成分是非常敏感的。假设在一个亮度连续变化的图像中,突然出现一个亮点,那么我们非常easy察觉出来,类似的,假设图像有个突然的跳跃—明显的边缘,我们也是非常easy察觉出来的。

这些突然变化的分量就是图像的高频成分。

人眼一般是通过低频成分来辨别轮廓,通过高频成分来感知细节的(这也是为什么照片分辨率低的时候,人们仅仅能辨认出照片的大概轮廓,而看不到细节)。可是这些高频成分通常也包括了噪声成分。图像的平滑处理就是滤除图像的高频成分。

那么怎样才干滤除图像的高频成分呢?我们先来介绍一下图像数字滤波器的概念。

简单通俗的来说,图像的数字滤波器事实上就是一个n x n的数组(数组中的元素成为滤波器的系数或者滤波器的权重。n称为滤波器的阶)。

对图像做滤波的时候。把某个像素为中心的nxn个像素的值和这个滤波器做卷积运算(也就是相应位置上的像素和相应位置上的权重的乘积累加起来),公式例如以下

 技术分享当中x , y 为当前正在处理的像素坐标。

通常情况下,我们滤波器的阶数为3已经足够了,用于模糊处理的3x3滤波器例如以下

                           技术分享     

经过这种滤波器。事实上就是等效于把一个像素和周围8个像素一起求平均值,这是很合理的---等于把一个像素和周围几个像素搅拌在一起—自然就模糊了。


"precision mediump float; 							   \n"
	"vec4 dip_filter(mat3 _filter, sampler2D _image, vec2 _xy, vec2 texSize)               \n"
	"{                                                									  \n"
	"	mat3 _filter_pos_delta_x=mat3(vec3(-1.0, 0.0, 1.0), vec3(0.0, 0.0 ,1.0) ,vec3(1.0,0.0,1.0));            \n"
	"   mat3 _filter_pos_delta_y=mat3(vec3(-1.0,-1.0,-1.0),vec3(-1.0,0.0,0.0),vec3(-1.0,1.0,1.0));              \n"
	"	vec4 final_color = vec4(0.0, 0.0, 0.0, 0.0);                                      \n"
	"	for(int i = 0; i<3; i++)                                                          \n"
	"	{                                                                                 \n"
	"		for(int j = 0; j<3; j++)                                                      \n"
	"		{                                                                             \n"
	"			vec2 _xy_new = vec2(_xy.x + _filter_pos_delta_x[i][j], _xy.y + _filter_pos_delta_y[i][j]); \n"
	"			vec2 _uv_new = vec2(_xy_new.x/texSize.x, _xy_new.y/texSize.y);            \n"
	"			final_color += texture2D(_image,_uv_new) * _filter[i][j];                 \n"
	"		}																			  \n"
	"	}																				  \n"
	"	return final_color;																  \n"
	"}																					  \n"
	"varying vec2 v_texCoord;															  \n"
	"uniform vec2 TexSize;    															  \n"
	"uniform sampler2D s_baseMap;														  \n"
	"void main()																		  \n"
	"{																					  \n"
	"	vec2 intXY = vec2(v_texCoord.x * TexSize.x, v_texCoord.y * TexSize.y);   		  \n"
	"	mat3 _smooth_fil = mat3(1.0/9.0,1.0/9.0,1.0/9.0,										  \n"
	"							1.0/9.0,1.0/9.0,1.0/9.0,										  \n"
	"							1.0/9.0,1.0/9.0,1.0/9.0);										  \n"
	"   vec4 tmp = dip_filter(_smooth_fil, s_baseMap, intXY, TexSize);"
	"	gl_FragColor = tmp;                                                  			  \n"
	"}																					  \n";

C++版

void dip_filter(int x,int y,BYTE *pre,BYTE *now,int Stride,int index)
{
	int dir_x[3][3]={-1,0,1,-1,0,1,-1,0,1};
	int dir_y[3][3]={1,1,1,0,0,0,-1,-1,-1};
	int tmpX,tmpY;
	Vec4 Pixel,PrePixel;
	double num=sqrt(2.0);
	double filter[3][3]={-1,0,1,
							-num,0,num,
							-1,0,1};
	Pixel.Clear();
	for (int i=0;i<3;i++)
	{
		for (int j=0;j<3;j++)
		{
			tmpX=x+dir_x[i][j];
			tmpY=y+dir_y[i][j];
			int tmp=tmpY*Stride+4*(tmpX);
			PrePixel.SetPixel(pre,tmp);
			Pixel+=PrePixel*filter[i][j];
		}
	}
	BeGray(Pixel);
	Pixel.GetPixelToNow(now,index);
}


以上的模糊滤波器称为BOX滤波器,是最简单的滤波器。假设考虑到离开中心像素的距离对滤波器系数的影响,我们通常採用更加合理的滤波器---高斯滤波器—一种通过2维高斯採样得到的滤波器。它的模板例如以下:

技术分享

非常easy看出来。离开中心越远的像素。权重系数越小。

对于锐化操作,经常使用的锐化模板是拉普拉斯(Laplacian)模板,这个模板定义例如以下:

技术分享

easy看出拉普拉斯模板的作法:先将自身与周围的8个象素相减,表示自身与周围象素的区别。再将这个区别加上自身作为新象素的灰度。

可见,假设一片暗区出现了一个亮点,那么锐化处理的结果是这个亮点变得更亮,这就增强了图像的细节。

以下三副图分别表示了经过BOX滤波。高斯滤波和拉普拉斯滤波后的图像

技术分享 技术分享 技术分享

BOX 模糊                  高斯模糊                    拉普拉斯锐化

高斯模糊和拉普拉斯锐化效果的GLSL和BOX的代码基本一致,就是filter的系数不同。这里不在列出。

通过这个两个效果。我们介绍了图像的滤波操作,这种操作,也成为模板操作。它实现了一种邻域运算(Neighborhood Operation),即某个象素点的结果灰度不仅和该象素灰度有关。并且和其邻域点的值有关。模板运算在图象处理中常常要用到。能够看出。它是一项很耗时的运算。有一种优化的方法称为可分离式滤波,就是使用两个pass来进行x/y方向分别滤波,能让运算次数大大降低。

并且滤波器阶数越高,优势越明显。

数字图像滤波的时候,相同还须要注意边界像素的问题,只是幸好,GLSL能让边界处理更加的透明和简单。


进阶效果之描边效果

相对浮雕效果来说。描边(边缘检測)的代码并不复杂多少,仅仅是在理论上相对来说略微复杂一点,并且效果看上去更加的讨人喜欢一些。

我们知道 ,假设在图像的边缘处。灰度值肯定经过一个跳跃。我们能够计算出这个跳跃,并对这个值进行一些处理,来得到边缘浓黑的描边效果。

首先我们能够考虑对这个象素的左右两个象素进行差值。得到一个差量。这个差量越大,表示图像越处于边缘,并且这个边缘应该左右方向的,相同我们能得到上下方向和两个对角线上的图像边缘。这样我们构造一个滤波器

技术分享

经过这个滤波器后。我们得到的是图像在这个象素处的变化差值,我们把它转化成灰度值,并求绝对值(差值可能为负),然后我们定义差值的绝对值越大的地方越黑(边缘显然是黑的)。否则越白,我们便得到例如以下的效果:

技术分享

图:铅笔描边效果

该效果的代码例如以下(当中dip_filter函数代码同上):

	"precision mediump float; 							   \n"
	"vec4 dip_filter(mat3 _filter, sampler2D _image, vec2 _xy, vec2 texSize)               \n"
	"{                                                									  \n"
	"	mat3 _filter_pos_delta_x=mat3(vec3(-1.0, 0.0, 1.0), vec3(0.0, 0.0 ,1.0) ,vec3(1.0,0.0,1.0));            \n"
	"   mat3 _filter_pos_delta_y=mat3(vec3(-1.0,-1.0,-1.0),vec3(-1.0,0.0,0.0),vec3(-1.0,1.0,1.0));              \n"
	"	vec4 final_color = vec4(0.0, 0.0, 0.0, 0.0);                                      \n"
	"	for(int i = 0; i<3; i++)                                                          \n"
	"	{                                                                                 \n"
	"		for(int j = 0; j<3; j++)                                                      \n"
	"		{                                                                             \n"
	"			vec2 _xy_new = vec2(_xy.x + _filter_pos_delta_x[i][j], _xy.y + _filter_pos_delta_y[i][j]); \n"
	"			vec2 _uv_new = vec2(_xy_new.x/texSize.x, _xy_new.y/texSize.y);            \n"
	"			final_color += texture2D(_image,_uv_new) * _filter[i][j];                 \n"
	"		}																			  \n"
	"	}																				  \n"
	"	return final_color;																  \n"
	"}																					  \n"
	"varying vec2 v_texCoord;															  \n"
	"uniform vec2 TexSize;    															  \n"
	"uniform sampler2D s_baseMap;														  \n"
	"void main()																		  \n"
	"{																					  \n"
	"	vec2 intXY = vec2(v_texCoord.x * TexSize.x, v_texCoord.y * TexSize.y);   		  \n"
	"	mat3 _smooth_fil = mat3(-0.5,-1.0,0.0,										  \n"
	"							-1.0,0.0,1.0,										  \n"
	"							 0.0,1.0,0.5);										  \n"
	"   vec4 delColor = dip_filter(_smooth_fil, s_baseMap, intXY, TexSize);           \n"
	"   float deltaGray = 0.3*delColor.x + 0.59*delColor.y + 0.11*delColor.z;          \n"
	"   if(deltaGray < 0.0) deltaGray = -1.0 * deltaGray;                             \n"
	"   deltaGray = 1.0 - deltaGray;                                                  \n"
	"	gl_FragColor = vec4(deltaGray,deltaGray,deltaGray,1.0);                        \n"
	"}																					  \n";

C++版

void Gaussian_filter(int x,int y,BYTE *pre,BYTE *now,int Stride,int index)
{
	int dir_x[3][3]={-1,0,1,-1,0,1,-1,0,1};
	int dir_y[3][3]={1,1,1,0,0,0,-1,-1,-1};
	int tmpX,tmpY;
	Vec4 Pixel,PrePixel;
	double filter[3][3]={1.0/16,2.0/16,1.0/16,
		2.0/16,4.0/16,2.0/16,
		1.0/16,2.0/16,1.0/16};
	Pixel.Clear();
	for (int i=0;i<3;i++)
	{
		for (int j=0;j<3;j++)
		{
			tmpX=x+dir_x[i][j];
			tmpY=y+dir_y[i][j];
			int tmp=tmpY*Stride+4*(tmpX);
			PrePixel.SetPixel(pre,tmp);
			Pixel+=PrePixel*filter[i][j];
		}
	}
	//BeGray(Pixel);
	Pixel.GetPixelToNow(now,index);
}




上面演示的效果种用到的模板就是一种边缘检測器,在信号处理上是一种基于梯度的滤波器。又称边缘算子。梯度是有方向的,和边沿的方向总是正交(垂直)的,在上面的代码中,我们採用的就是一个梯度为45度方向模板。它能够检測出135度方向的边沿。

以上是简单的边缘检測算子。更加严格的。我们能够採样Sobel算子,Sobel 算子有两个,一个是检測水平边沿的 技术分享,还有一个是检測垂直平边沿的技术分享。相同,Sobel算子还有一种形式是各向同性Sobel算子。也有两个,一个是检測水平边沿的技术分享,还有一个是检測垂直边沿的技术分享。各向同性Sobel算子和普通Sobel算子相比,它的位置加权系数更为准确,在检測不同方向的边沿时梯度的幅度一致。读者能够自行尝试Sobel算子的效果,仅仅要改动pencil_filter的值就能够了。

高级效果之伪 HDR/Blow

HDR和Blow在如今主流游戏中是很时髦的效果。

所谓HDR就是高动态范围的意思,我们知道。在普通的显示器和位图里。每通道都是8-bit,也就是说RGB分量的范围都是0-255,这用来表示现实中的颜色显然是远远不够的,现实中的图像的动态范围远远大的多,那么怎样在现有的显示设备里尽可能的保持更大的动态范围,并且让它能更符合人眼的习惯就成了图形学研究的一个热点。通常真正的HDR的做法都是採用浮点纹理。把渲染运算的过程中,我们使用16bit的动态范围来保存运算结果,然后我们对运算结果进行分析。求出这个图像的中间灰度值,然后对图像进行调整映射到LDR的设备中。可是这种算法有两个很耗资源的过程,当中一个是浮点纹理,另外一个就是求图像中间灰度(通常情况是把图像不停的渲染到RenderTarget。每渲染一次,图像大小缩小一半。直到缩小到1x1大,一个1024 x1024的图像须要渲染10次!)。因此尽管HDR的效果很美丽。可是眼下还是仅仅有为数不多的游戏採用了这种算法,大部分都是採用的伪HDR+blow效果。

伪HDR效果一般是又一次调整图像的亮度曲线。让亮的更亮,暗的更暗一些,而Blow效果则是图像的亮度扩散开来。产生非常柔的效果。

在这里我们採用一个二次曲线来又一次调整图像的亮度,这个曲线的方程是

   x [ (2-4k) x + 4k-1 ).

K的取值范围为0.5 – 2.0

经过这个公式调整以后,图像上亮的区域将更加的亮。而且总体亮度会提高。那么接下来,我们怎样使图像的亮度扩散开来呢?一种可行的方法就是对场景图像做一次downsample。

把它变成原来的1/4次大小,那样就等于亮度往外扩散了4x4个象素的区域。

	"precision mediump float;     \n"
	"varying vec2 v_texCoord;	  \n"
	"uniform sampler2D s_baseMap; \n"
	"uniform float k;					  \n"
	"vec4 xposure(vec4 _color, float gray, float ex)  \n"
	"{							  \n"
	"	float b = (4.0*ex - 1.0);     \n"
	"	float a = 1.0 - b;          \n"
	"	float f = gray*(a*gray + b); \n"
	"	return f*_color;		  \n"
	"}							  \n"
	"void main()				  \n"
	"{							  \n"
	"	vec4 _dsColor = texture2D(s_baseMap, v_texCoord); \n"
	"	float _lum = 0.3*_dsColor.x + 0.59*_dsColor.y;    \n"
	"	vec4 _fColor = texture2D(s_baseMap, v_texCoord);  \n"
	"	gl_FragColor = xposure(_fColor, _lum, k);         \n"
	"}                                                    \n";

C++版

void HDR(int x,int y,BYTE *pre,BYTE *now,int index,double k){
	Vec4 Pixel;
	double GrayPixel;
	Pixel.SetPixel(pre,index);
	GrayPixel=GetGray(Pixel)/255.0;
	double b=(4*k-1.0);
	double a=1-b;
	double f=GrayPixel*(a*GrayPixel+b);
	Pixel*=f;
	OverFlow(Pixel);
	Pixel.GetPixelToNow(now,index);
}


以下是原图像和经过处理后图像的对照:

技术分享 技术分享 技术分享

原图                      k = 1.1                      k = 1.6

图:经过伪HDR+Blow处理过的图像和原图的对照

高级效果之水彩化

真正的水彩效果在shader中是比較难实现的。它须要进行中值滤波后累加等一些操作,还须要处理NPR中的笔触一类的概念。本文绕开这些概念,仅仅从视觉效果上能尽量模拟出水彩的画的那种感觉来。

我们知道,水彩画一个最大的特点是水彩在纸上流动扩散后会和周围的颜色搅拌在一起,另外一个特点就是水彩一般会形成一个个的色块,过渡不像照片那样的平滑。

针对这两个特点。

我们能够设计这种一个算法来模拟水彩画的效果。

我们能够採用噪声纹理的方式。既事先计算好一个nxn的随机数数组,作为纹理传递给Pixel shader,这样在Pixel Shader里我们就能获得随机数了。得到随机数后,我们将随机数映射成纹理坐标的偏移值,就能模拟出色彩的扩散了。典型的噪声纹理是这个样子的:

技术分享

图:噪声纹理

接下来我们须要处理色块,我们对颜色的RGB值分别进行量化,把RGB分量由原来的8bit量化成比特数更低的值。这样颜色的过渡就会显得不那么的平滑,而是会呈现出一定的色块效果。

通过以上两步处理后,我们得到的图像依旧有非常多的细节。尤其是第一步处理中产生的非常多细节噪点,非常自然的我们就想到通过平滑模糊的方式来过滤掉这些高频噪声成分。

算法设计好了,接下来看看我们怎样在RenderMonkey里实现这个算法。

类似上一个效果。我们须要两个pass来完毕这个算法,第一个pass叫flow pass,模拟颜色的流动和处理颜色的量化。

第二个pass叫Gauss pass。也就是前面提到的高斯模糊算法。

我们的重点在第一个pass。

在模拟扩散的pass中,我们相同须要一个RenderTarget。以把结果保存在当中以便兴许处理,然后还须要一个噪声纹理来产生随机数。详细代码例如以下:

	"precision mediump float;    \n"
	"varying vec2 v_texCoord;    \n"
	"uniform sampler2D s_baseMap;  \n"
	"uniform vec2 TexSize;       \n"
	"float _waterPower = 40.0;     \n"
	"float _quatLevel = 5.0;       \n"
	"vec4 quant(vec4 _cl, float n)  \n"
	"{                            \n"
	"	_cl.x = floor(_cl.x*255.0/n)*n/255.0;  \n"
	"	_cl.y = floor(_cl.y*255.0/n)*n/255.0;  \n"
	"	_cl.z = floor(_cl.z*255.0/n)*n/255.0;  \n"
	"	return _cl;                            \n"
	"}                                         \n"
	"void main()                               \n"
	"{                                         \n"
	"	vec4 noiseColor = _waterPower*texture2D(s_baseMap,v_texCoord);           \n"
	"	vec2 newUV =vec2 (v_texCoord.x + noiseColor.x/TexSize.x,v_texCoord.y + noiseColor.y/TexSize.y);  \n"
	"	vec4 _fColor = texture2D(s_baseMap,newUV);                 \n"
	"	gl_FragColor = quant(_fColor, 255.0/pow(2,_quatLevel));   \n"
	"}                                                \n";

C++版

void WaterFilter(int x,int y,BYTE *pre,BYTE *now,int Stride,int index,double _quatLevel,double _waterPower,int width,int height){
	Vec4 nowPixel,RoundPixel;
	int indexRound;
	double Level;
	nowPixel.SetPixel(pre,index);
	int rx=rand()%2,ry=rand()%2;
	indexRound=(y+rx)*Stride+(x+ry)*4;
	RoundPixel.SetPixel(pre,indexRound);
	Level=255/pow(2,_quatLevel);
	RoundPixel.r=floor(RoundPixel.r/Level)*Level;
	RoundPixel.g=floor(RoundPixel.g/Level)*Level;
	RoundPixel.b=floor(RoundPixel.b/Level)*Level;
	OverFlow(RoundPixel);
	RoundPixel.GetPixelToNow(now,index);
}


代码中的_quatLevel用来表示对图像的量化比特数。值越小。色块越明显。比較合理的取值范围是2-6。_waterPower则表示图像颜色扩散范围,取值范围在8-64之间的效果比較好。

以下是经过水彩画处理后的图像:

技术分享 技术分享

 图:水彩画效果。左图量化比特数为6比特,扩散范围为20象素。

                 右图量化比特数为5比特,扩散范围为40象素


最后贴个C++版用到的函数

struct Vec4{
	double r,g,b,a;
	Vec4 (){
		this->r=0;
		this->g=0;
		this->b=0;
		this->a=0;
	}
	Vec4(double r,double g,double b){
		this->r=r;
		this->g=g;
		this->b=b;
	}
	Vec4 operator+(const double one) const{
		return Vec4(r+one,g+one,b+one);
	}
	Vec4 operator+(const Vec4& rhs)const{
		return Vec4(r+rhs.r,g+rhs.g,b+rhs.b);
	}
	Vec4& operator+=(const double one){
		r+=one;
		g+=one;
		b+=one;
		return *this;
	}
	Vec4& operator+=(const Vec4& rhs){
		r+=rhs.r;
		g+=rhs.g;
		b+=rhs.b;
		return *this;
	}
	Vec4 operator-(const double one) const{
		return Vec4(r-one,g-one,b-one);
	}
	Vec4 operator-(const Vec4& rhs)const{
		return Vec4(r-rhs.r,g-rhs.g,b-rhs.b);
	}
	Vec4& operator-=(const double one){
		r-=one;
		g-=one;
		b-=one;
		return *this;
	}
	Vec4 operator*(const double one) const{
		return Vec4(r*one,g*one,b*one);
	}
	Vec4& operator*=(const double one){
		r*=one;
		g*=one;
		b*=one;
		return *this;
	}
	Vec4 operator/(const double one) const{
		return Vec4(r/one,g/one,b/one);
	}
	Vec4& operator/=(const double one){
		r/=one;
		g/=one;
		b/=one;
		return *this;
	}
	void Clear(){
		r=g=b=0;
	}
	void SetPixel(BYTE *pre,int index){
		this->r=pre[index];
		this->g=pre[index+1];
		this->b=pre[index+2];
		this->a=pre[index+3];
	}
	void SetPixel(double RGB){
		this->r=RGB;
		this->g=RGB;
		this->b=RGB;
	}
	void SetPixel(double R,double G,double B){
		this->r=R;
		this->g=G;
		this->b=B;
	}
	void GetPixelToNow(BYTE *now,int index){
		now[index]=this->r;
		now[index+1]=this->g;
		now[index+2]=this->b;
		
	}
};
void OverFlow(Vec4 &Pixel){
	if (Pixel.r>255.0)
	{
		Pixel.r=255;
	}
	else if (Pixel.r<0.0)
	{
		Pixel.r=-Pixel.r;
	}

	if (Pixel.g>255.0)
	{
		Pixel.g=255;
	}
	else if (Pixel.g<0.0)
	{
		Pixel.g=-Pixel.g;
	}

	if (Pixel.b>255.0)
	{
		Pixel.b=255;
	}
	else if (Pixel.b<0.0)
	{
		Pixel.b=-Pixel.b;
	}
}
void OverFlow(double &Pixel){
	if (Pixel>255.0)
	{
		Pixel=255;
	}
	else if (Pixel<0.0)
	{
		Pixel=-Pixel;
	}
}
void ToGray(Vec4 &Pixel){
	double detaGray;
	detaGray=Pixel.r*0.3+Pixel.g*0.59+Pixel.b*0.11;
	Pixel.SetPixel(detaGray);
	OverFlow(Pixel);
}
double GetGray(Vec4 Pixel){
	double detaGray;
	detaGray=Pixel.r*0.3+Pixel.g*0.59+Pixel.b*0.11;
	OverFlow(detaGray);
	return detaGray;
}
void BeGray(Vec4 &Pixel){
	double detaGray;
	detaGray=Pixel.r*0.3+Pixel.g*0.59+Pixel.b*0.11;
	
	if (detaGray<0.0)
	{
		detaGray=-detaGray;
	}
	else if (detaGray>255.0)
	{
		detaGray=255;
	}
	detaGray=255-detaGray;
	Pixel.SetPixel(detaGray);
}void Solve(){    BYTE *pre = (BYTE*)m_srcImg.Scan0;    BYTE *now = (BYTE*)m_copySrcImg.Scan0;    //函数在这边调用就可以。

    int index=0,Stride=m_copySrcImg.Stride;    int width=m_pCopyImg->GetWidth();    int height=m_pCopyImg->GetHeight();    index=Stride+4;    for (int j=1;j<m_copySrcImg.Height-1;j++)    {        for (int i=1;i<m_copySrcImg.Width-1;i++)        {           //Gaussian_filter(i,j,pre,now,Stride,index);            //dip_filter(i,j,pre,now,Stride,index);            //relief(i,j,pre,now,Stride,index);            //Mosaic(i,j,pre,now,Stride,index);            //Mosaic_Point(i,j,pre,now,Stride,index);            //HDR(i,j,pre,now,index,1.1);            //WaterFilter(i,j,pre,now,Stride,index,10,40,width,height);                index+=4;        }        index+=8;    }    //调用函数。

}



GLSL/C++ 实现滤镜效果