概述
最近接到一个需求,需要实现速度线的效果。这里,会一步步地展示实现的过程。希望对大家能有所启发。下面是项目的Github地址,欢迎Star。
实现
首先,合理地使用搜索引擎,找一下速度线效果的参考图:
对于上图,下面进行一些分析。
对于黑色线条的形状,猛一看没什么思路。但如果换一种角度,考虑与黑色线条互补的白色部分,是类似中间一个光团,往周围发射的形状。这种形状,就比较好实现了。下面,先实现这种形状。
下面是后面要用到的基础代码,主要关注片元着色器部分:
1 | Shader "RoadOfShader/2.2-SpeedLine/Speed Line" |
实现的效果如下所示:
主要的功能都在片元着色器中。首先,根据中心点偏移UV,目前的Center设置成\((0.5,0.5)\),目的是把UV的中心移动到图像的中心位置。然后,对偏移后的UV归一化,这一步达到的效果是,对于方向相同的UV,取值都相同,也就是说,对于同一方向的UV,对纹理采样的结果都是相同的。这样,就会形成从中心点往周围发散的效果。对于上图的效果图,使用的NoiseTex纹理比较特殊,是亮暗方格间隔的,选择这张图,主要是为了更好地展示上述采样的逻辑。(仔细看一下上图,黑色部分是不是就像上面的速度线了。)
速度线不是静止的,是有快速变化的,接下来,在上面的基础上做一些旋转的效果。
主要的改变都在片元着色器中:
1 | half2 uv = input.uv - _Center.xy; |
添加了一个速度参数_RotateSpeed,根据时间变化,计算出旋转的角度angle。注意,这里的计算得到的angle是弧度,再使用sincos函数计算得到angle的正弦和余弦值。然后,构造2维的旋转矩阵rotateMatrix,对UV进行旋转后再计算得到归一化的normalizedUV。
实现的效果如下所示:
观察发现,上面的效果很明显地往一个方向旋转,而速度线并不是往一个方向旋转的。此时,可以加一个反方向的旋转,这样,就可以抵消这种单方向的旋转。
主要的改变都在片元着色器中:
1 | half2 uv = input.uv - _Center.xy; |
主要的区别是,添加了rotateMatrix1的计算,这里的有一点需要注意一些,对于反方向的旋转: \[ \begin{aligned} cos(-angle) = cos(angle)\\ sin(-angle) = -sin(angle) \end{aligned} \] 然后,计算得到normalizedUV1。使用normalizedUV0和normalizedUV1分别对纹理采样,将采样的结果相乘,就可以解决上面的往一个方向旋转的问题了。
实现的效果如下所示:
再观察一下最前面的效果图,发现黑色线条分布在边缘。很容易可以想到,可以根据UV距离中心点的距离对上面的结果做一个叠加,也就是根据UV距离做一个遮罩。
主要的改变都在片元着色器中:
1 | half textureMask = SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, normalizedUV0).r * SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, normalizedUV1).r; |
textureMask是之前计算得到的纹理遮罩,而uvMask是新计算的UV遮罩。这里计算uvMask,使用的是幂函数,使用幂函数,相对于直接使用uv的长度,幂函数曲线比直线更加平缓。然后对textureMask和uvMask进行叠加,得到最终的mask。
实现的效果如下所示:
目前,使用的NoiseTex是亮暗间隔的格子图。真正使用的,是噪声图。下面替换成噪声图。
实现的效果如下所示:
可以发现,边缘的条纹太密集了。这里,可以添加一个阈值,对上面计算得到的mask进行分层。
主要改变的片元着色器代码如下:
1 | half mask = smoothstep(_Threshold - 0.1, _Threshold + 0.1, textureMask * uvMask); |
主要就是使用_Threshold对纹理遮罩和UV遮罩做一个分层,这里,使用smoothstep函数,使分层的边缘不会那么的“硬”。
实现的效果如下所示:
到这里,与开头的速度线效果相比较,已经神似了。下面,给速度线条加上颜色控制。
主要改变的片元着色器代码如下:
1 | half mask = smoothstep(_Threshold - 0.1, _Threshold + 0.1, textureMask * uvMask); |
主要的区别在返回值部分。返回了_TintColor.rgb作为颜色,使用遮罩mask和_TintColor.a相乘的结果作为透明度。
这里需要注意一下,目前使用的混合模式如下:
1 | Blend SrcAlpha OneMinusSrcAlpha |
速度线目前使用面片实现的,是按照半透明物体渲染的。上面的混合模式,可以得到速度线与后面的场景混合的效果。当然,也可以使用如下的混合模式:
1 | Blend One OneMinusSrcAlpha |
此时,最终返回的颜色,要自己做颜色与透明度的叠加了:
1 | return half4(_TintColor.rgb * mask * _TintColor.a, mask * _TintColor.a); |
实现的效果如下所示:
最终的代码如下。
扩展
上面实现了基本的速度线效果。但速度线一般是全屏的。可以使用粒子、全屏的面片或者后处理实现全屏的效果,这里不再赘述。另外,还可以进行扩展。目前使用的是单层的速度线,也可以经过改变获得双层甚至多层的速度线。主要的算法都是上面的。考虑到对NoiseTex采样次数多了会对性能造成影响,这里,可以充分利用NoiseTex的其它通道,一次采样,可以获得四个通道的数据,根据不同通道的数据,可以实现多层速度线的效果。
参考
- [1] Steven Universe