MCJE着色器教程——从开发入门到游戏崩溃(四)——后处理着色器(Post Json)
后处理json被放置在post文件夹中,它的名字决定了Minecraft会在什么时候调用它。当你看到这个文件夹在minecraft命名空间下时,你就应该想到,没错,这是原版的东西。我们目前只能通过替换原版的后处理json来达到自定义着色器的效果。目前可用的后处理json名字如下:
creeper.json
:旁观模式下的苦力怕视角invert.json
:旁观模式下的末影人视角spider.json
:旁观模式下的蜘蛛视角entity_outline.json
:当屏幕上有发光实体时使用transparency.json
:“极佳!”画质时被使用
后两者因为容易操控等特效是最常替换的JSON文件。在原版的资源包中,你可以在post文件夹下找到许多其他的json文件,这些文件在1.9之前被用于“超级秘密选项”。随着这一功能的移除,这些json文件不再被调用,但是仍然被保留了下来。
后处理json的文件格式如下:
targets
在第一节中,我们介绍了缓冲。targets列表中就定义了一系列的缓冲(渲染目标)。这些缓冲将用于此json后面连接着色器的时候使用。注意,正如图中看到的,transparency.json中必须包含名为translucent
、itemEntity
、particles
、weather
、clouds
的缓冲,而entity_outline.json中必须包含名为final的渲染目标。如果没有包含需要的缓冲,会导致严重的错误,轻则资源包卸载,重则游戏崩溃。
列表中的每一个元素都定义了一个缓冲,这些缓冲可以不全被使用。元素可以是一个字符串,也可以是一个单独的object。前者默认大小为屏幕分辨率,后者则可以手动指定缓冲的高度和宽度。
缓冲手动指定宽度和高度的时候,便是以左下角为原点,向上为y正轴,向右为x正轴,长宽为指定大小的画面。例如指定了一个200*200的缓冲,让它输出到minecraft:main显示,效果如下:
passes
passes列表定义了从输入到输出的完整渲染的过程。每一个元素就指定了使用某一个着色器程序,这些着色器程序合起来,就构成了完整的渲染过程。
name
是一个着色器程序JSON的名字(上面才讲过的东西,可别晕了)。
intarget
和outtarget
分别是一个输入缓冲和一个输出缓冲。输入缓冲在经过着色器程序处理以后,将会赋给输出缓冲。两者不可相同。若指定为"minecraft:main"
,则表示游戏屏幕(最初的画面)。特别的,若前面的name
指定为"blit"
(这也是一个着色器程序,并不是什么有特殊含义的字符串),就相当于将intarget
中的数据直接复制到了outtarget
中。
auxtargets
就是我们前面提到的采样器。name对应于着色器程序json中的samplers中的name。id图中说的还算清楚。值得注意的是,DiffuseSampler并不需要在这里声明就能调用。DiffuseSampler可以理解为游戏画面,将会被游戏自动赋给处于intarget中的着色器。
双线性插值(bilinear)
双线性插值,又称为双线性内插。在数学上,双线性插值是有两个变量的插值函数的线性插值扩展,其核心思想是在两个方向分别进行一次线性插值。双线性插值作为数值分析中的一种插值算法,广泛应用在信号处理,数字图像和视频处理等方面。
看下面的图就懂了。图片来源:(原版着色器指导 | spgoding.com)
关于深度缓冲(深度缓冲_百度百科 (baidu.com))
当三维图形卡渲染物体的时候,每一个所生成的像素的深度(即z坐标)就保存在一个缓冲区中。这个缓冲区叫作z缓冲区或者深度缓冲区,这个缓冲区通常组织成一个保存每个屏幕像素深度的x-y二维数组。如果场景中的另外一个物体也在同一个像素生成渲染结果,那么图形处理卡(GPU)就会比较二者的深度,并且保留距离观察者较近的物体。然后这个所保留的物体点深度保存到深度缓冲区中。最后,图形卡就可以根据深度缓冲区正确地生成通常的深度感知效果:较近的物体遮挡较远的物体。这个过程叫作z消隐。
uniforms
很有意思。在着色器程序JSON中我们事实上已经定义了uniform变量并对其进行了赋值。在这里是对已经声明并赋值的uniform变量进行再赋值,就好像调用函数传入参数一样(只是这个函数的每一个形参都有默认值)。
下面是一个完整的后处理JSON的例子(使用了entity_outline.json):
{ "targets": [ "final" ], "passes": [ { "name": "prog1", "intarget": "minecraft:main", "outtarget": "final", "auxtargets":[ { "id":"test", "name":"test", "width":100, "height":100, "bilinear":false } ] }, { "name": "blit", "intarget": "final", "outtarget": "minecraft:main" } ] }
例子中声明了final这一个缓冲。当然,这个缓冲也是必须声明的缓冲。然后使用了两个着色器程序,一个是prog1,一个是blit。因为blit是起到单纯的数据传输作用,因此实际起作用的是prog1(这个着色器程序JSON就是我们上文讲解着色器程序JSON中使用的例子)。在prog1中我们向其中传递了一个采样器,这个采样器对应的是我们资源包中<命名空间>/textures/effect文件夹下的文件(不包含拓展名)。因为我直接将图片放到了minecraft命名空间下,因此就不额外写命名空间了。
着色器渲染的顺序
在Minecraft中着色器是按照一定的渲染顺序调用:
- 渲染普通方块和实体到
minecraft:main
缓冲中 - 渲染纯色的发光实体到final(发光实体实际上是先按照实体的边缘渲染了一层纯色的图层,之后再将实体叠加到这个图层上)
- 调用
entity_outline.json
,对minecraft:main
和final
进行处理 - 渲染其他的东西(手、水、方块实体)到
minecraft:main
- 把
final
覆盖到minecraft:main
上 - 运行实体视角着色器(例如
creeper.json
),处理minecraft:main
通过发光着色器向
minecraft:main
写入数据(第 3 步)似乎会破坏掉有关深度的信息,使得其他的东西(第 4 步)被渲染到minecraft:main
的所有东西之上。
由此可以看出,final中其实储存了特定的数据。同时,为了避免minecraft:main深度被破坏的问题,我们大部分情况会采用更改transparency.json
来应用着色器。如果你在使用着色器之后出现了一些奇怪的现象,可以参考原版资源包中的json文件对比看看。
终于,资源包调用着色器的两个重要JSON文件讲完了(摊)。其中大量涉及硬核代码知识的部分我都经可能进行了通俗化处理,当然也可能因此会导致一些偏差产生,还望各位读者指出斧正。下一节中,我们就会讲解一个详细的例子了,即使你现在尚有些糊涂,我相信你在看了例子以后必然会有豁然开朗之感。加油,最难的一部分已经过去了!
彩蛋:test.png: