实用Shader样例详解2——灰度图和鱼眼效果
发表于2016-05-31
1、灰度图
灰度图在游戏中的应用十分广泛,例如当游戏场景的可点击对象由于状态变化变为不可以点击时,我们通常的处理就是把按钮变灰,直观地告诉玩家按钮处于非激活状态,除此之外还有玩家死亡时、技能CD等等,都涉及到灰度图的应用。今天的shader样例就是要分享彩色图转灰度图的处理,既然要是实现灰度图效果,我们首先要理解灰度。
灰度使用黑色调来表示物体,用黑色为基准色,不同的饱和度的黑色来显示图像。每个灰度对象都具有从 0%(白色)到100%(黑色)的亮度值。了解了灰度的定义,我们要怎么从RGB转换到灰度呢?每种颜色都有深浅,要如何保留权重?事实上RGB转灰度有个心理学公式Gray = 0.30 * R + 0.59 * G + 0.11 * B,因为人眼对RGB颜色的感知敏感度不相同,所以转换的时候需要给予不同的权重,这个公式就是根据光谱图得到的,而这个原理普遍应用于计算机图像处理系统。例如图像的常见纹理压缩格式RGB565,绿色的权重就较高,就是为了得到比较好的图像显示效果。
下面我们就开始动手来实现灰度图的效果,只是对片元的颜色的RGB值操作,只需要使用片元着色器,使用公式把片元的RGB转化为灰阶,保留原图的alpha值即可。
片元着色器:
1 2 3 4 5 6 7 8 | uniform sampler2D tex; void main() { vec4 pixColor = texture2D(tex, gl_TexCoord[0].xy); float gray = dot(pixColor.rgb, vec3(0.299, 0.587, 0.114)); gl_FragColor = gl_Color * vec4(gray, gray, gray, pixColor.a); } |
效果如下:
至于灰度的一些进阶效果就有待大家自己去开发了,比如从彩色到灰度的渐变效果,技能CD效果等等,事实上这些都是使用算法基于纹理坐标来进行操作,从而实现程序上的动画效果,只不过是基于着色器,操作的是纹理像素。
2、鱼眼效果
熟悉摄影的童鞋对鱼眼肯定不陌生,至于鱼眼在游戏中的应用可能不是很普遍,但很多游戏都会通过使用鱼眼的空间扭曲效果来得到很赞的UI表现力,要怎么用好这个效果就要大家充分发挥自己的想象力啦~
这个shader的实现原理参考了[Computer Generated
Angular Fisheye Projections]这篇论文,有两种类型的鱼眼效果,半球形鱼眼和角鱼眼,分别如下图所示:
半球形的鱼眼是一个半球到一个平面的平行投影,生成的图像是圆形的,失真效果比较明显。而角鱼眼效果是从图像的中心根据圆心角按照一定比例进行投影,失真效果比较小,这里我们选择角鱼眼效果来作为我们的实现。
创造一个角鱼眼的投影效果我们需要确定图像平面的每个点到场景中摄像机的矢量,这里我们创建一个180度的角鱼眼效果,把图像映射到以虚拟相机为中心的半球上,也就是说把纹理UV坐标系映射到一个半球的UV坐标系上。对于球体来说,我们可以使用球体方程x2 + y2 + z2 = r2或者极坐标来进行操作。
u = r cos(phi) + 0.5
v = r sin(phi) + 0.5
r = atan2(sqrt(x*x+y*y),p.z) / pi
phi = atan2(y,x)
得到映射关系方程后,我们根据UV坐标直接操作片元即可完成映射,下面我们就来动手写我们的Shader啦~
片元着色器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | uniform sampler2D tex; uniform mat4 texcoordMatrix; varying vec4 vertColor; varying vec4 vertTexcoord; uniform float aperture; const float PI = 3.1415926535; void main( void ) { float apertureHalf = 0.5 * aperture * (PI / 180.0); // This factor ajusts the coordinates in the case that // the aperture angle is less than 180 degrees, in which // case the area displayed is not the entire half-sphere. float maxFactor = sin (apertureHalf); // The st factor takes into account the situation when non-pot // textures are not supported, so that the maximum texture // coordinate to cover the entire image might not be 1. vec2 stFactor = vec2(1.0 / abs (texcoordMatrix[0][0]), 1.0 / abs (texcoordMatrix[1][1])); vec2 pos = (2.0 * vertTexcoord.st * stFactor - 1.0); float l = length(pos); if (l > 1.0) { gl_FragColor = vec4(0, 0, 0, 1); } else { float x = maxFactor * pos.x; float y = maxFactor * pos.y; float n = length(vec2(x, y)); float z = sqrt (1.0 - n * n); float r = atan (n, z) / PI; float phi = atan (y, x); float u = r * cos (phi) + 0.5; float v = r * sin (phi) + 0.5; gl_FragColor = texture2D(tex, vec2(u, v) / stFactor) * vertColor; } } |