
拔刀剑:迟来的礼物
拔刀剑渲染重构与优化——献给每一位拔刀剑爱好者!
迟来的礼物 — 拔刀剑渲染优化模组
📖 这是什么?
这是一个针对 拔刀剑:重铸版 的客户端渲染优化模组,旨在消除手持刀剑、挥刀、渲染掉落刀剑、使用光影或加载 YSM 模型时出现的严重 FPS 下降问题。
一句话总结:持刀帧率翻倍,挥刀帧率提升三倍,掉落的高模刀剑获得 30 倍性能提升。
📊 性能对比
模组版本:1.0.2
测试配置:AMD Ryzen 7 9700X + NVIDIA RTX 5070 Ti
第三人称视角,BSL 光影,测试刀剑:炼金王国
无光影
| 场景 | 原版 | 加速渲染 | 迟来的礼物 | 提升幅度(对比原版) |
|---|---|---|---|---|
| 手持刀剑 | 359 | 412 | 810 | +126% |
| 挥刀 | 220 | 248 | 620 | +182% |
| 17 把高模掉落刀剑 | 61 | 425 | 730 | +1097% |
光影(BSL)下
| 场景 | 原版 | 加速渲染 | 迟来的礼物 | 提升幅度(对比原版) |
|---|---|---|---|---|
| 手持刀剑 | 208 | 330 | 381 | +83% |
| 挥刀 | 115 | 153 | 365 | +217% |
| 17 把高模掉落刀剑 | 29 | 240 | 258 | +1134% |
YSM 兼容性
| 场景 | 原版 | 加速渲染 | 迟来的礼物 | 提升幅度(对比原版) |
|---|---|---|---|---|
| 无光影 · 手持 | 374 | 398 | 724 | +94% |
| 无光影 · 挥刀 | 208 | 362 | 605 | +191% |
| 光影 · 手持 | 215 | 301 | 374 | +74% |
| 光影 · 挥刀 | 172 | 261 | 345 | +101% |
⚙️ 工作原理
不再“每帧重绘”
原版渲染在每一帧都会遍历 OBJ 模型的每个分组、面和顶点。本模组在加载时将数据“烘焙”为 GPU 友好的格式,之后每帧只需直接复用即可。
静态 VBO 缓存
刀身、刀鞘、物品图标、刀架上的刀以及掉落刀剑共享一个 96 MiB 的 LRU 缓存。同一把刀第二次出现时,直接复用已上传到显存的数据,无需重新提交顶点数据。
附魔闪光效果也拥有独立的静态缓存,不再拖累性能。
光影下的“智能节省”
- 阴影渲染时使用简化的 32 段代理网格,而非完整的高模模型
- 透明特效(斩击特效、次元斩等)完全跳过阴影贴图阶段
- 发光刀身不再重复投射阴影
挥刀动画 —— “只做必要计算”
原版在每次挥刀时都会计算完整的 PMD 顶点蒙皮,但实际上只使用了两个骨骼锚点。本模组跳过了无用的顶点计算,仅保留骨骼姿态。
刀剑特效 LOD
近距离显示完整细节,中距离减少层级,远距离仅保留核心刀身——在光影下还会进一步简化。
无首次使用卡顿
模型、纹理和 RenderType 会在进入游戏之前完成预热。首次挥刀或打开物品栏时不会突然卡顿。
自动缓存清理
退出世界或重新加载资源时自动清除缓存——长时间游玩性能保持一致。
兼容性与安全回退
保留 RenderOverrideEvent。如果检测到动态 UV、动态透明度或不可缓存的网格,会自动回退到原版渲染——不会崩溃。
⚠️ 不兼容性
与 [加速渲染] 不兼容 —— 两个模组功能重叠。同时安装可能导致渲染异常或性能下降。
斩击效果渲染 API
本 API 面向需要替换斩击效果模型或纹理的拔刀剑附属模组。附属模组只需选择资源,而“迟来的礼物”继续负责基于距离的 LOD、光影兼容性、网格优化和资源预热。
采用此 API 后,附属模组不得使用 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 继续使用拔刀剑默认的 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
));
}
此 register 重载会自动将定义的 OBJ 和纹理添加到预热列表中。
动态提供器
当定义需要根据特效状态动态选择时,实现 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定义的提供器胜出。 - 当没有规则匹配时,使用拔刀剑默认的模型和纹理。
- 重复注册相同 ID 会替换之前的规则。
- 提供器异常会被隔离并记录日志,允许其余规则继续匹配。
注销规则
boolean removed = SlashEffectRenderRegistry.unregister(
new ResourceLocation("example", "special_blade_slash")
);
注销仅影响后续的斩击效果选择。已预热的资源由 Minecraft 的资源生命周期管理。
正在加载版本记录…
正在加载评论…
评论在新手盒子客户端中发表,这里同步展示。