unity如何实现绘制表格_[Unity+shader]绘制halftone动画,实现星之卡比新星同盟切屏效果...

23f61b659ab04491ee18372aa1d07d70.png

卡比是个好游戏,这次的切屏和loading画面也是可爱得不行。先放出这篇文章想要重现的效果:

8efb133833f787e15d79c365cb617c0e.gif
卡比的进门切屏效果

接下来放出我们最终实现的效果:

5112707d1469d76419790dc3c06df438.gif

16e9a31ebe410ab36c9bca0b04b66cf7.png

使用起来也是十分简单的,以上效果连动画系统都可以完成。

分析一番后,我们可以将卡比的切屏特效拆分成几个部分:

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
 }

2169ded94580360e8d357fc7a0c830d2.png
这里面还定义了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;
}

看一下效果,似乎出现了一些问题呢

4a435fa5d345da9fb83574eec07e1db1.png

可以发现,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的距离是一个递增的关系,在后方的格子里,圆点的半径超过了格子的尺寸时,并不会在相邻的格子里显示出来。
这个问题在格子里画方块时不会看出异常

0a21025d91ac26c31364b9d5df569049.png
画圆点的时候就可以发现出现了一些明显的直线(上上图的第一列圆点处),画方块的时候就满看不出来了

这时就需要在计算时将后方法格子一起考虑进去了

	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图形就画出来了

48020e9964814ca96e4d37e1d68d2971.gif

但是这和我们常见的半调图形还有一些不同……

f86b2871fa3dad8fda627a25ee7f4fd0.png
是的,格子之间没有偏移,完全是横平竖直的分布,显得一点也不好看,这就需要加上一个参数来让它在y方向有一个偏移,之后为了让图形更加自然,在y方向偏移时,x方向也进行一定压缩。于是我们对frag进行以下修改

3e6440c56f6f466d7a0c540f9af9e1bc.png
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切图效果就完成了。

9d9e79d166adbd1785ddba82262303ba.gif

说到这里,大家可能会想,这个跟直接使用一张贴图作为遮罩有什么不同呢?答案是,确实没什么不同,目前为止的效果是完全可以使用一张遮罩实现的。使用遮罩甚至还能达成更多花哨的效果,但遮罩也有它所不能完成的事情。接下来修改格子划分的方法,让格子的位置不再跟随position变化,再改变一下计算圆点半径的方法,作出圆点一个个弹出的效果。

a287ca05f973dc859abed06aae5b5e89.png

修改一下格子划分的方法,让格子的位置不再跟随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。

bdfc50df7f0f7be0a5def03744ed5471.gif

接下来,需要一个表明角度的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​sakuraplus.github.io

里面的halftone切图的效果基本完成了。

至此,重现halftone切图效果的重点部分已经完成,下一话讲一讲很简单的mask旋转和缩放。

saku一只松鼠:[Unity+shader]绘制halftone动画,实现星之卡比新星同盟切屏效果(下)

如果对更多的好玩特效感兴趣,可以到这里先睹为快~

Buy me a coffee: Asset Store

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值