介绍
OptiFabric — 在 Fabric 上使用 OptiFine
一个将 OptiFine 引入 Fabric 的 Fabric 模组。把 OptiFabric 和你自己的 OptiFine jar 放进 mods/,剩下的交给它处理——无需手动修补 jar,无需运行 OptiFine 安装程序,也不需要 Forge。
ℹ️ 不会捆绑或再分发 OptiFine。请从官方网站获取与你的 Minecraft 版本匹配的版本——目前 1.20.6 和整个 26.x 系列(26.1.2 / 26.2)都只有预览版。
OptiFabric 按发布分支分别开发,每个分支都是独立项目;不同 jar 不能互换,并且每个文件名都标注了对应的 Minecraft 版本:
| 分支 | Minecraft 版本 | Jar | 加载器 | Java |
|---|---|---|---|---|
| 1.20.6 | 1.20.6 | OptiFabric-1.1.9+mc1.20.6.jar | ≥ 0.19.3 | 21+ |
| 1.21.x | 1.21 – 1.21.11(共十个版本) | OptiFabric-2.2.16+mc<version>.jar | ≥ 0.19.5 | 21+ |
| 26.x | 26.1.2 / 26.2 | OptiFabric-Reforged-2.2.11+mc26.1.2.jar / OptiFabric-Reforged-2.2.11+mc26.2.jar(每个版本各有一个 jar) | ≥ 0.19.5 | 25 |
支持的 OptiFine 版本
点击此链接查看支持的 OptiFine 版本。 https://github.com/Kynarain/OptiFabric-Reforged/blob/main/docs/OPTIFINE_VERSIONS.md
模组兼容性列表
点击此链接查看模组兼容性列表。 https://github.com/Kynarain/OptiFabric-Reforged/blob/main/docs/COMPATIBILITY_ALL.md
**如果你想在 1.20.6 上使用锂,请使用 OptiLithium Reforged。不过,1.20.6 之后的版本不需要 OptiLithium Reforged。
https://www.curseforge.com/minecraft/mc-mods/optilithium-reforged
性能表现
以下数据来自第三方FPS Benchmark模组(https://www.curseforge.com/minecraft/mc-mods/fps-test-benchmark)(fpstest-1.0),每种配置都进行了一次 3 分钟的脚本化测试:包含 19 个电影化场景片段(森林、村庄、战斗、红石、洞穴、下界、末地),并使用确定性种子27182。所有配置都在同一台机器上运行,使用字节级完全相同的options.txt——唯一变量是安装的模组。
| 配置 | 已安装的模组 | 平均 FPS | 1% 低帧 | 0.1% 低帧 | 最差帧 | 最低 FPS |
|---|---|---|---|---|---|---|
| 原版 Fabric(基准) | Fabric API | 3631 | 774 | 442 | 17.2 ms | 58 |
| OptiFabric + OptiFine | Fabric API + OptiFabric 2.2.10 + OptiFine 1.21.1 HD U J1 | 5040 | 1098 | 713 | 3.3 ms | 302 |
| OptiFabric + OptiFine + 锂 + FerriteCore + C2ME——我们通常的推荐配置 | 以上配置 + 锂 0.15.4 + FerriteCore 7.0.3 + C2ME 0.4.0-alpha.0.29(mc1.21.1) | 4475 | 1077 | 721 | 3.1 ms | 319 |
| Sodium + 锂(替代渲染方案) | Fabric API + Sodium 0.8.13 + 锂 0.15.4 | 5680 | 1017 | 349 | 12.5 ms | 80 |
表格说明了什么
- 在这里,OptiFabric + OptiFine 的平均表现比纯 Fabric 高 38.8%,最差帧也好 5 倍(3.3 毫秒 vs 17.2 毫秒)——OptiFine 自带的优化确实发挥了作用,因此使用这个模组并不是为了获得 OptiFine 的功能而必须付出的性能代价。
- 与 Sodium 组合相比,平均表现落后约 13%,但帧时间尾部表现更好:0.1% Low 为 713 vs 349,最差帧为 3.1–3.3 毫秒 vs 12.5 毫秒,最低 FPS 为 302 vs 80。换句话说,Sodium 的平均速度略快,而 OptiFabric + OptiFine 更稳定。
- 在此场景中,测得的三个“性能”模组表现中性至负面(5040 → 4475)。这是意料之中的:此基准测试刻意减少了世界生成和刻运行负载,而在真实的长期存档中,这恰恰是 C2ME / Lithium / FerriteCore 通常能发挥作用的地方。不要据此认为“这些模组毫无用处”。
测试环境(四次运行完全相同)
Minecraft 1.21.1 · Fabric Loader 0.19.5 · Java 22.0.2 · 最大堆内存 5836 MB · Intel i5-12600KF · AMD Radeon RX 7800 XT(驱动程序 25.12.1.251128)· Windows 10 · 渲染距离 8 · 关闭 VSync · maxFps:260(= 不限)· 关闭光影 · 相同的options.txt · 种子 27182。
原始报告(包含逐帧采样)已与源代码一同发布:https://github.com/Kynarain/OptiFabric-Reforged/tree/1.21.x/benchmarks/2026-10-05-fps-benchmark——一台机器、每种配置运行一次、一次基准测试;这些数字仅供参考,不是性能规格。
为什么需要它
OptiFine 是为原版(以及 Forge)打造的:它的补丁是针对官方混淆名称编译的,而 Fabric 使用中间命名空间。Fabric API 也会向许多相同的类中注入代码。两者结合后便会产生难以诊断的冲突——构造函数结构不同、合成字段名称不同、辅助方法被内联移除、对象创建被替换为 OptiFine 自己的子类,等等。
这些冲突并非表面问题。若在重映射器的类路径中没有游戏,重映射过程会静默地漏掉单个类中的 35 个方法,导致 281 个抽象契约损坏、254 个虚方法重写丢失。OptiFine 的重新编译器会把原版方法变成转发到其自有重载方法的薄封装——这会让 Fabric API 的注入点偏离原有位置,随后 Mixin 在加载时便会导致整个类加载失败。OptiFine 还会整体替换类(例如它的 ChunkOF 区块、包含父类在内的视频设置界面),并改写 Fabric 用于匹配注入点的 lambda 函数体。
在 26.x 系列中,问题的形式有所变化:Minecraft 26.1 及更新版本不再混淆——官方名称就是运行时名称,也没有 yarn 或可供重映射使用的真正 intermediary(26.1.2 仅发布了 0.0.0 占位版本)。因此,该系列完全不使用映射,在官方命名空间中运行,并使用自己的模组 ID(optifabric_reforged),因为一些模组会声明 `”breaks”: {“optifabric”: “*”}`,而 Fabric Loader 会根据 ID 匹配——仅更改显示名称并不足够。
它的作用
在启动的最早阶段(加载器的 preLaunch 阶段),OptiFabric 将:
运行 OptiFine 自带的安装程序,提取游戏类的补丁(从 1.21.6 起,补丁以 xdelta 差分文件形式提供;optifine.Patcher 的用法相同);
移除 volde-ification,并重建目标被 OptiFine 移动过的 lambda(LambdaRebuilder);
将补丁从 official 映射重映射为 intermediary——将游戏 jar 同时加入重映射器的类路径和输入,这样从超类型继承的成员才能保留其映射后的名称;
修复 OptiFine 与 Fabric API 之间已知的结构冲突,并将每个冲突都追溯到字节码层面;
将结果拆分为两部分:OptiFine 自身的类和资源放入游戏类路径,打过补丁的 net/minecraft/** 类放入 ClassCache;
将修复后的类交给 Fabric Loader 自带的 GameTransformer,并将所有内容缓存到 /.optifine// 下,这样后续启动就能复用缓存,无须重复处理(耗时 1–2 秒,而不是 5–7 秒)。
在 26.x 系列中会跳过步骤 2 和 3,这正是它作为独立项目存在的全部原因。
替换发生在 Mixin 之前:当 Minecraft 类即将加载时,Loader 会向游戏提供方请求 GameTransformer.transform(…) 返回现成的字节码。因此,OptiFabric 交出的是 Mixin 的输入,而不是输出——这就是其他模组针对这些相同类的 mixin 仍能正常工作的原因。由此也产生了一项硬性限制:交出这些类之前,绝不能对游戏类进行反射操作——只要调用一次 Class.getMethods(),就会加载该方法签名中涉及的所有类型,并使该类在本次会话剩余时间内固定为原版类。
缓存目录包含 cache-format.txt(当前为 26;格式不匹配时会重建所有内容)、Optifine-mapped.jar(不含 MC 类的 OptiFine,也就是放入类路径的内容)以及 Optifine.classes.gz(打过补丁的 MC 类)。
安装
使用 Fabric Loader 为你的 Minecraft 版本安装客户端(1.20.6 需要 ≥ 0.19.3,1.21.x 和 26.x 需要 ≥ 0.19.5),并使用 Java 21+——或者在整个 26.x 系列(26.1.2 / 26.2)中使用 Java 25,这是游戏本身的要求。
将 OptiFabric 和你为对应确切版本准备的 OptiFine jar 放入该实例的 mods/ 文件夹。直接放入 OptiFine 安装程序 jar 即可——无需先运行安装程序(已解压且包含 notch/ 类的 OptiFine 也能使用)。不要安装两个 OptiFine jar(游戏会报告 DUPLICATED),也不要混用来自两个系列的 jar。
使用 Fabric 配置启动游戏——不要使用启动器创建的 1.21.x-OptiFine_xxx 配置,因为它会注入 OptiFine 本身。如果运行正常,标题屏幕左上角会显示 OptiFine 版本,视频设置中也会出现 OptiFine 选项。
可以同时加载 Fabric API(此移植版专门针对它进行了适配)。启用版本隔离(PCL2 / HMCL)后,游戏目录和 mods/ 会位于 versions// 下,.optifine/ 缓存也会在那里创建。
要求
| 1.20.6 | 1.21.x | 26.x | |
|---|---|---|---|
| Minecraft | 1.20.6 | 1.21 – 1.21.11(每个版本各需一个 jar) | 26.1.2 / 26.2(每个版本各需一个 jar) |
| Fabric Loader | 0.19.3 或更新版本 | 0.19.5 或更新版本 | 0.19.5 或更新版本 |
| Java | 21+(已在 25 上测试) | 21+(已在 25 上测试) | 25 |
| 端 | 客户端 | 客户端 | 客户端 |
| OptiFine | preview_OptiFine_1.20.6_HD_U_J1_pre18.jar(仅预览版) | 你自己的构建版本,版本必须完全匹配 | 26.1.2:preview_OptiFine_26.1.2_HD_U_K1_pre2.jar,26.2:preview_OptiFine_26.2_HD_U_K2_pre4.jar(仅预览版,每个发行版本各需一个) |
| 可选 | Fabric API(支持) | Fabric API(支持) | Fabric API(已在 0.155.3+26.1.2、0.161.0+26.2 上测试) |
验证了什么
每个版本都会通过一条命令、在单个加载器中进行检查,方式与游戏加载这些类时相同:使用 JVM 验证器和 ASM 数据流验证器检查每个已修补的类及每个 OptiFine 类,随后运行五种扫描器(Mixin 成员引用、@At注入点、抽象契约 / 丢失的虚方法重写 / 无法解析的引用、invokedynamic 句柄、本地变量捕获)。
| Minecraft | 已修补的游戏类(JVM) | OptiFine 类(JVM) | ASM 验证器 | 扫描器 |
|---|---|---|---|---|
| 1.20.6 | 425 / 425 | — | 0 个问题 | 干净 |
| 1.21.3 / 1.21.4 | 440 / 440 · 474 / 474 | 816 / 816 · 812 / 812 | 0 | 仅有 Indigo 已禁用时的 @At未命中 |
| 1.21.6 / 1.21.7 / 1.21.8 | 487 / 487 · 500 / 500 · 516 / 516 | 820 / 820 · 823 / 823 · 831 / 831 | 0 | 相同 |
| 1.21.9 / 1.21.10 / 1.21.11 | 519 / 519 · 553 / 553 · 570 / 570 | 832 / 832 · 836 / 836 · 874 / 874 | 0 | 相同 |
| 26.1.2 | 567 / 567 | 879 / 879 | 0 | 所有列均为 0(此版本未声明 contains_renderer,因此 Indigo 已启用) |
| 26.2 | 571 / 571 | 879 / 879 | 0 | 全部为 0(比 1.21.x 版本更干净,后者仍有少量 Indigo 已禁用时的未命中) |
| 游戏内验证:启动、标题画面、单人游戏、多人服务器、方块 / 区块 / 物品和实体渲染、光影(使用 ComplementaryReimagined 验证)、1.21.3–1.21.11 的抗锯齿、F3 调试界面;运行期间没有 [ERROR],也没有崩溃报告。在 26.1.2 中,Indigo 会注册自己的渲染器;按方块位置生成几何体的模组(LambdaBetterGrass 的草地美化、连接纹理)使用光影时也能正确渲染。 |
已修复的兼容性问题
以下问题均由实际崩溃发现,并追溯到字节码层面:
Fabric API 的 ShaderProgramMixin 在 super() 之前注入 → OptiFine 的委托构造函数会被重写,而内联的标识符创建现在会复制游戏自身的 Identifier.ofVanilla(…) 调用(包装器精准定位该调用,而 new Identifier(…) 会绕过它);
OptiFine 留下了混淆状态且描述符不匹配的字段(例如粒子工厂表)→ 根据名称、类型和存储值重新对齐,并沿继承层次查找重写方法;
OptiFine 在重新编译时内联掉的私有辅助方法 → 恢复原版方法体,让注入重新有目标可用;
OptiFine 将方法改成转发至自身重载方法(区块构建、模型烘焙、方块轮廓、物品模型)→ 恢复原版方法体——这正是“所有模型都烘焙失败”和方块不可见问题的原因;
OptiFine 将对象创建重定向到自己的子类(ChunkOF 区块对象)→ 添加一个无操作标记,恢复 Fabric 的 NEW 注入点;
移植过来的修复器在替换构造函数时一并丢弃了字段初始化 → 不再替换该构造函数(这个问题会导致多人游戏会话在两秒后断开);
Lambda 方法体在重新编译时使用了不同的名称和签名(lambda$addMainPass$1 增加了一个参数)→ 恢复原版方法体;在 OptiFine 将方法引用替换为自己的 Lambda 时,LambdaMethodRefFix 会将它们重命名回方法名,因为该版本的 Fabric API 会根据引导句柄匹配自定义注入点;
一个区域构造函数从未存储区段位置(随后 ChunkCacheOF.renderStart() 会在空区域内触发 NPE)→ 修复器调用六参数构造函数,并从该方法已有的打包 long 值推导参数——1.21–1.21.4 的结构改为接收 ChunkSectionPos 对象,两种结构均已处理;
一个 Fabric hook 的上下文始终无法填入 OptiFine 的渲染阶段结构(BEFORE_BLOCK_OUTLINE)→ StubInjectionTargetFix 重命名目标并留下一个同名副本,导致 hook 注入到无人调用的代码中:崩溃消失了,方块轮廓仍由 OptiFine 绘制;
移动方块渲染器 hook 也遇到了同样的问题:其调用方位于另一个类中,原本会调用被注入的副本 → CallSiteRedirectFix 也重定向了该调用点(没有这项修复,多人游戏客户端约 30 秒后就会崩溃);
没有任何东西注册为渲染器时,按 F3 会崩溃——声明 contains_renderer 只能让 Indigo 退让;Fabric 自己的调试条目仍会调用 Renderer.get() → RendererApiFallback 注册了一个无操作的占位渲染器;
占位渲染器的第一个版本会导致整个会话崩溃——它使用 Class.getMethods() 读取 Fabric API 接口,在补丁后的类交接过来之前就加载了游戏类,并让它们永远固定为原版版本 → 现在占位渲染器仅根据接口的类文件本身通过 ASM 生成(不解析类型),通过 MethodHandles.findStatic 注册,并且只在类就位后注册;在 26.1.2 中,渲染器 API 还迁移到了 api.client.renderer.v1,旧的查找方式会静默失败;
不需要任何修复器修改的类不再重新计算堆栈帧——全局 override 修复器总是报告有变更,因此重新计算每个经过补丁的类会使合并后的局部变量类型退化为 java.lang.Object,游戏随即因 VerifyError: Bad type on operand stack 拒绝该类;
26.x:OptiFine 的精简壳类会留下两个同名方法,而 Fabric API 的目标名称不包含描述符 → 需要逐个调用点消除歧义(删除无人调用的重载;对仍被调用的方法重命名,并通过 CallSiteRedirectFix 跟随其调用方)。操作有误时会出现 LVTGeneratorError: Could not locate method metadata … 或 Scanned 0 target(s);
26.x:自行生成四边形的模型会悄无声息地消失——Fabric 的地形 hook 注入到原版区块构建中 BlockPos.betweenClosed 循环的位置,而 OptiFine 自己的 compile 重载没有这个循环,因此 hook 根本不会运行。FrapiTesselateBridgeFix + OptifineFrapiBridge 会将这一次镶嵌调用转交给 Fabric 的渲染器,并把四边形交给 OptiFine 自己的 BlockQuadOutput,因此顶点格式、渲染层、光照和着色器属性仍由 OptiFine 处理;只有 emitQuads 声明在 net.minecraft. 之外的模型才会被转交(原版模型仍沿用 OptiFine 的路径,否则着色器会丢失 OptiFine 的额外顶点属性);
抗锯齿:FXAA 链由游戏的后处理链加载器从 post_effect/ 加载,因此这个模组早期版本曾“贴心地”删除该文件,导致每次重新加载资源时都记录 Resource not found: minecraft:post_effect/fxaa_of_2x.json,而且每次切换抗锯齿都会报 Failed to load post chain。现在 OptiFine 自带的链文件会原样保留;而在 1.21.9 / 1.21.10 中,后处理管线会通过 gl_VertexID 绘制不带属性的全屏三角形,但 OptiFine 的 fxaa_of_*.vsh 仍会读取 Position 属性(这就是“开启抗锯齿后屏幕变黑”的问题),因此这两个顶点着色器也会相应重写。
完整列表(症状 / 原因 / 修复)见更新日志;每轮修复的证据见 docs/DEVELOPMENT.md(未混淆版本的行号见 docs/PORT_26.x.md)。
已知问题
与 Sodium 冲突——两者都是渲染器;请勿同时安装。
与 RyoamicLights 不兼容——OptiFine 会替换整个视频设置界面(包括其父类),导致该模组的注入失败,并在打开界面时立即崩溃。OptiFine 内置动态光源(视频设置 → 画质 → 动态光源),因此无需安装该模组。
依赖 FRAPI/indigo 的模组在 1.20.6 和 1.21.x 上不再使用 indigo 的自定义渲染;地形由 OptiFine 渲染,而 Renderer.get() 会返回一个不起作用的占位对象(F3 中显示 OptifineRendererPlaceholder)。在 26.1.2 上则恰好相反——Indigo 会注册自己的渲染器,并渲染使用该几何体的内容。
有两个 Fabric API 钩子被有意设为不工作状态(BEFORE_BLOCK_OUTLINE 事件不会触发;移动方块的 FRAPI 路径会被绕过)。这两种情形仍可通过原版/OptiFine 正常渲染。
1.21.6 / 1.21.7 在启用光影后会立即崩溃(NullPointerException … ShadersTex.initDynamicTextureNS 中的 “multiTex” 为 null)。问题不在光影包(三个互不相关的光影包都会以相同方式崩溃,移除光影包的自定义纹理也无济于事),也不在此模组:OptiFine 为这两个版本发布的预览构建会在首次创建纹理时注入调用,却没有建立后续构建中会建立的 setParentTexture 关联。适用于这两个版本的全部七个 OptiFine 构建都会以相同方式崩溃。不启用光影时,这两个版本都可以正常启动和游玩。
26.1.2 / 26.2 需要 Java 25——使用 Java 21 启动任一版本,都会在窗口出现前失败。这是游戏本身的要求,与此模组无关。
OptiFine 无法读取 Fabric 模组内的资源——你会在日志中看到 Unknown resource pack type: …ModNioResourcePack。这是 OptiFine 本身的限制。
与 OptiFine 版本不匹配的光影包会在日志中显示 [Shaders] Invalid program name: …、Unknown macro value: IRIS_VERSION 或 ParseException: Model variable not found: …;这些问题来自光影包。
这里只涵盖上文列出的版本——其他 Minecraft 版本需要单独测试;OptiFine 根本没有为 1.21.2 / 1.21.5 发布构建;此处涵盖的最新版本是 26.2。
暮色森林尚未完全支持——它可以进入标题界面,也确实能进入世界,但成功与否**并不稳定**:一项研究中 11 次尝试有 4 次成功;后续测试中,窗口最小化时 19 次有 5 次成功,窗口可见时 18 次有 13 次成功。**加载世界时,请保持游戏窗口可见并置于最前方**,并至少等待两到三分钟再判断是否卡住。尝试卡住不会影响存档,可以直接重试。
故障排除
缓存在哪里?/如何强制重建?/.optifine//。删除 .optifine/ 文件夹即可强制重建——升级后,缓存格式 (26) 通常会自行重建缓存。
为什么我在日志中找不到 [OptiFabric]?它的输出会显示在启动器控制台中,而不是 logs/latest.log;加载器只会将 log4j 输出写入该文件。筛选 [OptiFabric] 可查看已准备的类数量,以及被 Loader 接管的类数量。
找不到游戏 JAR 文件 / 我想手动指定其位置——添加 -Doptifabric.mc-jar=。
我想检查修补后的类——添加 -Doptifabric.extract=true;重映射后的 OptiFine 类会解压到 .optifine//optifine-classes/。
卡在加载界面——使用 jstack 获取两份线程转储(间隔约 15 秒),然后进行比较。如果线程栈完全相同且 CPU 使用率一直不变,说明确实卡住了;如果线程栈停在原生调用(glfwSwapBuffers)中,则是画面呈现问题。加载时不要最小化窗口(启用垂直同步时,渲染线程会阻塞在该调用处)。
模型/物品/纹理大量消失(日志中满是 Unable to bake … model)——某个 Fabric mixin 未能转换该类;最外层的错误消息通常掩盖了真正原因。常见原因是 OptiFine 将原版方法改成了转发方法,导致注入点发生偏移。
可忽略的常见警告 — [OptiFine] (Reflector) Class not present: net.minecraftforge.* / sun.misc.SharedSecrets(OptiFine 正在探测 Forge 和旧版 JDK),Failed to locate initialiser injection point in (class_2591,…)(应用 OptiFine 的 BlockEntity 补丁时会出现此提示),[OptiFabric] Resource not found: minecraft:shaders/post/fxaa_of_{2,4}x.json(OptiFine 正在探测 1.21.6 之前使用的链路位置;当前使用的链路位于 post_effect/),以及 Skipping bad option: lastServer.
报告问题
请附上:
logs/latest.log(如果发生崩溃,还请附上 crash-reports/ 中对应的文件——文件末尾会有一个 — OptiFabric — 部分,列出 OptiFine 版本、jar 状态和重映射后的 jar 路径);
mods/ 文件夹的文件列表;
你的 OptiFine 版本(例如 OptiFine_1.21.11_HD_U_J9)以及所运行的 Minecraft 版本。
许可与致谢
这是由 Modmuss50 和 Chocohead 移植的 Chocohead/OptiFabric,采用 MPL-2.0 许可证;移植的文件保留其原始版权声明。项目不包含也不再分发 OptiFine——它由 spx(sp614x)制作,请从官方网站获取。本项目是社区移植版,与 OptiFine、Fabric 或 Mojang 团队无关,也未获其认可或支持。
常见问题
OptiFabric Reforged 支持哪些 Minecraft 版本?
OptiFabric Reforged 支持 Minecraft Java 版 1.20.6–26.2,共 13 个版本,可用的加载器:Fabric。下载时注意选择和你游戏版本一致的文件。
OptiFabric Reforged 怎么安装?
先用 PCL2 或 HMCL 启动器安装对应版本的 Fabric,再把下载的 .jar 文件放进游戏目录的 mods 文件夹,重新启动游戏即可。开启了版本隔离的,mods 文件夹在 versions/版本名 下面。
kynarain 的其他作品
查看全部 →OptifiNeoforge
在 NeoForge 上加载 OptiFine。
OptiForge Reforged
Run OptiFine on incompatible Forge versions, loaded alongside FML.
OptiLithium Reforged
Run OptiFine and Lithium in the same Fabric client.
Legacy Tripwire
Restores legacy tripwire behavior: tripwires disarmed with shears stay disarmed and keep emitting a redstone signal until triggered again.
评论
登录后评论和评分