仿电流效果的实现

2015年03月15日 18:28 0 点赞 0 评论 更新于 2025-11-21 17:15

在影视作品中,我们常常能看到电流“滋啦啦”的特效,这种效果非常酷炫,令人不禁思考如何在实际开发中实现它。

当从特效场景中获取这个效果时,我发现它十分有趣。以往实现电流效果,通常采用贴图动画或者直接用程序控制面片位移的方法。由此可见,多研究他人的 shader 实现,能让我们收获颇丰。

然而,当我打开关键的 Electricity_Sh.shader 文件时,着实感到头疼,因为这是反编译后的代码。我不清楚这样的代码是如何生成的,若有知晓的朋友,还望不吝赐教。大家可以打开源文件查看,里面全是诸如 float4 Mask8 = float4(0.0, v.texcoord.y, 0.0, 0.0); 这类令人费解的代码。我花了一个下午才看完,虽略有收获,但也深知其中还有许多未知。接下来,我就将自己的收获分享给大家,希望能对大家有所帮助。

在讲解代码之前,先给大家展示一下闪电原本的样子,它呈现为一条螺旋形的丝带。在后续讲解源码时,我会说明为何要采用这种形状。

Shader 的主要作用是改变每个顶点的位置,理解这一点后,我们再继续往下看。

下面是我修改后的 vs 代码:

void vert (inout appdata_full v, out Input o) {
float4 m_time = frac((_Time + v.color.r + v.color.g + v.color.b) * float4(0.05));
float4 m_time_global = m_time * float4(25) * float4(_MotionRateGlobal);
float4 floor0 = floor(m_time_global * float4(1.123));
float4 floor1 = floor(m_time_global * float4(3));
// Z 轴的初始偏移量
float4 zoffset = m_time_global + (floor0 + floor1) * float4(8.93);
o.color = v.color;
float4 zRates = float4(3, 0.15, 0.15, -0.1051);
float4 zFrequencys = float4(193.1759, 8.567, 93.15, 25.31);
float4 zScales = float4(0.25, 5, 0.5, 1.5);
float4 zSum = float4(0);
for(int i = 0; i < 4; i++) {
float4 offset = zoffset * _NoiseMotionRate.xxxx * zRates[i];
offset = float4(0.0, v.texcoord.y, 0.0, 0.0) + offset;
offset = offset * zFrequencys[i] * _NoiseFrequency.xxxx;
float4 SinOffset = sin(offset);
float4 sum = SinOffset * zScales[i] * _NoiseScale.xxxx;
zSum += sum;
}
// y 轴的初始偏移量
float4 yoffset = zoffset + float(2);
float4 yRates = float4(0.256, 0.190731, 2.705931, -0.107335);
float4 yFrequencys = float4(7.071, 79.533, 179.5317, 23.0917);
float4 yScales = float4(5, 0.5, 0.25, 1.5);
float4 ySum = float4(0);
for(int i = 0; i < 4; i++) {
float4 offset = yoffset * _NoiseMotionRate.xxxx * yRates[i];
offset = float4(0.0, v.texcoord.y, 0.0, 0.0) + offset;
offset = offset * yFrequencys[i] * _NoiseFrequency.xxxx;
float4 SinOffset = sin(offset);
float4 sum = SinOffset * yScales[i] * _NoiseScale.xxxx;
ySum += sum;
}
float4 Mask1 = float4(0.0, ySum.y, zSum.y, 0.0);
float4 Add13 = float4(-1) + v.texcoord.y * float4(2);
float4 Abs0 = abs(Add13);
float4 Lerp1 = lerp(Mask1, float4(0.0), Abs0);
float4 Add0 = Lerp1 + v.vertex;
v.vertex = Add0;
o.meshUV.xy = v.texcoord.xy;
o.meshUV.zw = v.texcoord1.xy;
}

下面对代码进行详细讲解:

float4 m_time = frac((_Time + v.color.r + v.color.g + v.color.b) * float4(0.05));
float4 m_time_global = m_time * float4(25) * float4(_MotionRateGlobal);
float4 floor0 = floor(m_time_global * float4(1.123));
float4 floor1 = floor(m_time_global * float4(3));

这部分代码主要是为了获取 floor0floor1。其作用是让闪电产生跳动的效果,floor 函数会使 floor0floor1 的值发生较大变化,从而使闪电在形状上产生明显的改变。使用 floor0floor1 是为了让跳动效果不那么规律,大家可以尝试将其中一个置零,观察效果的变化。

// Z 轴的初始偏移量
float4 zoffset = m_time_global + (floor0 + floor1) * float4(8.93);
o.color = v.color;
float4 zRates = float4(3, 0.15, 0.15, -0.1051);
float4 zFrequencys = float4(193.1759, 8.567, 93.15, 25.31);
float4 zScales = float4(0.25, 5, 0.5, 1.5);
float4 zSum = float4(0);
for(int i = 0; i < 4; i++) {
float4 offset = zoffset * _NoiseMotionRate.xxxx * zRates[i];
offset = float4(0.0, v.texcoord.y, 0.0, 0.0) + offset;
offset = offset * zFrequencys[i] * _NoiseFrequency.xxxx;
float4 SinOffset = sin(offset);
float4 sum = SinOffset * zScales[i] * _NoiseScale.xxxx;
zSum += sum;
}

闪电上凹凸不平的效果正是由这段代码实现的。其主要功能是结合四个振幅、偏移和速率不同的正弦函数的解,用于 Z 轴上的偏移。之所以能达到这样的效果,是因为 zRateszFrequencyszScales 这些参数设置得恰到好处,这类似于简单的傅里叶变换。不过,我并不清楚这些参数是如何确定的,希望有了解的大牛能给予指点。大家也可以使用 Matlab 等工具进行尝试。

下面一段代码与上述代码类似,用于 Y 轴上的偏移:

// y 轴的初始偏移量
float4 yoffset = zoffset + float(2);
float4 yRates = float4(0.256, 0.190731, 2.705931, -0.107335);
float4 yFrequencys = float4(7.071, 79.533, 179.5317, 23.0917);
float4 yScales = float4(5, 0.5, 0.25, 1.5);
float4 ySum = float4(0);
for(int i = 0; i < 4; i++) {
float4 offset = yoffset * _NoiseMotionRate.xxxx * yRates[i];
offset = float4(0.0, v.texcoord.y, 0.0, 0.0) + offset;
offset = offset * yFrequencys[i] * _NoiseFrequency.xxxx;
float4 SinOffset = sin(offset);
float4 sum = SinOffset * yScales[i] * _NoiseScale.xxxx;
ySum += sum;
}

这里有一个问题:为什么 X 轴上的偏移不需要处理呢?实际上,X 轴的偏移已经在前面提到的螺旋形模型中得到了解决,采用这种形状的模型可以省去 X 轴上的偏移操作,而且效果非常理想。

最后,我们会发现电流的起点和终点是固定不动的,这是因为模型的 UV 值起到了作用。当 UV 值中的 v 值为 0 或 1 时,偏移值为 0。大家可以根据这些代码进行修改,实现自己想要的效果。希望以上内容能对大家有所启发,若有不清楚的地方,欢迎随时提问。

作者信息

boke

boke

共发布了 3994 篇文章