简介
今天是只狼发售一周年,作为去年的goty,只狼最核心的系统莫过于弹反,笔者去年也是和弦一郎大战几百回合,通关之后很想实现以下弹反瞬间的效果。
最终实现效果如下:
笔者不是专业技美,如有问题,欢迎指出。
实现思路
首先仔细观察一下只狼在弹反的瞬间是怎么一个过程
首先是,受到攻击的瞬间,这一瞬间,产生了火花,并从弹反中心扩散出了一道模糊波纹,特效大小占据屏幕接近一半
接着逐渐开始消散,火花熄灭,周边部分出现明显径向模糊效果
最后,中心开始逐渐清晰,到屏幕边缘的地方就基本消失了
总结起来就是从中心点开始,随着时间逐渐扩散
设计流程
特效部分我不是很擅长,所以就网上找了一个现成的卡通特效,我们这次就着重看一下后处理的部分。
径向模糊
首先我们实现一个由内到外扩张的一个整体径向模糊,方式也比较简单,先获取到弹反的中心点BlurCenter,然后在fragment阶段各个像素和他计算相对位置获得方向,对方向上的点采样取平均就可以了,核心代码如下:
fixed4 frag (v2f i) : SV_Target
{
//这里是为了让扩散为圆形而不是屏幕的比例,_Aspect是屏幕的宽高比
float2 p=i.uv*float2(_Aspect,1);
float2 dir =normalize(p-_BlurCenter);
dir*=_MainTex_TexelSize.xy;
//对方向上的点采样取平均
fixed4 col =tex2D(_MainTex,i.uv-dir*1) ;
col +=tex2D(_MainTex,i.uv-dir*2) ;
col +=tex2D(_MainTex,i.uv-dir*3) ;
col +=tex2D(_MainTex,i.uv-dir*5) ;
col +=tex2D(_MainTex,i.uv-dir*8) ;
col +=tex2D(_MainTex,i.uv+dir*1) ;
col +=tex2D(_MainTex,i.uv+dir*2) ;
col +=tex2D(_MainTex,i.uv+dir*3) ;
col +=tex2D(_MainTex,i.uv+dir*5) ;
col +=tex2D(_MainTex,i.uv+dir*8) ;
col *=0.1;
return col;
}
此时我们应用在unity中,效果如下:
原图:
区别还是挺大的,很明显有径向模糊的效果,有那种感觉了。
径向模糊优化
上面的实现是逐像素的采样,而如果用户的电脑分辨率非常高的话,或者说用户的电脑配置不够,或者我们的平台是手机,那么我们就要做一定的优化。
我们可以先降低图片的分辨率,然后再采样,比如1080变成540,甚至270p,这样我们就能够大大降低gpu的负担,不过会多一层pass
270p压缩图:
270p+径向模糊:
540p+径向模糊:
注意:对应不同的像素密度我们也要控制采样范围
相关的cs部分如下:
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
RenderTexture rt1 = RenderTexture.GetTemporary(source.width >> downSampleFactor, source.height >> downSampleFactor, 0, source.format);
RenderTexture rt2 = RenderTexture.GetTemporary(source.width >> downSampleFactor, source.height >> downSampleFactor, 0, source.format);
Graphics.Blit(source, rt1);
//使用降低分辨率的rt进行模糊:pass0
Graphics.Blit(rt1, rt2, material, 0);
//使用rt2和原始图像lerp:pass1
material.SetTexture("_BlurTex", rt2);
Graphics.Blit(source, destination, material, 1);
//释放RT
RenderTexture.ReleaseTemporary(rt1);
RenderTexture.ReleaseTemporary(rt2);
}
波纹扩散
为了做更好的过渡以及定制化,我们使用unity的curve来实现当前径向贴图的采样程度
同时,为了更好的性能,我们应该把这部分计算放在gpu部分,可以采用生成贴图的方法:
private void Start()
{
//初始化振幅贴图(也就是把waveform曲线初始化到gradTexture上面)
gradTexture = new Texture2D(1024, 1, TextureFormat.Alpha8, false);
gradTexture.wrapMode = TextureWrapMode.Clamp;
gradTexture.filterMode = FilterMode.Bilinear;
for (var i = 0; i < gradTexture.width; i++)
{
var x = 1.0f / gradTexture.width * i;
var a = curve.Evaluate(x);
gradTexture.SetPixel(i, 0, new Color(a, a, a, a));
}
gradTexture.Apply();
//初始化material
material = new Material(PointBlurShader);
material.hideFlags = HideFlags.DontSave;
material.SetTexture("_GradTex", gradTexture);
}
这样,shader部分就得到了当前曲线上所有的数据,存储在了_GradTex
中。
最终,我们在frag部分完成当前的采样,相关代码如下
//计算当前像素点的采样程度,_Timer为特效产生开始的计时
float GetBlurPercent(float2 pos){
//计算当前时间以及像素为主下,对应的曲线横坐标t
float t=_Timer-length(pos-_BlurCenter)/_BlurSpeed;
//如果要让这个波纹的宽度变大可以调整BlurCircleRadius
t*=1/_BlurCircleRadius;
//获取对应值
return tex2D(_GradTex,float2(t,0)).a;
}
fixed4 frag_mix (v2f_lerp i) : SV_Target{
fixed4 rawCol = tex2D(_MainTex, i.uv1);
fixed4 blurCol = tex2D(_BlurTex, i.uv2);
fixed4 col = lerp(rawCol,blurCol,_BlurStrength
//获取当前像素点在曲线上的值
*GetBlurPercent(i.uv1*float2(_Aspect,1))
//范围限制,调整BlurRange可以控制波的大小
*min(_BlurRange/length(i.uv1*float2(_Aspect,1)-_BlurCenter),1));
return col;
}
特效
后处理的部分就基本完成了,然后我们加上一些lensflare和particle,我们的效果就完成了~调的参数有点高,狼都看不清了2333
应用
我观察了很多游戏,尤其是动作游戏,在径向模糊这块几乎是一个必备的要素,而且反馈感非常高,可以把打击感提高好几个层次。
要问为啥都是卡婊的游戏,我也不知道。
结语
项目原工程地址:https://github.com/cyclons/SekiroBlock
相比于写游戏逻辑,写shader还是非常享受的一个过程,编译快,反馈强,唯一比较难受的还是ide吧。
感谢观看!
参考文章
Unity Shader-后处理:径向模糊效果
unity用shader来实现2D涟漪/水波纹特效