貓狗喵
本帖最后由 貓狗喵 于 2019-5-26 21:41 编辑
这篇文章中的内容只是我个人的理解,如果有误欢迎在下方提出,也欢迎进行补充

(本篇内容由巴哈姆特文章翻译过来,以下称着色器为 shader)

前言:

Minecraft 中关于原版 shader 的讨论非常的少 (不过 optifine 的 shader 资源包倒有不少)
最早的 shader 历史可以追溯到 1.7.2 版本,该版本中推出 super secret setting 的功能 (但在 1.9 版本移除),并且在资源包中放入 shader 相关的档案
而在 1.8 版本更新后又新增如生物发光、观察者模式附身在蜘蛛、苦力怕等生物时的 shader

早在 1.7 版本推出不久就有人写了一篇文章介绍透过资源包套用自定义 shader 的方式
不过当时还需要自己手动切换 super secret setting 才能看到效果

有趣的是,直到近期才又有人开始讨论原版 shader 的内容
最近我在 discord 群里看到这张 GIF (原文连结)

根据巴哈板友的补充,这个应该是利用 volume rendering 的技术做的
大致上的概念应该是将正方体切成非常多的小正方体,用类似微分的方式还原出 3D 的效果

我看到这张图后去找了关于原版 shader 的资料
最后找到的是这篇文章,研究如何自动切换玩家的 shader 滤镜。下面是他实作的效果


关于他如何切换滤镜的方式我会在下面简单说明

之后我就一边拆解他的档案一边上网查 shader 的写法
写着写着中二病就犯了,固定一种滤镜做解谜应该很有趣,但总觉得少了点什么
既然可以做后制特效,没有一点动画特效怎么说得过去?
然而如何控制当前的动画画格是一个很大的问题,困难点在哪我会在下面提到
而最后总算让我想到一个解决方法

关于 shader 动画的成果可以去看看我另外一篇原版模组的文章,其中时间停止的特效就是透过 shader 动画达成



基本概念介紹:

如果有写过游戏或需要GUI介面程式的人可能多多少少有听过 shader 这个东西
关于 shader 的定义这边就交给 WIKI 来解释,毕竟我也没有非常了解
在这篇文章中我使用的只有 fragment shader 的部分,也就是最单纯的对画面上每一格 pixel 的颜色进行修改,达到滤镜效果的 shader 程式

Minecraft 的 shader 是使用 GLSL 这个 openGL 开发的程式语言编写的,语法跟 C 非常的像
如果在这边讲 shader code 怎么写的话内容会太杂乱,再加上我也不是非常熟悉,所以有兴趣研究的可以拆解这篇文章中的各个范例或是上网查shader 的教学(个人推荐这个网站),这边就不多做说明了
另外 chyx 大佬也有一篇相关的介绍 (文章连结)


Minecraft 的 shader 是使用 GLSL 这个 openGL 开发的程式语言编写的,语法跟 C 非常的像
如果在这边教 shader code 怎么写的话内容会太杂乱,再加上我也不是非常熟悉,所以有兴趣研究的可以拆解这篇文章中的各个范例以及上网查 shader 的教学(个人推荐这个网站),这边就不多做说明了

而关于 shader 架构的部分我也不是很清楚,许多东西也是拆其他内建 shader 的档案或上网查来的
这边就我认知的架构来跟大家做个简介:
首先在资源包的 shaders 资料夹中会有两个子资料夹,分别是 post 跟 program
post 中我们会用到的是 entity_outline.json 这个档案,也就是生物发光用到的 shader

注意:用了这个东西之后就没办法使用原本的发光效果了,所以请斟酌使用
(或许有办法同时套用自己的 shader 与发光的 shader 运算,但我没有成功试出来)
这个 json 档中的内容主要分成 target 跟 passes 两项
  • target 的功用是列出图片暂存的 buffer,其中 previous 是上一个画面的内容,而 final 是画面最后会显示的内容。另外还有一个 minecraft:main 并不会列出来,这是在 particles、方块实体、半透明方块、第一人称玩家的手等等被显示出来前的画面
  • passes 的功用则是依序从指定 target 输入,经过指定的 shader 运算后输出到指定 target,并且可以传入某些参数给 shader 程式。

我们要做的事原则上就是把 minecraft:main 的内容输入到我们的 shader 进行运算,并输出回 minecraft:main 或输出到 final 中来做画面的显示 (似乎不能直接输出到来源 target,所以要用 swap 来作为中继 buffer,名字应该不一定要叫 swap)

program 中我们至少需要两个档案,分别是对应前面 post 中 shader 名称的 json 档与一个 fsh 档或 vsh 档 (可能同时用到两者),fsh 跟 vsh 分别就是 fragment shader 跟 vertex shader 的程式码档案,就如前面所说,这边只会介绍 fsh 档案
首先看到 json 档案,里面的 vertex 跟fragment 两项内容就决定了要使用哪个 vsh 跟 fsh 档案,而 uniforms 是要传给 shader 程式的参数,私心认为比较重要的是 InSize 跟 Time 两项参数,其中一个是你 Minecraft 画面的解析度,另一个是不断重复 0 -> 1 的,单位为秒的数值 (似乎是从游戏着色器启动的瞬间开始循环)。可惜的是,能够传入的参数类型与名称都是 Minecraft 的程式写死的,我们没有办法修改或自定义参数。
而参数中 value 的意义我也没有弄懂,如果有人知道的话欢迎补充

完成基本的架构之后就是程式的功夫了,如何运算出你想要的滤镜效果端看各位的程式能力



技术说明

首先介绍一下上面那个切换滤镜效果的作法的原理
要达成切换的效果势必要有记录这个动作
然而 GLSL 语言不能修改外部共用的参数 (read only)
因此他透过颜色的方式记录在画面中 (即画面右上角那块方形区域)
详细步骤为:

这么做的缺点是会牺牲画面上的某些像素点,以及可能刚好对到颜色而不小心触发特定 shader 运算
还有如何把指定颜色显示在玩家画面中的指定位置也是很大的问题,虽然关闭环境渲染的方块模型不会有阴影导致亮度减低,但即使给了夜视效果,不同亮度下的颜色还是会有些微不同 (燃烧中的模型实体只有顶部那面会是原始的颜色亮度)。这些都让这个技术的实用性大幅降低,不过我认为他的强大效果值得我们思考可能解决问题的方案


接着就来介绍我使用的动画滤镜的原理:(以下内容会包含我为什么这么做的思路过程,若没兴趣可以只看蓝字内容)
首先要有动画滤镜的效果,就必须要有一个会随时间变动的 variable
而我在查 shader 的教学时发现多数 shader 程式都会传入一个时间的参数,后来也发现 Minecraft 中使用的就是 Time 这个参数,于是我一开始就用 Time 尝试动画滤镜的设计
不过 Time 有两个很大的问题,其一是他的动画时长最多只有 1 秒,其二是我们没办法在游戏中知道当前Time 的数值 (就像动画材质一样,我们没办法知道当前这个瞬间他会显示哪个画格的材质)。因此使用 Time 来做一次性的动画滤镜是不可行的,只能用于随时间具有重复性值的循环动画滤镜 (如水波)

当初我卡在这边差点就放弃动画滤镜的可能性了,不过某天灵光一闪,终于想到一个解决办法
原理与上述切换滤镜效果的做法非常相似,差别在于储存颜色的方式
上述的做法在储存颜色时只有单纯的将上个画面的颜色保留下来作为纪录,而我改为将上个画面的颜色稍作修改再纪录。如此一来就能每个 frame 自动依序切换不同滤镜效果,也就是我们要的动画滤镜

至于要怎么修改纪录的颜色,最直觉的方式是每次增加或减少一点 RGB 中某个的数值。然而我并没有采用这种方式,主要原因是这样会占用非常多种颜色,用作纪录的颜色越多,意外触发 shader 运算的可能性就越高
在这边我采用的方法是使用多个像素点的颜色组合来判断,每个像素点只使用两种颜色,同时判断 10 个像素点就有 1024 种组合,将其视作二进位数字就可以简单的算出动画画格编号,足够制作 17 秒以上 60FPS 的动画
另外在选择纪录用的颜色时,我会尽量选单一颜色的值特别高 (较亮) 的颜色,因为在实际显示的画面有光线阴暗度的影响,出现较亮颜色的机率较低,也就较不易发生意外触发的状况

当然这个做法也有一些缺点
  • 需要占用画面显示的范围 (不过其实非常小,你们可以找找我成果作品中把红色记录在哪边)
  • 动画播放速度会受玩家 FPS 影响,FPS 越大播放速度越快
  • 仍无法完全解决意外触发的可能性


另外在我的成果作品中,是透过判断画面正中间的点是否为我指定的颜色,来触发指定的shader 动画,搭配在玩家脚下放置关闭环境渲染的发光方块,可以较精确的让玩家成功触发shader 动画。不同的状况下可能有其他更适合的触发方式,这部分还有很大的空间值得研究


其他还有一些细节,例如怎么避免用来触发动画的颜色看起来很突兀,这些就留给各位慢慢思考了。
这项技术目前看起来还很不成熟,很多细节需要大家多多思考研究出良好的解决方案。
这篇文章就当个抛砖引玉吧!




5/26更新
大家好,我又回来了
这次补充的东西是关于 FPS 影响 shader 动画速度的解决方案还有更好的 shader 触发方式。
FPS 影响 shader 动画速度的问题在前面已经提过了,而如果有试着去应用的人应该会发现,要触发 shader 动画这件事还真不是普通的难,上面虽然提供一种用关闭阴影渲染方块的方式,但是使用起来总差强人意。针对这两大问题,我想了一些解决方案,这次就来分享我目前认为相对好用的解决方案吧。


FPS 对动画速度的影响
首先要了解这个问题的发生原因:由于每个 frame 都会推进一格动画画格,所以当 FPS 越高的时候,画格推进速度就越快,也就导致动画速度越快。然而我们没有办法直接知道玩家的 FPS,所以我们要想办法得到这个数值(当然你也可以选择在游戏中每 tick 切换显示给玩家的颜色,不过这难度我认为相对高出许多,实用性大幅降低)。针对这个问题,我想到的方式是规律性的纪录一个数值,然后每个 frame 增加那个数值,最后再定时把那个数值记录下来。要做到这件事情需要一个规律的、按照时间变动的参数,刚好我之前提到的 Time 这个变数就完美符合这些条件。另外恰巧我在看 Onnowhere 的 twitter 有没有新的关于 shader 的东西时,发现他已经用了 FPS 侦测的技术来控制动画,于是就跟他讨论了一番。

原文:https://twitter.com/Onnowhere_/status/1117015621063938053

Onnowhere 虽然身为原版 shader 技术的领头羊,不过多数技术细节都保持着神秘,我也不方便细问。但是从对话内容可以得知他是用动画材质来侦测 FPS 的,同样符合我上面说的规律性、依照时间变化的特性,因此可以合理猜想他的做法应该跟我所想的差不多。当然如果之后他发表了他的作品,而且其中用的逻辑与我的不同那(如果闲着)再来分析一番,这边就照我的想法来解说吧。

首先需要两个变数,一个是用来每个 frame 更新 FPS 数值的,以下简称为 A,另一个是用来纪录我们侦测出来的 FPS 数值,以下简称为 B。之所以要用两个变数是因为如果只有 A 变数的话,我们只能拿到持续更新计算中的 FPS 而不是一个稳定的数值。
接下来我们把规律变化的时间拆分成三个段落,这边我取的是前 0.1 秒、后 0.1 秒与中间的 0.8 秒。之所以用 0.1 秒当最小区间是因为最小的 FPS 是 10,也就是每个 frame 会占用 0.1 秒,假如我们用 0.05 秒当最小区间,10 FPS 的玩家有可能这个 frame 是 0.96 秒,下个frame就跳到 0.06 秒了,也就是不会进入我们切割好的第一个段落。当然取 0.1 秒对于 FPS <= 10 的玩家还是有可能遇到这样的状况,不过如果 FPS 低落成这样我想玩家应该也没有动力玩下去了。
变数有了,时间也切割好之后就能开始设计程式了,这边设计的逻辑很简单。首先对于变数 A,在第一个时间区间中我们写入 0 这个数字,而在第二个时间区间中我们每个 frame 把这个数字加 1,最后一个时间区间则不更动数字。这样就能每秒分别统计其中的 0.8 秒经过了多少个 frame。而对于变数 B,它的功能是纪录变数 A 统计出来的 FPS 数值,因此在前两个时间区间中他只要维持之前记录到的数值即可,而最后一个时间区间则是将变数A 最新统计出来的数值更新过来。大略的程式概念如下:
变数 A
  1. if (Time < 0.1)
  2.     recordColor(0, varACoord); // 将0写数A变数
  3. else if (Time < 0.9)
  4.     recordColor(getFrame(PrevSampler, varACoord) + 1, varACoord); // 将A变数的值+1
  5. else
  6.     gl_FragColor = vec4(PrevTexel.rgb, 1.0); // 维持纪录的数值
复制代码
变数 B
  1. if (Time < 0.9)
  2.     gl_FragColor = vec4(PrevTexel.rgb, 1.0); // 维持上个frame的数值
  3. else
  4.     gl_FragColor = texture2D(PrevSampler, varACoord); // 从A变数复制数值
复制代码

这样我们就成功拿到 FPS 的数值了,在动画设计的时候只要拿这个数去做一些运算就可以让动画维持预期中的播放速度了。要注意的是用这段程式拿到的是 0.8 秒经过的 frame 数量,在做运算的时候记得把这个数再除以 0.8。另外需要注意的是,只有当这个 shader 正在被执行才会去更新 FPS 的数值,也就是第一次成功纪录到数值以前都是拿不到 FPS 的。
同样衍伸出的问题是,如果FPS 记录到一半shader 就被中断,下次重新执行shader 的时候会发生Time 的数值跳跃的问题,因为Time 并不是这个shader 执行时才会更新,而是随着游戏持续在更新,不过这些问题影响不大,只要曾经持续执行过1 秒以上的shader 程式就不太会有问题。
另外还有一个要注意的问题是FPS 开太高的时候,动画总共需要的画格数量可能会太多,可能会超过用来记录画格的变数的最大数值,需要自己跟玩家提示最大的FPS不应超过多少。


更好的 shader 触发方式
原本的范例中用的是关闭阴影渲染的发光(或给夜视)方块模型来显示颜色,因为这样是我之前所知道唯一可以显示出近乎原始颜色的方式。不过有在做地图的人应该都不喜欢在玩家周围放方块破坏地形吧?即使用 tp 的方式把玩家传送到充满该方块的房间再传回去也相当麻烦,而且一般生存地图无法使用。最理想的方式肯定是用实体来显示颜色,可是实体模型又不能关闭阴影渲染。

好巧不巧前些时间我刚好听闻到一个黑科技,那就是 fontjson 档案。

这东西从 1.13 被加入游戏中,在网路上的文档也是跟 shader 一样稀有。因为我有一些 cocos2dx 的开发经验,所以大概可以理解这东西在做什么,不过这边就不做详细介绍了。简单来说它可以让你指定任意字元要显示的东西,这东西可以是你自己下载的字体,也可以是图片。
这下可有趣了,有什么东西不会被其他东西挡住,会自己转向玩家,而且亮度可以轻松调到最亮?不就是实体的名条吗?这简直是??专门为了触发 shader 而存在的啊!不过用来显示名条的实体也不是可以随便选的,为了让名条保持最亮必须让实体着火,而又不能显示出火焰,本身还必须是透明的,于是 Marker 盔甲架就上榜了。其他还有一些细节需要注意,像是如果名条离玩家画面中心太远,名条在旋转面向玩家的时候,可能会转超出玩家的画面。还有名条的灰色背景会让颜色稍微变暗,所以要让那个背景避开画面中的侦测点,有兴趣的人可以去参考我的 DIO 原版模组的作法。


Atara
支持支持!!

brooke_zb
本帖最后由 brooke1999 于 2019-5-7 16:43 编辑

那天看了你回复后又心痒痒跑去巴哈看了你那篇文章,可惜我的编程基础十分薄弱,对于shader这一类更是完全没接触过,可以说是完全看不懂。
可惜了,一般玩家都玩不来shader,不然应用与地图肯定很有趣,大佬有时间也可以讲一讲一些简单的应用,比如如何通过触发某个指令使屏幕产生一些有趣的滤镜效果(恐怖地图新素材get√)

(那个把画面变成沙漠的gif真是看得我一脸懵逼)

⊙v⊙
0基础,从哪学起,dalao点个明路!
如果是那个shaderbook的话不行...到了代码那里都不带半个字解释的


森林蝙蝠
⊙v⊙ 发表于 2019-5-7 16:52
0基础,从哪学起,dalao点个明路!
如果是那个shaderbook的话不行...到了代码那里都不带半个字解释的

https://www.shadertoy.com/

森林蝙蝠
森林蝙蝠 发表于 2019-5-7 16:56
https://www.shadertoy.com/

当然要参考一些书籍,像什么learn opengl啦,opengl编程指南啦,unity shader精要啦,然后参照这里的源码试着写。

貓狗喵
⊙v⊙ 发表于 2019-5-7 16:52
0基础,从哪学起,dalao点个明路!
如果是那个shaderbook的话不行...到了代码那里都不带半个字解释的

用现成的文件去改会容易些
主要知道几个基本的函式或变数,其他就是用创意去编程了

首先要知道glsl中的RGBA以及像素点的座标都是0~1的float类型数据
例如(1.0, 0.0, 0.0, 0.5)可以代表半透明的红色
而(1.0, 1.0)可以代表画面最右上方像素点的座标

然后要理解成这个程式是每个像素点同时执行的
也就是解析度800*600的画面,同时会有480000个像素点在执行这段程式
至于现在是哪个像素点在执行程式,则从texCoord变数得知

常用变数、类型、函数就如下方列表:
  1. // 变数类型
  2. sampler2D;  // 储存画面数据的变数类型
  3. vec2;  // 与vec3, vec4同为float vector,差别是阵列长度
  4.        // vector有一些attributes如xyzw或rgba,分别代表vector中不同位置的数值,可以任意组合成其他vector或取得单一数值
  5.        // vector可以做许多基本的数值运算
  6. ivec2;  // int版本的vector系列

  7. // 常用变数
  8. sampler2D DiffuseSampler;  // 当前画面的sampler2D变数
  9. sampler2D PrevSampler;  // 上个画面的sampler2D变数
  10. vec2 texCoord;  // 目前这个像素点在画面中的位置,是个代表xy座标的vector
  11. vec2 oneTexel;  // 一个像素点在画面中xy的长度
  12. vec4 gl_FragColor;  // 当前这个像素点最后要显示的颜色

  13. // 常用函数
  14. vec4 texture2D(sampler2D sampler, vec2 coord);  // 取得sampler画面中coord位置的像素的颜色
复制代码

BlackCB.
虽然不懂c语言,但看了lz的教程还是学到了很多东西
mc能做到越来越开放也是很让人激动的呢

1305791312
MCBBS有你更精彩~

墨仔
支持支持!!

brooke_zb
大佬,恭喜精华,今天刚好看到你油管上解决了动画受玩家fps影响的视频,不如更新一下帖子?#滑稽

貓狗喵
brooke1999 发表于 2019-5-12 16:39
大佬,恭喜精华,今天刚好看到你油管上解决了动画受玩家fps影响的视频,不如更新一下帖子?#滑稽 ...

有时间会更新一下内容
刚好昨天发现一个用来触发shader动画的好东西
到时候一起整理出来

Jokey_钥匙
这样夜视效果就能更改了?

Hui先生
顶顶顶............

貓狗喵
467440721 发表于 2019-5-13 12:10
这样夜视效果就能更改了?

不太理解你的意思
是指更改什么东西?

Jokey_钥匙
貓狗喵 发表于 2019-5-13 22:26
不太理解你的意思
是指更改什么东西?

夜视效果的……滤镜?
让它本来变亮的效果改成变暗
(这也是着色器的一部分吧)

半度微凉.
顶~很有帮助

貓狗喵
467440721 发表于 2019-5-14 06:32
夜视效果的……滤镜?
让它本来变亮的效果改成变暗
(这也是着色器的一部分吧) ...

夜视貌似不是著色器
感觉只是调整你画面里周围的亮度

1723624171
这个研究很不错,谢谢

王泽大神
我感觉我的世界创造出立体材质很困难,所以支持一下作者

zfydxhjq
不懂c语言,但看了lz还是学到很多

2271091929
666666666666666666666

xiaohan_xixi
加油加油~

NBone
        MCBBS有你更精彩

NBone
        MCBBS有你更精彩

12345sssssa
111111111111

yuri1007
What?What this!

chyx
请问能获取玩家的坐标或者面朝的方向吗?

貓狗喵
chyx 发表于 2019-5-25 02:38
请问能获取玩家的坐标或者面朝的方向吗?

这个我不确定,不过看onnowhere的twitter说法应该是可以

chyx
貓狗喵 发表于 2019-5-25 12:51
这个我不确定,不过看onnowhere的twitter说法应该是可以

对应的变量是什么呢?

貓狗喵
chyx 发表于 2019-5-25 15:39
对应的变量是什么呢?

这我就不清楚了,他的twitter从来不透漏详细作法

是暮光哈~
好像很复杂,个人认为

ee12341
大佬.............

Jokey_钥匙
问俩问题:
1、如果使用你的dio材质包,原来的生物发光还能用吗?
2、我想在我的地图中使用持续的滤镜(整场地图无需切换滤镜),我目前想到的办法是颜色检测直接检测从黑色到白色,然后持续修改,但是有没有更好的办法?

貓狗喵
467440721 发表于 2019-5-29 08:33
问俩问题:
1、如果使用你的dio材质包,原来的生物发光还能用吗?
2、我想在我的地图中使用持续的滤镜(整 ...

我的DIO那个会盖掉发光效果,虽然就架构来看我觉得可以保留发光效果,但我没有试出来怎么做
如果只有一种滤镜而且不会取消的话,不用做任何侦测只要保持发光实体在画面中就行

htskr
6666666666666666666

无归
感觉好多,慢慢看

704287913
mcbbs有你更精彩

lovethx
...................

cznbqw
太复杂了 ,膜拜一个

501595504
神乎其技,不服不行

桐乃源玖
小小的优化带来了很好的体验 赞一个

woc159357

虽然不懂c语言,但看了lz的教程还是学到了很多东西
mc能做到越来越开放也是很让人激动的呢....

苍白的豆子
6666666666666

雾帆梵
我的天啊,显卡爆炸

x_sonner
mcbbs有你更精彩

shenyidada
感谢分享,辛苦了

GVORI
学废了................

GVORI
学废了.......

cmzzsy3433
感谢大佬的分享

第一页 上一页 下一页 最后一页