千家信息网

Unity+shader如何绘制halftone动画实现星之卡比新星同盟切屏效果

发表于:2024-09-22 作者:千家信息网编辑
千家信息网最后更新 2024年09月22日,这篇文章将为大家详细讲解有关Unity+shader如何绘制halftone动画实现星之卡比新星同盟切屏效果,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了
千家信息网最后更新 2024年09月22日Unity+shader如何绘制halftone动画实现星之卡比新星同盟切屏效果

这篇文章将为大家详细讲解有关Unity+shader如何绘制halftone动画实现星之卡比新星同盟切屏效果,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

卡比是个好游戏,这次的切屏和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 }
这里面还定义了dots的直径 _Diameter ,和halftone过渡区域的长度 _Num
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图形就画出来了但是这和我们常见的半调图形还有一些不同……

是的,格子之间没有偏移,完全是横平竖直的分布,显得一点也不好看,这就需要加上一个参数来让它在y方向有一个偏移,之后为了让图形更加自然,在y方向偏移时,x方向也进行一定压缩。于是我们对frag进行以下修改
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动画实现星之卡比新星同盟切屏效果就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

0