作为光影包与纹理包开发者,我为什么留在Optifine

2023-08-11 20:21:53 来源: 哔哩哔哩

观前提示:本文带有强烈的主观色彩和对Optifine的强烈偏袒,并且由于撰写本文时笔者血压过高,逻辑可能比较差。如果您认为Optifine罪该万死应该立刻消失,让所有人投奔Sodium和Iris的怀抱的话,请立即关闭本篇专栏。


(相关资料图)

熟悉笔者的人都应该知道,笔者经常在各种地方说Sodium和Iris两个模组的“坏话”,而对于不熟悉笔者的人来说,看一看笔者的签名也大概能知道笔者对这两个模组的态度了。不过由于Sodium和Iris的用户越来越多,不少地方都有人对笔者说Optifine怎么怎么烂,Iris和Sodium怎么怎么好,应该去用那两个模组并且做它们的支持之类的话。笔者不想就同一个话题在不同的地方重复回答,同时也对这俩模组实在有许多不满,今天就在这里发一个专栏,详细地作为一个资源包与光影包双包开发者讲述一下笔者为什么不喜欢Sodium与Iris,以及为什么坚持停留在Optifine。以及,在以后还有人问我这方面问题的时候,有一个可以直接发送的链接。

前言:为什么停留在Optifine

笔者自然知道为什么Sodium与Iris会这么受欢迎。倾向于Sodium与Iris的人里面,除去跟风,无非是出于三个原因:优化好,兼容性好和开源。第一个是否在绝大部分情况下成立还有待商榷,不过第二个和第三个是实打实的。由于Optifine的工作方式是暴力修改与注入原版代码,受限于Minecraft的EULA注定无法开源;而对原版代码的暴力修改也使得模组兼容性差是理所当然的事情。不过由于笔者的情况比较特殊,因为笔者本身几乎没有mod需求,不管是玩Minecraft本身还是做资源包与光影包开发,都只需要装载一个Optifine,甚至不需要通过Forge安装,导致笔者几乎不会受到Optifine的兼容性带来的负面影响,自然对于Optifine这方面的容忍度也相当高。

Optifine也并非没有给笔者带来困扰,笔者在Github也给Optifine提过不少issue。但是笔者所碰见的Optifine的问题,除去适配新MC版本时产生的问题之外,对笔者来说影响都相对很小,甚至可以忽略不计。而Sodium与Iris相较于Optifine,对于笔者的开发体验来说都是无比的糟糕,同时也给不少笔者熟悉的资源包/光影包开发者带来了很多的困扰。笔者会在下面详细的讲述这些非常不愉快的开发体验。

Sodium:优化?还是破坏?

由于笔者的资源包(3D Default)大量使用了Optifine的资源包特性,包括但不限于自定义实体模型与发光纹理等,在Sodium出来的一段时间内笔者完全没有尝试过Sodium+Iris的组合。直到有人来反馈资源包的问题,笔者无法复现并让他排除mod的影响,最终测出来是Sodium的问题,笔者才意识到Sodium可能破坏了甚至是对原版资源的兼容性。

上面说的问题就是 /CaffeineMC/sodium-fabric/issues/226 ,表现为完整方块的模型,只要关闭了模型的ambientOcclusion(环境光遮蔽),就会导致模型位于本方块所处空间内的部分全部变黑,也同样影响了游戏内硬性关闭环境光遮蔽的方块,例如各种光源方块。根据笔者个人的推测,这是由于Sodium对于原版的AO系统进行了更改导致的(当然如果有了解这一块的人欢迎指正)。这个问题经历了长达两年的时间才得到解决,笔者不清楚是因为大伙都不把这当回事,还是Sodium开发者+有意向的社区贡献者都一直没法解决,又或者这个bug本身就是Sodium的刻意为之的优化产生的,总之这个bug导致笔者对Sodium的第一印象就非常之差。

Sodium对笔者的影响主要就是这一个问题,但是笔者从其他资源包的作者那也听到过数个Sodium的问题。一个是Sodium与部分mod一起加载的时候会导致哞菇背上的蘑菇使用的模型异常,如果资源包作者在对应蘑菇的方块状态文件内配置了多个随机模型,那么哞菇背上的蘑菇模型就会迅速的在这几个被配置的模型中切换,产生严重的闪烁。这个问题在停用Sodium后会恢复正常。

另一个问题的影响对于使用3D模型的资源包作者来说影响则广泛得多:Sodium会降低模型顶点位置的精度。由于浮点数的精度是有限的,顶点着色器最终输出的位置在几乎所有情况下都不是实际准确的,导致了顶点位置会随着视角转动和玩家移动产生微小的变化;而如果模型的几个面有一部分重合,就会在这种变化中遮蔽关系不断变化,产生非常迅速的闪烁。而即使是静止不动,也能看见两个面在间断的互相遮蔽。我们把这种现象叫做深度冲突(z-fighting)。

有些时候,出于优化或者其他视觉效果上的目的,部分面的重合无法避免。于是,为了避免深度冲突,大部分模型作者就会把一个面前移一些,或者把一个面后移一些。移动的幅度一般不会很大,以避免破坏视觉上的效果。但是Sodium降低顶点精度之后导致了一个问题:如果移动幅度不够大,Sodium降低顶点位置精度后还是重合在一起,照样深度冲突;如果移动幅度太大,又会影响整体的视觉效果。笔者对Sodium产生这个问题的原因有较为详细的了解,并且笔者认为这就是他们优化带来的无可避免的副作用,几乎不存在修复的可能。

Iris:最让人高血压的一集

笔者不怎么喜欢Sodium/Iris这套组合,绝大部分得拜Iris所赐。在Iris 发布之前,笔者给GFME做了Iris的兼容,已经算是给够面子了。毫不客气的说,每一次使用Iris,都能给笔者带来全新的极为负面的体验,导致笔者出于测试的目的使用了几次Iris之后,已经再也不想碰它了。

首先来聊点震撼的。做着色器开发时碰到问题时,我们一般会使用NVIDIA Nsight Graphics或者Renderdoc等工具进行详细的debug,同时也能提供性能参数作为优化时的参考。但是Iris使用Nsight Graphics的时候有一个非常严重的问题:它会在日志中打印大量无用的OpenGL debug message,多的时候一秒钟甚至能打印上万条,不说在这种密度的无用信息中找有效信息的难度,海量的日志还会大量拖慢不少启动器的日志界面的性能,同时占用相当大的磁盘空间和内存空间。如果对Nsight Graphics启动的Iris的日志大小没有什么概念的话,笔者这里有一个现成的例子:接近5分钟的时间,Iris产生了一个600MB大小,156万行的日志。笔者曾经在ShaderLABS Discord频道吐槽过用Nsight Graphic开启Iris时输出过多的日志信息,Iris主开发之一的IMS的回答是“Optifine直接把OpenGL的调试信息彻底关了,这样做不好”。可惜这个五分钟产生的600M日志是在这件事之后产生的,不然笔者一定要把这个log拍到IMS脸上,相较于产生一个这样的玩意,笔者宁愿不输出那一大堆没有用的GL调试信息。

Iris本身在合并着色器文件的时候也根本没有考虑过着色器开发者。经常看日志的玩家应该能看见Iris在编译着色器之前会删除未使用的函数————出发点是好的,每次切换光影设置都预先把宏应用了然后删除未使用函数可以尽可能的减少对着色器的影响,减少需要重新编译的着色器的数量,以提升修改光影设置之后的重载光影的性能。但是Iris有一个大问题:合并着色器,应用宏和删除未使用函数的时候,都没有使用#line宏规范文件序号和行数,导致如果着色器在Iris上编译报错,报错的内容基本上没有用————文件序号恒定为0,而行数是执行合并后作为一个整体的文件的,只能通过后面的报错内容猜测具体是什么位置,或者在开启Iris的开发者模式后,在.minecraft/patched_shaders文件夹找到出问题的着色器,根据Iris日志里提供的报错行数找到错误位置,通过上下文猜测这一段是在哪个文件的哪一行。同时,大量的删除未使用函数也会影响到日志的阅读,导致有效信息获取变得更加困难(虽然说Iris的日志本身就没什么有效信息)。而在Optifine内,几乎所有的文件合并,以及Optifine自己加的宏,都通过#line进行了规范,如果着色器编译错误,在绝大部分情况下都能在日志中给出准确的文件序号和行号,同时会给出所有的文件序号对应的文件。

目前版本的Sundial在Iris上运行时有不少问题,这得归功于Iris的Optifine功能兼容工作:同一个功能做了一部分,但是没有做完。Iris在声称支持了自定义uniform,而后来在声称支持了Optifine在+上的core profile写法。有自定义uniform的光影在+的Iris上确实能跑了,但是不少光影的自定义uniform仍然存在问题————Iris没有实现自定义uniform的所有功能。Sundial没有受到Iris的自定义uniform不完善的影响,不过不少国内外光影都受到了影响,比较明显的是群系雾:Iris没有做自定义uniform里群系ID的支持,只有群系的大体分组的支持。这导致了Bliss与itt等部分光影依赖于群系ID的群系雾在Iris上完全无法正常工作。Sundial受到影响的地方是+的Core profile支持。Sundial使用“区块位置”(在以下是gl_,在以上是vaPosition)进行完整方块的判断。由于Iris声称支持了Optifine的core profile但是没有完全支持,导致Sundial的完整方块判断几乎所有方块都能通过,使得竹子、花盆等本来在体素化时会被忽略的方块参与了体素化,甚至连手持物品都能写入体素,产生了大量异常的黑影。

如果说上面的问题还只是让人觉得他们写代码不注意细节,笔者在帮助国内其他光影测试Iris兼容性的时候发现的问题则更让人担心Iris的代码质量:如果在gbuffer内对特定缓冲指定特殊的混合方式,这种特殊的混合方式可能会泄漏到后处理部分。IterationT 3是被这个bug影响的最严重的光影,产生的现象包括但不限于捡起部分物品时白屏(这一段时间整个colortex1的混合方式都变成了根据alpha混合,而Tahnass有没有特殊数据要写入时给alpha通道输出0的习惯)、手持部分物品时反射错误(IterationT在某个地方使用输出的alpha通道存储反射相关数据)等。

此外,Iris还有着独特的着色器读取机制:如果一组计算着色器组(例如至composite_为一组)中某个后缀没有出现,则剩余的计算着色器都不会读取(具体描述参考 /IrisShaders/Iris/issues/1840)。这也是笔者在Iris的github反馈的唯一一个issue————之后越用越糟心,干脆不用了,自然也不去反馈了。IMS在这个issue下的回答是这么做是为了改善光影加载时间。好吧,从这个回答我们就能看出来Iris是怎么读着色器的:它会按照着色器正常的加载顺序一个个查着色器在不在,在的话就读取,不在的话就不读取;而开始的Optifine最高可以有一万多个着色器,其中绝大多数都是计算着色器,所以这个“优化”看起来还算合理。但是实际上这种读取方式本身优化就非常差。笔者并没有去仔细读过Iris的代码,不过从笔者的个人经历外推的话,如果他们代码写的差一点,每个着色器文件都去查询文件在不在,那性能确实稀烂;如果他们代码写的好一点,先把所有存在的文件存一个哈希表或者是别的表,然后再在表里面查文件名存不存在,那还相对好不少。但是实际上存在着更优的解决方案:直接读取目标目录(比如shaders,或者shaders/worldxx内的维度专属着色器)下的所有文件,对这些文件进行分类和排序,然后按照顺序直接做接下来的缓冲绑定与着色器编译工作。使用这种读取方式不仅在效率上快得多,而且可以做到着色器的完全覆盖,不会像现在一样为了优化被迫在计算着色器的读取上做出妥协。当然,这个bug的受害者也是IterationT 3,Tahnass使用计算着色器写泛光效果用的是Dual Blur,降采样和升采样是成对出现的,而Tahnass大概是觉得效果已经够了没必要,删除了一组升降采样,导致计算着色器组的后缀出现了一个空缺,这导致了Iris直接放弃了读取后方的升采样着色器,最终使IterationT 3在读取泛光时,读取到的是场景的法线数据,从而产生了过亮的现象。

在笔者撰写这篇专栏的时候,IterationT 3在最新的的Iris上又出现了一个bug:gbuffer的alphaTest失效了,草、火把等本应忽略透明像素的方块因为未知原因没有忽略透明像素,让许多方块的模型渲染出来与原版有较大差异。如上文所说,因为每次使用Iris都能给我带来全新的负面体验,笔者这次完全没有兴趣去测试这个新bug是怎么回事,Tahnass会不会修复就不知道了。

结语

我知道Optifine的模组兼容性差,我也知道Sodium/Iris因为开源和使用mixin前景更广阔,但是从我个人的开发体验来看,Sodium+Iris除了提供了一些诸如SSBO或者一些额外image之类的新特性外,对开发带来的负面体验远大于新特性带来的正面感受。而在与Iris的接触中看到的各种细节问题更是让我担心他们的代码质量与开发态度。因此,我在现在,与至少未来的一段时间内,不会有使用Iris,或者解决Iris独有bug的想法。谨在此撰写这篇专栏,以方便未来有人再次询问相关问题时直接粘贴链接。

标签:

[责任编辑:]

最近更新