这篇文章将为大家详细讲解有关Unity+shader如何绘制halftone动画实现星之卡比新星同盟切屏效果,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。
成都地区优秀IDC服务器托管提供商(成都创新互联).为客户提供专业的光华机房服务器托管,四川各地服务器托管,光华机房服务器托管、多线服务器托管.托管咨询专线:18982081108
卡比是个好游戏,这次的切屏和loading画面也是可爱得不行。先放出这篇文章想要重现的效果:
接下来放出我们最终实现的效果:
使用起来也是十分简单的,以上效果连动画系统都可以完成。
分析一番后,我们可以将卡比的切屏特效拆分成几个部分:
1.沿一个方向移动的halftone图形
2.两张贴图/颜色的切换
3.任意形状的mask贴图以指定轴旋转缩放
那么从第一步开始,绘制一个halftone图形。
要绘制halftone dots 并让它们在画面中移动,首先需要一个property来确定图形的位置。
Properties { _MainTex ("Texture A", 2D) = "black" {} _MainTexB ("Texture B", 2D) = "black" {} [Space(10)] _Position("Halftone Position", Float)=1 _Diameter("Diameter", Range(0,1) )=0.25 _Num("Length", Range(1,16)) = 3.0 }
struct v2f { float4 pos : SV_POSITION; half2 uvTA: TEXCOORD0; half2 uvTB: TEXCOORD1; half2 uvORI: TEXCOORD2;//original }; v2f vert(appdata_img v) { v2f o; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.uvTA=(v.texcoord-_MainTex_ST.zw)*_MainTex_ST.xy ; o.uvTB=(v.texcoord-_MainTexB_ST.zw)*_MainTexB_ST.xy ; o.uvORI.xy=v.texcoord; return o; }
这里两张贴图可以使用tillng和offset,而绘制halftone的uv则使用原始的值。
之后以position为基础,将uv划分为若干网格。最后以每个格子的中心点为圆心画出圆点。
fixed4 frag(v2f i) : SV_Target { float _rd; fixed2 posCenter; fixed indexOfGrid=floor((i.uvORI.x-_Position)/_Diameter);//num of grids between uv and PosW posCenter.x=_Position+(indexOfGrid + 0.5)*_Diameter; posCenter.y=(floor(i.uvORI.y/_Diameter) + 0.5)*_Diameter; _rd=0.5*_Diameter* abs(indexOfGrid)/_Num;//radius of the current grid fixed inCircle=step(distance(i.uvORI,posCenter),_rd); inCircle=clamp(inCircle,0,1); fixed4 texA=tex2D(_MainTex, i.uvTA).rgba; fixed4 texB=tex2D(_MainTexB, i.uvTB).rgba; fixed4 sum= lerp(texB,texA,inCircle); return sum; }
看一下效果,似乎出现了一些问题呢
可以发现,position的左右两侧同时出现了对称的圆点,解决这个问题,只需要让uv大于position的部分直接显示textureB即可。
if(indexOfGrid>=1){ return tex2D(_MainTexB, i.uvTB).rgba ;//return texture A when uv is in front (larger) of PosW }
另一个问题是,圆点的半径根据和position的距离是一个递增的关系,在后方的格子里,圆点的半径超过了格子的尺寸时,并不会在相邻的格子里显示出来。
这个问题在格子里画方块时不会看出异常
这时就需要在计算时将后方法格子一起考虑进去了
float _rdNext=_rd+0.5*_Diameter/_Num; fixed2 posCenterNext= posCenter-fixed2(diameterW,0); //center of up-next grid //fixed inCircle=step(abs(i.uvORI.x-posCenter.x),_rd)*step(abs(i.uvORI.y-posCenter.y),_rd); //Square fixed inCircle=step(distance(i.uvORI,posCenter),_rd)+step(distance(i.uvORI,posCenterNext),_rdNext); //Dot
现在,一个可以移动的halftone图形就画出来了但是这和我们常见的半调图形还有一些不同……
fixed4 frag(v2f i) : SV_Target { float _rd; fixed2 posCenter; fixed diameterW,diameterH; diameterW=_Diameter*(1-_rotOffset/2);//width of grid , reduce when _rotOffset is larger than zero diameterH=_Diameter; fixed indexOfGrid=floor((i.uvORI.x-_Position)/diameterW);//num of grids between uv and PosW if(indexOfGrid>=1){ return tex2D(_MainTexB, i.uvTB).rgba ;//return texture A when uv is in front (larger) of PosW } posCenter.x=_Position+(indexOfGrid+0.5)*diameterW; fixed modOffset=frac(indexOfGrid*_rotOffset)*_Diameter; posCenter.y=(floor((i.uvORI.y-modOffset)/diameterH)+ 0.5)*diameterH+modOffset; _rd=0.5*diameterH* abs(indexOfGrid)/_Num;//radius of the current grid float _rdNext=_rd+0.5*diameterH/_Num; fixed2 posCenterNextUp=posCenter-fixed2(diameterW,_Diameter*(_rotOffset-1)); fixed2 posCenterNextDown=posCenter-fixed2(diameterW,_Diameter*_rotOffset); //center of down-next grid float _rdPrev=_rd-0.5*diameterH/_Num; fixed2 posCenterPrevUp=posCenter+fixed2(diameterW,_Diameter*(_rotOffset-1)); fixed2 posCenterPrevDown=posCenter+fixed2(diameterW,_Diameter*_rotOffset); //center of down-next grid //fixed inCircle=step(abs(i.uvORI.x-posCenter.x),_rd)*step(abs(i.uvORI.y-posCenter.y),_rd);//Square fixed inCircle=step(distance(i.uvORI,posCenter),_rd); inCircle+=step(distance(i.uvORI,posCenterNextUp),_rdNext)+step(distance(i.uvORI,posCenterNextDown),_rdNext); inCircle+=step(distance(i.uvORI,posCenterPrevUp),_rdPrev)+step(distance(i.uvORI,posCenterPrevDown),_rdPrev); inCircle=clamp(inCircle,0,1); fixed4 texA=tex2D(_MainTex, i.uvTA).rgba; fixed4 texB=tex2D(_MainTexB, i.uvTB).rgba; fixed4 sum= lerp(texB,texA,inCircle); return sum; }
因为在uv方向上都有了偏移,所以绘制一个格子里的内容时需要左上左下、右上右下四个格子。
在properties中,添加
_rotOffset("Offset Between Points", Range(0,0.5)) = 0.0
由于圆点是对称图形,偏移量的取值范围只需要是格子尺寸的一半。
至此,一个可以移动位置的halftone切图效果就完成了。说到这里,大家可能会想,这个跟直接使用一张贴图作为遮罩有什么不同呢?答案是,确实没什么不同,目前为止的效果是完全可以使用一张遮罩实现的。使用遮罩甚至还能达成更多花哨的效果,但遮罩也有它所不能完成的事情。接下来修改格子划分的方法,让格子的位置不再跟随position变化,再改变一下计算圆点半径的方法,作出圆点一个个弹出的效果。
修改一下格子划分的方法,让格子的位置不再跟随position变化:
fixed indexOfGrid=floor((i.uvORI.x)/diameterW);//num of grids between uv and PosW posCenter.x=(indexOfGrid+0.5)*diameterW; fixed modOffset=frac(indexOfGrid*_rotOffset)*_Diameter; posCenter.y=(floor((i.uvORI.y-modOffset)/diameterH)+ 0.5)*diameterH+modOffset;
然后再改变一下计算圆点半径的方法,让其根据uv和position 的关系变化:
_rd=0.5*(_Position-posCenter.x)/_Num;//radius of the current grid float _rdNext=_rd+0.5*diameterW/_Num; float _rdPrev=_rd-0.5*diameterW/_Num;
这里计算前后格子中的半径时,需要改用压缩过的diameter,不然就会出现不太对劲的效果。由于划分格子的方法已经发生了变化,原本的if(indexOfGrid>=1)的时候,直接输出textureB已经无效了,实际上,判断的条件应该是圆点半径小于0。
接下来,需要一个表明角度的property,来让图形按照一个方向移动,而不是只能从左到右。
_Rotation("Rotation", Range(0,360)) = 0.0
旋转角度的算法,在很多地方都可以找到。这里直接转换计算halftone时使用的uv坐标,而textureA和B的uv不受影响。由于各个顶点之间的uv变化是线性的,这一转化可以放在vert函数中完成。
o.uvORI.xy=v.texcoord; fixed2 offsetXY=fixed2(0.5,0.5); float Rot = _Rotation * (3.1415926f/180.0f); float s = sin(Rot); float c = cos(Rot); float2x2 rMatrix = float2x2(c, -s, s, c); rMatrix *= 0.5; rMatrix += 0.5; rMatrix = rMatrix * 2 - 1; o.uvORI.xy = mul(o.uvORI.xy-offsetXY, rMatrix)+offsetXY;
这个_Rotation换成弧度角也是完全可以的。 _Rotation("Rotation", Range(0,6.283)) = 0.0 ,到时候就不需要进行乘PI再除180的计算。
现在看一下 示例场景
SlideshowEffect: Halftone and Popup
https://sakuraplus.github.io/make-terrain-with-google-elevation/webplayershow/webplayershow.html
里面的halftone切图的效果基本完成了。
关于Unity+shader如何绘制halftone动画实现星之卡比新星同盟切屏效果就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。