分享

关于Shader着色器的使用(这个是GL通用的,用什么开发设置都一样) | Java与Ripple怎么玩?

 LVADDIE 2015-06-04

什么是Shader呢?简单的说,Shader(着色器)是一段能够针对3D对象进行操作、并被GPU所执行的程序。通过这些程序,程序员就能够获得绝大部分想要的3D图形效果。在一个3D场景中,一般包含多个Shader。这些Shader中有的负责对3D对象表面进行处理,有的负责对3D对象的纹理进行处理。早在微软发布DirectX 8时,Shader Model的概念就出现在其中了,并根据操作对象的不同被分为对顶点进行各种操作的Vertex Shader(顶点渲染引擎)和对像素进行各种操作的Pixel Shader(像素渲染引擎)。

Shader的意义在于,它可以利用GPU加速的方式,为OpenGL和OpenGLES中的纹理进行程序员设定的样式改变,从而实现用户需要的任意效果。

下文以cocos2d-x为例,介绍一下基本的使用模式(好吧,因为此文伪原创,所以用这货,等过一阵翻过手来写个libgdx和lgame(等改版,目前使用的GL 1.x不支持,下个版本换2.0)的,反正能直接照搬,又增加两篇原创wink

首先是一张效果图,左上的是原图,第二个是灰色效果,第三是彩色横条效果,第四是高亮,第五是边缘检测,第六是冰花效果,第七是卡通渲染,第八是石刻效果,第九是高斯模糊。

1425024184189533

1.灰色效果

灰色效果是非常简单的一种Shader效果,对于这个效果更多的是用来测试基本的Shader效果能否实现。

灰色效果的设计理念就是使原图片的RGB的值按比例减少。我们都知道一般灰色是128,128,128.我们通过把RGB的值降低,可以明显的使图片色调变暗,从而实现变灰效果。

具体操作就是把原来图片的alpha通道保留,RBG点成一个比例。

Gray.fsh

varyingvec4v_fragmentColor;
varyingvec2v_texCoord;
uniformsampler2DCC_Texture0;
voidmain()
{
vec4v_orColor=v_fragmentColor*texture2D(CC_Texture0,v_texCoord);
floatgray=dot(v_orColor.rgb,vec3(0.3,0.3,0.3));
gl_FragColor=vec4(gray,gray,gray,v_orColor.a);
}

一般来说,一个Shader效果可以有一个vertexshader一个fragmentshader.上面的Gray.fsh就是我们实现变灰的代码,而下面的Gray.vsh则决定了位置。由于变灰效果网上很多大牛都写过,所以不做过多讲解。有一点就是下面的关于变灰后图片的位置网上有个代码使用了CC_MVPMatrix导致图片位置不对。

Gray.vsh

attributevec4a_position;
attributevec2a_texCoord;
attributevec4a_color;
varyingvec4v_fragmentColor;
varyingvec2v_texCoord;
voidmain()
{
gl_Position=CC_PMatrix*a_position;
v_fragmentColor=a_color;
v_texCoord=a_texCoord;
}

这个vertexshader是我看到后来别人回复修改过后的,大家使用我的就没有问题了,后面我更多的会针对fragmentshader进行讲解,毕竟这是实现效果的主要部分。

2.彩色横条效果

彩色横条效果是Cocos2d-x-iPhone和Cocos2d-x3.0里面都自带的一个shader效果。这个效果是从McCollough效果上衍生而来的,对历史和理论感兴趣的可以下载下我的App具体学习。如果我没记错,McCollough好像是从人类视觉感知的一个现象衍生出来的一个效果。

#ifdefGL_ES
precisionlowpfloat;
#endif
varyingvec2v_texCoord;
uniformsampler2DCC_Texture0;
vec4colors[8];
voidmain(void)
{
colors[0]=vec4(1,0,0,1);
colors[1]=vec4(0,1,0,1);
colors[2]=vec4(0,0,1,1);
colors[3]=vec4(0,1,1,1);
colors[4]=vec4(1,0,1,1);
colors[5]=vec4(1,1,0,1);
colors[6]=vec4(1,1,1,1);
inty=int(mod(gl_FragCoord.y/3.0,7.0));
gl_FragColor=colors[y]*texture2D(CC_Texture0,v_texCoord);
}

这个效果的设计理念也是很清晰的。首先我们把原图的y轴色彩抽出,然后强制性的打上一些颜色,这样就会形成一个个的彩色横条。

然后我们把原图的材质给乘到打上横条以后的色彩上,就形成了彩色横条效果。

3.高亮效果

这个效果我暂时压后,因为这个效果里面涉及到了高斯模糊,所以在后面讲解会更加方便。

4.边缘检测

边缘检测的Shader效果也是Cocos2d-x里面本身就带有的一个Shader效果。

具体上的操作是使用了索贝尔算子,来计算出2D图形空间上梯度。在技术上,它是一种离散性差分算子,用来运算图像亮函数的梯度近似值。

当然,索贝尔算子仅仅是边缘检测一种方法之一,另外还有罗盘算子,Canny算子等等。

索贝尔算子包含了2组3*3的矩阵,分别为横向以及纵向,将它与图像做平面卷积,既可以亮度差分近似值。

假设A代表原始图形,上面的理论参考了wikipedia上的资料:http://en./wiki/Sobel_operator

然后我们来看具体实现,EdgeDetection.fsh.

#ifdefGL_ES
precisionmediumpfloat;
#endif
varyingvec4v_fragmentColor;
varyingvec2v_texCoord;
vec2resolution;
uniformsampler2DCC_Texture0;
floatlookup(vec2p,floatdx,floatdy)
{
vec2uv=p.xy+vec2(dx,dy)/resolution.xy;
vec4c=texture2D(CC_Texture0,uv.xy);
return0.2126*c.r+0.7152*c.g+0.0722*c.b;
}
voidmain(void)
{
//simplesobeledgedetection
resolution=vec2(1024.0,768.0);
vec2p=v_texCoord.xy;
//Gxmatrixmultiplication
floatgx=0.0;
gx+=-1.0*lookup(p,-1.0,-1.0);
gx+=-2.0*lookup(p,-1.0,0.0);
gx+=-1.0*lookup(p,-1.0,1.0);
gx+=1.0*lookup(p,1.0,-1.0);
gx+=2.0*lookup(p,1.0,0.0);
gx+=1.0*lookup(p,1.0,1.0);
//Gymatrixmultiplication
floatgy=0.0;
gy+=-1.0*lookup(p,-1.0,-1.0);
gy+=-2.0*lookup(p,0.0,-1.0);
gy+=-1.0*lookup(p,1.0,-1.0);
gy+=1.0*lookup(p,-1.0,1.0);
gy+=2.0*lookup(p,0.0,1.0);
gy+=1.0*lookup(p,1.0,1.0);

floatg=gx*gx+gy*gy;

gl_FragColor.xyz=vec3(1.-g);
gl_FragColor.w=1.;
}
//Thisshaderisreferredtococos2d-xofficialengineshader.
//Checkhttp://www./fordetailedinformation.

仔细看下,gx,gy的值就是根据索贝尔算子矩阵所得,最后的每个像素的梯度近似值也与理论相符。这也是为什么这个着色效果可以做出边缘检测的原因。

5.冰花效果

冰花效果是个很有意思的模糊效果,这个效果源于一个很有爱的国际友人在10年博客上发表的一篇博文(查看文章)。

后来我在学习模糊效果的时候,一个国外论坛上有人引用了这个简单有有效的效果。

简单的说,这个着色效果模仿了冰花玻璃的效果,原理是根据一个伪随机的向量平移了像素的位置。

给张大点的效果图,可以更清晰的看出这个效果(效果图稍后补充)。

看上去是一个很有意思的效果,不过这个效果的实现却是惊人的简单。

FrostBlur.fsh

#ifdefGL_ES
precisionmediumpfloat;
#endif
varyinglowpvec2v_texCoord;
uniformsampler2Du_texture;
floatrand(vec2co)
{
returnfract(sin(dot(co.xy,vec2(100,100)))+
cos(dot(co.xy,vec2(50,50)))*5.0);
}
voidmain()
{
vec2rnd=vec2(0.0);
rnd=vec2(rand(v_texCoord),rand(v_texCoord));
gl_FragColor=texture2D(u_texture,v_texCoord+rnd*0.02);
}

核心代码只有5行,其实就是平移了像素的位置,不过效果却不错。

6.卡通渲染

卡通渲染是一种去真实感的渲染方法,旨在使电脑生成的图像呈现出手绘的效果。渲染过程中一般会把常规光源的取值被逐一计算并投射到一小片独立的明暗区域上,产生卡通式的单调色彩。然后会有一个勾边的过程,用于突出物体。

当然,有些更精细的实现会得出更好效果。比如Unity就发表过一个卡通渲染的教程,中间包括采光,散光等一系列的处理(http://en./wiki/GLSL_Programming/Unity/Toon_Shading)。

现在我们来看下Cocos2d-x的实现。

celShading.fsh

 

#ifdefGL_ES
precisionmediumpfloat;
#endif
varyingvec4v_fragmentColor;
varyingvec2v_texCoord;
vec2resolution;
uniformsampler2DCC_Texture0;
#defineFILTER_SIZE2
#defineCOLOR_LEVELS7.0
#defineEDGE_FILTER_SIZE2
#defineEDGE_THRESHOLD0.05
vec4edgeFilter(inintpx,inintpy)
{
vec4color=vec4(0.0);
for(inty=-EDGE_FILTER_SIZE;y<=EDGE_FILTER_SIZE;++y)
{
for(intx=-EDGE_FILTER_SIZE;x<=EDGE_FILTER_SIZE;++x)
{
color+=texture2D(CC_Texture0,v_texCoord+vec2(px+x,py+y)/resolution.xy);
}
}
color/=float((2*EDGE_FILTER_SIZE+1)*(2*EDGE_FILTER_SIZE+1));
returncolor;
}
voidmain(void)
{
//Shade
resolution=vec2(1024.0,768.0);
vec4color=vec4(0.0);
for(inty=-FILTER_SIZE;y<=FILTER_SIZE;++y)
{
for(intx=-FILTER_SIZE;x<=FILTER_SIZE;++x)
{
color+=texture2D(CC_Texture0,v_texCoord+vec2(x,y)/resolution.xy);
}
}
color/=float((2*FILTER_SIZE+1)*(2*FILTER_SIZE+1));
for(intc=0;c<3;++c)
{
color[c]=floor(COLOR_LEVELS*color[c])/COLOR_LEVELS;
}
//Highlightedges
vec4sum=abs(edgeFilter(0,1)-edgeFilter(0,-1));
sum+=abs(edgeFilter(1,0)-edgeFilter(-1,0));
sum/=2.0;
if(length(sum)>EDGE_THRESHOLD)
{
color.rgb=vec3(0.0);
}
gl_FragColor=color;
}

这里实现总共分为两个部分,一个是阴影(shade),一个是勾边(highlightedge)。

其实打阴影的过程,就是我们前面理论里的把常规光源投射到独立明暗区间的过程。这也是为什么卡通渲染会颜色有所加深,或者说,让有感觉一眼就不是真实图片。因为明暗间有平滑过渡的取值被明确的分开成了卡通式的单调色彩。

勾边的过程,也可使用上个效果中的索贝尔算子,也确实有不少应用和游戏中勾边过程使用了索贝尔算子。原中的边缘滤镜方法我并没有深究,有兴趣的朋友可以探索下。

我对这个效果的探索不是很深,其中主要原因就是这个效果渲染起来并不是很顺畅。甚至有些卡卡的,在iPadmini上都是这样,更不用说手机了。所以如果在实际应用中,肯定需要重新修改。不过阀值修改后,渲染速度会变快,不过卡通的质量也会有所下降。

7.石刻效果

石刻效果,是我个人非常喜欢的一个效果。这个效果理论上就是把像素变成高光或者阴影,换句话说就是明显的区分开来。

这个效果最早在Coco2d的开发中,就被开发者所发现了。代码简单,效果也很有特色。

我最早看到关于石刻效果的教程:《HowToCreateCoolEffectswithCustomShadersinOpenGLES2.0andCocos2d2.x》

这篇教程把时刻效果和反色放一起讲解了,还是很详细了。

不过想具体实现,用这个代码就已经足够了。

#ifdefGL_ES
precisionmediumpfloat;
#endif
//Theshaderisinspiredandmodifiedfromcocos2dcustomshadertutorial

http://www./10862/how-to-create-cool-effects-with-custom-shaders-in-opengl-es-2-0-and-cocos2d-2-x

varyingvec2v_texCoord;
uniformsampler2Du_texture;
voidmain()
{
//pixelsize
vec2onePixel=vec2(1.0/480.0,1.0/320.0);

//getv_texCoordate
vec2texCoord=v_texCoord;

//exactlystepfollowtheidea
vec4color;
color.rgb=vec3(0.5);
color-=texture2D(u_texture,texCoord-onePixel)*5.0;
color+=texture2D(u_texture,texCoord+onePixel)*5.0;
//grayscale
color.rgb=vec3((color.r+color.g+color.b)/3.0);
gl_FragColor=vec4(color.rgb,1);
}

核心代码其实就3行,我们想要让图片的像素看起来很明显不同,那我们需要个基本色,color.rgb=vec3(0.5);

然后基于这个基本色,把像素之间的色差给打开。

最后在利用我们之前的灰色效果,把整个图形变灰。

这样就能达成石刻效果了。

后来我在网上搜索,也有些算法是通过梯度计算边缘,然后也能达到同样的效果。不过光听都感觉,这个过程会有些繁复。

8.高斯模糊

最后个效果也就是高斯模糊,也叫高斯平滑。在计算机图像领域,是个很重的话题。通常用它来减少图像噪声以及降低细节层次。这种模糊技术生成的图像,其视觉效果就像是经过一个半透明屏幕在观察图像,这与镜头焦外成像效果散景以及普通照明阴影中的效果都明显不同。

从数学角度来看,图像的高斯模糊效果就是图像与正态分布做卷积。由于正态分布又叫高斯分布,所以这个效果也叫高斯模糊。由于高斯函数的傅里叶变换是另一个高斯函数,所以高斯模糊对于图像来说是个低通滤波器。

这里对于高斯模糊的实现,有一些可以增加渲染速度的小技巧。

高斯模糊是一种图像模糊滤波器,它用正态分布计算图像中每个像素的变换。N维空间正态分布方程为:

但是在实际过程中,我们可以先对水平方向先做一次一维的模糊,再对垂直方向做一次一维模糊,同样可以达到使用二维矩阵变换得到的效果。

这是因为高斯模式线性可分,引用下GameRendering开发小组的例子。其实打开数学公式也可以证明线性可分。

另一个实现过程中的一个小技巧就是可以用预计算的方法。

我们可以通过高斯函数,预先计算出模糊的权重。

这张是我App里面的一个教学slides,解释的还是很清楚的。

所以最后的实现可以变成:

Gaussian_Blur.fsh

#ifdefGL_ES
precisionmediumpfloat;
#endif
varyingvec4v_fragmentColor;
varyingvec2v_texCoord;
uniformsampler2DCC_Texture0;
vec2blurSize;
uniformvec4substract;
voidmain(){
blurSize=vec2((1.0/1024.0)*3.0,(1.0/768.0)*3.0);
vec4sum=vec4(0.0);
sum+=texture2D(CC_Texture0,v_texCoord-4.0*blurSize)*0.063327;
sum+=texture2D(CC_Texture0,v_texCoord-3.0*blurSize)*0.093095;
sum+=texture2D(CC_Texture0,v_texCoord-2.0*blurSize)*0.122589;
sum+=texture2D(CC_Texture0,v_texCoord-1.0*blurSize)*0.144599;
sum+=texture2D(CC_Texture0,v_texCoord)*0.152781;
sum+=texture2D(CC_Texture0,v_texCoord+1.0*blurSize)*0.144599;
sum+=texture2D(CC_Texture0,v_texCoord+2.0*blurSize)*0.122589;
sum+=texture2D(CC_Texture0,v_texCoord+3.0*blurSize)*0.093095;
sum+=texture2D(CC_Texture0,v_texCoord+4.0*blurSize)*0.063327;
gl_FragColor=sum*v_fragmentColor;
}

最后来张效果图:

前面跳过的高亮效果,是基于高斯模糊的一个变版。

那么具体实现就是如果我们把原来图形的直接应用高斯函数求出来的数值,那就是高斯模糊。如果我们在原值上加上高斯函数的值,那就是高亮+模糊。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多