结构和管线
OptiFine 加载光影的方法
OptiFine 会遍历 shaderpacks 下的文件夹和 .zip 压缩包,将所有包含 shaders 文件夹的内容都视为光影外壳(shaders 也是这一层级下唯一的寻找目标,其中包含了所有着色器和配置文件),我们将它们称为外壳文件夹 。除了读取外壳文件夹,OptiFine 还会读取 .txt 文件,如果这个文件的名字与外壳文件夹相同(如果是压缩包,则包含 .zip 后缀名),则会视为光影设置文件。
需要注意的是,OptiFine 会先寻找外壳文件夹,然后在外壳内寻找 shaders 文件夹,因此,如果将一个包含着色器文件的 shaders 文件夹直接放在 shaderpacks 下,或者外壳内还嵌套了一个文件夹(常见于压缩包,一些压缩软件会在打包时自动新建一层文件夹),则在新版本的 OptiFine 下不会读取。
老版本的 OptiFine 会认为前者的名字就叫 shaders ,并把它视为外壳文件夹,然后试图寻找这个外壳文件夹中的 shaders 文件夹;如果是后者的情况,OptiFine 只会试图在第一层外壳下寻找 shaders 文件夹。因此这两种情况都会导致光影虽然显示在列表中,却无法正确加载。
我们将 shaders 文件夹称为根文件夹 ,对应的 <光影名称>\shaders\ 称为根目录。
在 shaders 下, OptiFine 首先会寻找配置文件
shaders.properties ,当前光影的内部配置文件,我们称为光影配置(属性)。
entities.properties、 item.properties 和 block.properties ,实体、物品和方块类型在当前光影中的映射 ID,我们称为实体/物品/方块 ID 配置。
获取到配置文件之后,OptiFine 会继续寻找 lang 和 world<ID> 文件夹。
lang 文件夹下包含对可调宏定义和
const修饰符变量的翻译文件,或者说语言文件 ,它做的实际上是将 宏定义、 常量 和 选项卡 重映射为对应字符串以用于光影设置页面。语言文件的命名规则是 <语言>_<地区>.lang ,如 zh_cn.lang 。其中地区可以大写,但在某些 OptiFine 版本下,可能会导致包括中文在内的一些语言无法读取,因此,我们建议都使用小写。
光影设置会尝试读取对应当前游戏设置的语言文件,如果语言文件不存在,则先尝试读取英语语言文件(en_us.lang ),如果英语语言文件也不存在,则直接显示常量或宏名称。
world<ID> 文件夹下是对应维度的着色器, world0 表示主世界, world-1 表示下界, world1 表示末地,其他模组额外生成的世界通常也会有一个序号,如暮色森林是 world7 。OptiFine 会优先使用这里的着色器渲染对应的世界,当文件夹不存在时,会使用根文件夹下的着色器。
我们将 world<ID> 称为维度文件夹 ,对应的着色器称为维度着色器。
回到 shaders 文件夹下,OptiFine 在检查了所有默认读取的文件和文件夹之后,开始编译光影。它会将根文件夹和每个维度文件夹下着色器文件的代码编译为程序。根据管线顺序,OptiFine 会主动读取并编译 .vsh, .gsh, .csh, .fsh 文件,分别对应 顶点着色器、 几何着色器、 计算着色器 和 片元着色器。
OptiFine 允许我们使用类似 C / C++ 的 #include 宏来调用其不会主动读取的文件,使用根目录为起点的相对路径:
例如使用
来调用 .\shaders\libs\shadow.glsl 。这样做的好处是,当我们想对某些相同的程序或函数进行修改时,只需修改一次,减少出错。在我们的教程中,自定义文件的扩展名统一为 .glsl ,我们的插件会自动识别 .glsl 后缀,省去了自己设置的麻烦。当然,你也可以按照着色器的类型将其后缀设置为 .vert、 .frag、 .geom 以及其他自定义后缀。
这里总结了 OptiFine 默认读取的光影文件。
渲染管线
OptiFine 使用的是延迟渲染法:
在几何缓冲阶段将所有几何体按类型分批传入不同的着色器,并将计算结果写入缓冲区。
在延迟处理阶段,计算颜色并输出结果。
不过你也可以忽略延迟渲染阶段,直接在几何缓冲阶段进行光照等计算。
OptiFine 接管了从 GUI 开始直到场景输出的全部管线,并将几何缓冲拆成了两个部分,第一部分的大多数几何缓冲都是固体几何和裁切几何,第二轮则是半透明几何。两轮几何缓冲阶段穿插在三轮延迟处理阶段之间,在结尾处有一个额外用于处理输出的程序,它们共同构成了整个渲染管线。
下图列出了整个画面输出流程:
可以单击图片查看和保存附带深色背景的大图,我们将在下一章了解每个程序的处理对象。
