简介
可以先在这里看一下最终效果
做法总结来说,就是使用1张比较大的RenderTexture记录整个场景的墨水区域;可喷漆地面和墙面特别的展一个类似lightmapUV的不重叠splatUV;既使用这个uv去把墨水绘制到RenderTexture上,地面墙面的墨水的渲染也使用这个uv去采样RenderTexture获取墨水区域;
这次的许多做法是为了尽可能的还原Splatoon3中的效果(例如一发墨水同时只能在一面墙或地面上留下墨迹,这偏离了现实,或许是为了玩法考量)
RenderTexture
只是用一张RenderTexture记录整个场景的,包括2个队伍的(三色对战?甚至Splatoon4可能的4色对战)墨水的痕迹,所以只能选择一个通道记录一个队伍的墨水痕迹;
至于每个队伍的墨水颜色,可以通过全局设置传递给材质,不必再RenderTexture中记录。
另外通道记录的不是非0即1的颜色,而是带有渐变的高度信息的,这可以帮助制作出更丰富的墨水效果
Splatoon3中不同的武器、技能、大招绘制的墨水形状都是不同的,一把枪的每一发子弹、甚至子弹与墙面、地面的不同角度,留下的形状都不相同,这需要一些不同样式的遮罩贴图,绘制RenderTexture时,把Mask贴图传递进去,同时,还需要的信息有绘制的uv位置、mask的缩放、旋转、哪一方队伍等等数据:
1 | |
Mask贴图
绘制RenderTexture的shader核心代码:
1 | |
需要特别提一下,在绘制墨水时,需要清理掉其他队伍的墨水通道,但是又不能完全清理掉,需要用smoothstep留下一些过度,因为在物体上渲染敌我双方边际的墨水时,需要冗余空间避免留下一些缝隙没有任何一方的墨水,也就是这里:
1 | |
cpu侧调用绘制的代码核心如下:
1 | |
还需要一张tempTexture来暂存rendertexture,避免同时读写,这么大的图同时存在两张对于内存方面确实比较费。
物体上墨水的渲染
柔和的边缘
上面RenderTexture很大,但是具体到绘制一发子弹的墨水的局部,使用的像素依然很少
分辨率很低的情况下,如何避免明显锯齿的边缘?
我的代码如下,我只考虑了双色对战的情况
1 | |
墨水的高光
RenderTexture中一个通道储存的一方墨水遮罩是带有深度的,依据这个深度,通过ddx ddy构建法线,然后计算高光;我这里直接修改的是PBR的各项数据,没有给墨水部分特别的光照模型。
顺便一提,还经过各种尝试,计算了一个normaMask,他得到的法线,能尽可能在边缘有高光,中心部分较平整。
1 | |
模型的splatUV
模型的splatUV有以下要求
1、一个场景的所有可涂色物体的uv不能有重叠
2、连续的地面的uv是连续的,避免接缝
3、不连续的地面,所有的墙面之间的uv不能连续,且有一定间隔,避免错误溢出到其他表面,同时也符合Splatoon3的GamePlay
4、物体的空间大小和其uv所占的大小是成比例的,这可以减轻绘制RenderTexture,计算个队伍染色面积这些工作的复杂度
5、uv的展开方向是一致的,墨水Mask的方向绘制到RenderTexture中的旋转计算,如果uv展开方向不一致,则需要额外考虑uv的展开方向对旋转方向的影响,uv的展开方向还不太好获取
考虑到Splatoon3场景中大多数需要涂色的物体,实际上都属于地形的范畴,另外这些部分可以分离出来非常简单的面片模型,所以离线把splatUV制作到这些模型上是可接受的。
另外因为获取uv位置依赖unity的射线检测,而射线检测只提供到了uv1,所以这次把splatUV放到了uv1
cpu端涉及渲染的部分代码
获取RenderTexture数据,判断踩得哪方墨水
1 | |
绘制数据类,用于配置墨水数据
1 | |
绘制指令结构体
1 | |
发射子弹碰撞体,需要使用射线检测获取uv位置
1 | |
粒子系统涂色类,同样依赖射线检测
1 | |
可能可以做的优化
RenderTexture的绘制还是太费了,或许可以考虑使用ComputeShader来一次绘制完所有绘制指令,但这需要把所有可能用到的Mask打包成TextureArray,然后不传染单个texture,而是传入textureIndex
参考
https://discussions.unity.com/t/how-do-they-do-the-painting-in-splatoon/658039/10
https://www.bilibili.com/video/BV1Fe411T7qf/?spm_id_from=333.1245.0.0&vd_source=1599bc708e0ac08b02de3d09474f49b4