移动端枪击受伤效果(一)

参考了这个插件的大致实现思路,自己做的效果和它视频中展示的也是差不多的。但是它使用了compute buffer,这是移动端不支持的,这里做了比较大的改动。另外它的shader用的是surf,除了射击功能之外做了很多很复杂的效果,我主要还是做一个实现功能本身的demo,所以这部分也重写了。

这是demo的真机录屏

原理简介

1、美术分别制作起点mesh和终点mesh,起点mesh拥有两套uv,一套是自己的uv,一套是和终点mesh对应顶点在相同uv位置的uv(找对应点是根据uv距离最近点来判断的)
2、实际使用起点的mesh,根据那套用来对应的uv,按顶点id顺序,将终点mesh对应的顶点坐标和顶点法线传入shader
3、在起点mesh的shader中做lerp,这样就可以根据一个参数,做起点终点间mesh的morph

枪击效果:点击位置发射射线和mesh碰撞体求交,返回相交位置的世界坐标,将多个枪击点的世界坐标传入shader中,shader遍历每个枪击点,根据它与当前顶点的距离,迭代用来做morphing的参数,使得离枪击位置越近的点,越接近终点mesh。

下图分别为起点mesh和终点mesh的例子

不使用终点mesh法线的话,会对凹陷部位的效果有严重影响

关键实现

美术资源制作

制作流程建议:(以断手为例)
1、制作正常状态mesh,如完整的手,展好uv
2、制作非常态mesh时,复制一份正常态,对其进行变形,如把手指缩进去,顶点数量不超过65536(法线贴图存放限制)

3、调整并确定uv的对应关系,如原来手指上的点的uv区域,对应变形后的顶点区域。不用一一对应,只是一小片一小片区域对应的话,也能达到需要的效果。为了这一步方便,所以第二步时,尽量将顶点按照变形的规律移动,而且先移动好位置,再增减顶点。

4、选用非常态作为起点mesh展一套uv,放到uv0,这个uv就按普通情况展,不用考虑和另一个mesh的对应关系,之前的那套放到uv1
(让最后使用的变化反向是很方便的,但是使用非常态mesh做起点可以省一套uv)
下面分别是断手两个mesh的三套uv


5、将两个mesh放到工具中(目前做的工具在unity上),生成一个新的起点mesh和一张法线图。
6、还是在这个工具中,滑动变形滑条,看看顶点变形有没有问题,有问题的话要重新调整做对应关系的那张uv,如果没有问题的话就可以导出生成新mesh和法线图了
7、分别以两个mesh的uv0制作贴图
8、最后的提交资源是

  • 工具导出的mesh:长成非常态mesh的样子,有顶点色和两套uv,uv0是自己的,uv1是和正常态的对应uv
  • 工具导出的法线图:尺寸64*64,下方有很多彩色的像素点
  • 两个mesh的两套贴图,都是基于自己的uv0
将终点mesh的信息传入起点mesh的shader中。

也就是将终点mesh的顶点坐标和法线传入起点mesh的shader中,插件原本使用的是compute buffer,但是移动端不能使用,所以就将信息写入贴图。

如果mesh的顶点色没有更重要的用处的话,目前觉得比较好的方案是将终点mesh的顶点位置存在顶点色,用贴图存法线,试了一下效果非常好。

另外还尝试过两个方案

方案1:按照顶点id顺序,在动态贴图中写入顶点坐标和法线
内存中的贴图可以直接写入顶点坐标,不需要归一化,这个方法在真机上可能会有采样精度的问题,因为数据点密集排列而且相邻像素中存放的顶点位置可能是距离非常远的点。不过我的测试机iphone6s没有问题。

方案2:提前烘好贴图
因为这个效果原本就已经很费,所以希望尽量减少消耗。提前烘的话就需要写入(0,1)的值,就需要归一化顶点坐标,尝试了一下精度问题还是有点可怕的
这个是用没有手指的mesh,在shader中把有手指mesh的顶点赋给它出来的效果

射击效果

传射击位置只能通过动态贴图了,不过可以输入一张很小的,因为玩家射击的次数比较有限

传递到shader后,遍历每个射击点,对做morphing的参数迭代

1
2
3
4
5
6
7
8
9
10
11
float p = 0;
for (int ii = 0;ii < _pointsLength; ii++) {
//计算采样uv位置
float4 uvInBuffer = float4(
(ii % _ShootPointsSize + 0.5) / _ShootPointsSize*1.0,
(ii / _ShootPointsSize + 0.5) / _ShootPointsSize*1.0, 0, 0);
//采样得到第i个射击点
float3 points_i = tex2Dlod(_points, uvInBuffer).rgb;
//大致的p的计算方法
p = saturate(p += saturate((distance(worldPos, points_i) - HoleSize) * HoleIndensity));
}

实际p的计算方法更加细致,比如手臂处,可能打一枪就整个断了,最好影响的顶点范围大一些,而胸腔之类的影响区域比较小,被枪击时不会影响整个形状,所以使用一张mask,使得不同部位的被射击的效果不同。

真机测试

测试机为iPhone6s,测试时场景为天空盒和一个测试模型,影响性能的地方主要在射击的次数,分别在0次射击,5次射击,10次射击时抓帧,绘制时间分别为3.99ms,6.00ms,7.48ms