EtherTK 解构《武士零》 - 冲刺特效

冲刺特效也就是俗称的“残影”,《武士零》中玩家几乎所有的冲刺操作都会或多或少地伴随着这种特效,在本次重现的思路中,残影由一系列透明度随时间变化的剪影风格序列帧构成,当然,考虑到赛博朋克的艺术风格,这些残影的颜色也主要应该由红色和蓝色构成——
武士零冲刺演示

着色器代码

想要实现剪影风格的渲染也很简单,简单来说可以使用如下的 shader 代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
in vec2 textureCoord;

out vec4 FragColor;

uniform sampler2D texture1;

uniform vec3 color;
uniform float alpha;

void main()
{
vec4 textureColor = texture(texture1, textureCoord);
if (textureColor.a == 0)
FragColor = vec4(0, 0, 0, 0);
else
FragColor = vec4(color, alpha);
}

因为要在外部程序中控制透明度渐变从而实现消逝的过程,所以我们传入了 alpha 这个 Uniform 值,由于原始素材中没有使用半透明的像素,所以我们直接将传入的透明度作为了最终的片段透明度输出,如果需要考虑到原始素材中的半透明像素,那么只需要将 FragColor = vec4(color, alpha); 变更为 FragColor = vec4(color, alpha * textureColor.a);

Lua 脚本代码

而在 Lua 脚本代码中,我们只需要维护一个残影帧序列数组,渲染时遍历数组内已有的所有帧进行渲染即可,而在每次 Tick 时更新以下逻辑:

  • 更新角色动画和位置,并检查是否已到达了新的残影生成时间,如果需要生成新的残影那么就将当前动画帧及其位置颜色信息推入数组中;
  • 更新残影颜色计时器,生成新的残影颜色应该由颜色数组中的颜色循环赋值(如演示中的蓝色和红色);
  • 更新残影淡出逻辑,单帧残影贴图的透明度会随着时间衰减,从而出现逐渐消逝的效果。

对应的 Lua 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
-- 更新 Shield 逻辑
local UpdateShield = function(delta_ms)
-- 更新角色动画
timer_shield_animation = timer_shield_animation + delta_ms
if timer_shield_animation >= interval_shield_animation then
idx_texture_shield = idx_texture_shield + 1
if idx_texture_shield > #texture_shield_list then
idx_texture_shield = 1
end
timer_shield_animation = 0
end
-- 更新角色位置
if is_moving then
location_shield = Lerp(location_shield, dst_location_shield, speed_player * delta_ms)
if location_shield:sub(dst_location_shield):size() <= 1 then
timer_next_shadow, is_moving = 0, false
end
end
end

-- 更新 FlushEffect 逻辑
local UpdateFlushEffect = function(delta_ms)
-- 更新残影淡出逻辑
for _, shadow in ipairs(shadow_list) do
shadow.alpha = shadow.alpha - delta_ms / interval_shadow_fade_out * (alpha_shadow_init / 1.0)
if shadow.alpha < 0 then shadow.alpha = 0 end
end
-- 更新残影颜色逻辑
timer_switch_color = timer_switch_color + delta_ms
if timer_switch_color >= interval_switch_color then
idx_color_list = idx_color_list + 1
if idx_color_list > #color_list then
idx_color_list = 1
end
timer_switch_color = 0
end
-- 更新残影新增逻辑
if not is_moving then return end
timer_next_shadow = timer_next_shadow + delta_ms
if timer_next_shadow >= interval_next_shadow then
table.insert(shadow_list,
{
location = OpenGL.Vec2(location_shield),
texture = texture_shield_list[idx_texture_shield],
color = color_list[idx_color_list],
alpha = alpha_shadow_init
})
timer_next_shadow = 0
end
end

冲刺特效的实现逻辑相对简单,依托于引擎的话可以使用粒子系统进行实现,且性能会更佳。

看一下在 EtherTK 中的实现效果:
冲刺特效

最后贴一下万能的 Lerp 函数的实现,用来处理摄像机跟踪或滑铲效果极佳:

1
2
3
4
5
6
-- 二维向量插值
local Lerp = function(vec1, vec2, dist)
local vec_dir = vec2:sub(vec1)
dist = math.min(dist, vec_dir:size())
return OpenGL.Vec2(vec1:add(vec_dir:normalize():mul(dist)))
end

源码链接

感兴趣的朋友可以在这里查看本项目源码:https://github.com/VoidmatrixHeathcliff/DeconstructKatanaZERO/tree/main/FlashEffect

作者

Voidmatrix

发布于

2023-06-06

更新于

2023-06-06

许可协议

评论