拔刀剑:迟来的礼物
简介
📖 这是什么?
这是一个面向 SlashBlade: Resharped 的客户端渲染优化模组,专门解决拔刀剑在持刀、挥刀、落地刀、光影环境和 YSM 模型下掉帧严重的问题。
一句话总结:持刀翻倍,挥刀翻三倍,落地刀翻十几倍。
📊 帧数对比
模组版本 1.0.2
测试配置:AMD Ryzen 7 9700X + NVIDIA RTX 5070 Ti
第二人称视角,BSL 光影,炼金王国
无光影
| 场景 | 原版 | 加速渲染 | 迟来的礼物 | 提升(vs 原版) |
|---|---|---|---|---|
| 持刀 | 359 | 412 | 810 | +126% |
| 挥刀 | 220 | 248 | 620 | +182% |
| 十七把高模刀落地 | 61 | 425 | 730 | +1097% |
开启光影
| 场景 | 原版 | 加速渲染 | 迟来的礼物 | 提升(vs 原版) |
|---|---|---|---|---|
| 持刀 | 208 | 330 | 381 | +83% |
| 挥刀 | 115 | 153 | 365 | +217% |
| 十七把高模刀落地 | 29 | 240 | 358 | +1134% |
兼容 YSM 模型
| 场景 | 原版 | 加速渲染 | 迟来的礼物 | 提升(vs 原版) |
|---|---|---|---|---|
| 无光影 · 持刀 | 374 | 398 | 724 | +94% |
| 无光影 · 挥刀 | 208 | 362 | 605 | +191% |
| 光影 · 持刀 | 215 | 301 | 374 | +74% |
| 光影 · 挥刀 | 172 | 261 | 345 | +101% |
⚙️ 怎么做到的?
刀模不再"每帧重画"
原版每次渲染都要遍历 OBJ 模型的所有分组、面和顶点。本模组在模型加载时就把数据"烘焙"成 GPU 可直接读取的格式,之后每帧直接复用。
静态 VBO 缓存
刀身、刀鞘、物品图标、刀架上的刀、落地刀共用 96 MiB LRU 缓存。同一把刀第二次出现时,直接复用上次上传到显存的数据,不再重新提交顶点。
附魔闪光也有独立的静态缓存,不再额外拖慢帧率。
光影下"聪明地省"
- 阴影阶段用 32 段的简化刀模代替完整高模
- 刀光、次元斩等透明特效不进入阴影贴图阶段
- 发光刀身不重复投射阴影
挥刀动画"只算必要的"
原版挥刀会计算完整 PMD 模型的逐顶点蒙皮,但实际只用了两个骨骼挂点的位置。本模组跳过无用的顶点计算,只保留骨骼姿态。
刀光特效按距离降级
近距离全细节,中距离减少层数,远距离只保留主体,光影模式下进一步简化。
首次不卡顿
在进入游戏前就预热好模型、纹理和 RenderType,第一次挥刀或打开物品栏不再突发卡顿。
资源自动清理
退出世界或重载资源包时自动清理缓存,不会越玩越卡。
保留兼容与安全回退
保留 RenderOverrideEvent,遇到动态 UV、动态透明度或无法缓存的网格时自动回退原版渲染路径,不会崩溃。
⚠️ 不兼容
不兼容 [加速渲染] 模组,两者功能重叠,同时安装可能导致渲染异常或性能下降。
下载方式
CurseForge: https://www.curseforge.com/minecraft/mc-mods/slashblade-a-belated-gift
刀光渲染接口
该优化模组会导致其他附属通过 Mixin 修改 SlashEffectRenderer.render 失效,
开发者可以通过该接口修改刀光,同时拥有更好的性能。
只替换刀光纹理
在 FMLClientSetupEvent 的入队任务中注册:
import cn.star.a_belated_gift.api.client.SlashEffectRenderRegistry;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
private static final ResourceLocation RULE_ID =
new ResourceLocation("example", "special_blade_slash");
private static final ResourceLocation SLASH_TEXTURE =
new ResourceLocation("example", "model/util/special_slash.png");
private static void onClientSetup(FMLClientSetupEvent event) {
event.enqueueWork(() -> SlashEffectRenderRegistry.registerTexture(
RULE_ID,
100,
effect -> effect.getOwner() instanceof Player player
&& player.getMainHandItem().is(EXAMPLE_BLADE.get()),
SLASH_TEXTURE
));
}registerTexture 会继续使用 SlashBlade 默认的 model/util/slash.obj。
同时替换 OBJ 和纹理
import cn.star.a_belated_gift.api.client.SlashEffectRenderDefinition;
import cn.star.a_belated_gift.api.client.SlashEffectRenderRegistry;
private static final SlashEffectRenderDefinition SPECIAL_SLASH =
new SlashEffectRenderDefinition(
new ResourceLocation("example", "model/util/special_slash.obj"),
new ResourceLocation("example", "model/util/special_slash.png")
);
private static void onClientSetup(FMLClientSetupEvent event) {
event.enqueueWork(() -> SlashEffectRenderRegistry.register(
new ResourceLocation("example", "special_blade_slash"),
100,
effect -> effect.getOwner() instanceof Player player
&& player.getMainHandItem().is(EXAMPLE_BLADE.get()),
SPECIAL_SLASH
));
}这个注册重载会自动把定义中的 OBJ 和纹理加入预热列表。
动态 Provider
需要根据实体状态动态选择多个刀光定义时,实现 SlashEffectRenderProvider:
import cn.star.a_belated_gift.api.client.SlashEffectRenderDefinition;
import cn.star.a_belated_gift.api.client.SlashEffectRenderProvider;
import mods.flammpfeil.slashblade.entity.EntitySlashEffect;
import java.util.Collection;
import java.util.List;
public final class ExampleSlashProvider implements SlashEffectRenderProvider {
private static final SlashEffectRenderDefinition BLUE =
SlashEffectRenderDefinition.withTexture(
new ResourceLocation("example", "model/util/blue_slash.png"));
private static final SlashEffectRenderDefinition RED =
SlashEffectRenderDefinition.withTexture(
new ResourceLocation("example", "model/util/red_slash.png"));
@Override
public SlashEffectRenderDefinition resolve(EntitySlashEffect effect) {
if (!(effect.getOwner() instanceof Player player)
|| !player.getMainHandItem().is(EXAMPLE_BLADE.get())) {
return null;
}
return effect.getIsCritical() ? RED : BLUE;
}
@Override
public Collection<SlashEffectRenderDefinition> warmupDefinitions() {
return List.of(BLUE, RED);
}
}event.enqueueWork(() -> SlashEffectRenderRegistry.register(
new ResourceLocation("example", "dynamic_slash"),
200,
new ExampleSlashProvider()
));resolve 返回 null 表示当前规则不匹配,注册表会继续检查下一项。所有可能返回的资源都应通过 warmupDefinitions 声明。
匹配顺序
- 优先级数值较大的规则先匹配。
- 相同优先级按注册 ID 排序,结果不受模组加载顺序影响。
- 第一个返回非
null定义的 Provider 生效。 - 没有规则匹配时使用 SlashBlade 默认模型和纹理。
- 使用相同注册 ID 再次注册会替换旧规则。
- Provider 抛出的异常会被隔离并记录,其他规则仍可继续匹配。
注销
boolean removed = SlashEffectRenderRegistry.unregister(
new ResourceLocation("example", "special_blade_slash")
);注销仅影响后续刀光选择,已经预热的资源会由 Minecraft 的资源生命周期管理。