【GLSL】给 Minecraft 编写 Font Shader!使用 GB2312 字符集像素字,渲染游戏画面
# 给 Minecraft 编写 Font Shader!使用 GB2312 字符集像素字,渲染游戏画面## 仓库地址
(https://github.com/MickeyMeowMeowHouse/font-shader)
(https://gitee.com/a5k3rn3l/font-shader)
两个仓库同步维护,如果你可以访问 GitHub 的话请访问 GitHub 下载;如果你不可以访问 GitHub 的话可以从 Gitee 下载。
## 想法来由
这个项目其实最初是在我的 (https://www.0xaa55.com/thread-26582-1-1.html)之前诞生。我当时设想的是:假设我想要直接在服务器的终端界面上,不使用 X Server 转发的方式(因为卡顿和延迟严重),运行游戏 **客户端**,登录管理员客户端账号来 **视察** 服务器内玩家的游玩情况(或者给玩家帮忙,针对作弊玩家采集证据并依规干掉作弊玩家等)的话,有没有办法直接在服务器后台的 **终端界面** 上显示游戏的画面呢?
既然是终端界面,那肯定只能使用纯文本了。但是文字它也是有形状的呀,而且 SSH 终端是支持彩色文字的。既然如此,那似乎可以用文字来拟合图像的画面,并设置文字的颜色。每个文字的字体看着都像个 **卷积核**,用这个卷积核去匹配画面,然后筛选出最匹配的文字,那不就能……大致 **整** 出个画面了么?
一开始我是这样想的,但是其实有个问题就是服务器其实没有 GPU,因此也没有能够达到跑 Shader 的环境,说不定用 CPU Rasterize 到 XVFB 渲染画面的效率极低(毕竟 Minecraft 游戏的场景里的三角面数量肯定是上万上十万不止的了,还有分辨率要求),这个项目并不能落地。但是我可以先试试呀,用客户端跑 Shader 看看效果。我当时非常好奇如果使用汉字去拟合游戏的画面图像,会怎么样。
## 效果图



甚至还是非常有辨识度的(尤其是动态画面的情况下),只是无法在远距离的情况下区分僵尸和草地、树叶。

## 开发过程
使用 Sublime-text 开发,按照 (https://github.com/sp614x/optifine/blob/master/OptiFineDoc/doc/shaders.txt)来开发。
在整个渲染流程中,我们不需要针对地形、实体、实体方块、云、静态光源、动态光源等进行区分渲染。只需要使用默认的渲染流程,然后在最终合成的时候,进行字体的匹配。因此只需要实现 `composite` 的流程就可以了。因为我需要多个 Pass 来完成渲染,所以我实现了 `composite` 和 `composite1`、`final` 的 `vsh` 和 `fsh`。
为了避免特殊符号的干扰(特殊符号容易导致过拟合,在卷积计算中的分数过高,会淘汰掉其它的字体),在字体的采用上只采用纯 GB2312 汉字部分。
**GB2312 有 6762 个汉字** ,而如果在 Fragment Shader 里,采用了 *不太对* 的渲染策略(比如,使用一个分辨率为横竖字符个数的 Render Buffer,再对每个像素扩大采样到原图,遍历 6762 个汉字)的话,就无法把 GPU 的并行性优势充分发挥出来,从而导致严重的卡顿。
我的做法是将上述大小的 Render Buffer 再横竖扩展 X 倍,然后分区域分别匹配不同范围的汉字,最终再折叠这些区域,取分数最高的汉字用于显示。这样既可以遍历所有的汉字,又可以充分发挥 GPU 的并行计算优势。
首先需要设置配置文件,让 Optifine 的渲染引擎为我们加载字体文件(采用旧光照方式以简化开发,另外就是我讨厌 MC 里面的云,所以我关闭了云)
### 修改 `shader.properties`
```
clouds = off
oldLighting = true
texture.composite.gaux1=textures/gb2312.png
```
### 顶点着色器
顶点着色器(Vertex Shader)的部分没什么好说的。Optifine 提供了太多的常量接口,根本不需要传递什么信息给 Fragment Shader,只需计算好顶点投影坐标即可:
```
#version 130
out vec2 texCoord;
void main()
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
texCoord = gl_MultiTexCoord0.xy;
}
```
### 片段(像素)着色器
片段(像素)着色器(Fragment Shader)的部分就需要仔细设计了。
Pass1:**composite.fsh** 用于预处理画面,写出三个渲染目标,分别对应颜色、灰度、字体纹理采样。
```
#version 130
uniform sampler2D colortex0;
uniform sampler2D colortex4;
/* RENDERTARGETS: 0, 1, 2 */
uniform float viewWidth;
uniform float viewHeight;
in vec2 texCoord;
vec2 Resolution = vec2(viewWidth, viewHeight);
vec2 fragCoord = texCoord * Resolution;
void main()
{
vec4 color = texture(colortex0, texCoord);
float gray = pow(length(color.rgb), 0.8) * 0.7 + 0.3;
gl_FragData = color;
gl_FragData = vec4(gray);
gl_FragData = vec4(texelFetch(colortex4, ivec2(fragCoord), 0).rgb, 1.0);
}
```
Pass2:**composite1.fsh** 用于字符匹配。根据自己的像素位置,判断自己是要给哪个格子干活,要计算什么样的字符范围的卷积,以及分数存储到哪里。这块的代码很长,我就不粘出来了,有条件的朋友们请自行查阅 Repo。
Pass3:**final.fsh** 用于折叠上一个 Pass 生成的结果,合并所有的匹配结果取分数最高,然后从字体库里采样,显示字体。
## 用户指导:如何安装着色器
确保已安装 optifine,然后打开 Minecraft 根目录(通常位于`C:\Users\你的用户名\AppData\Roaming.minecraft`)。进入`shaderpacks`文件夹(如果没有,则需要手动创建),新建名为`font-shader-1.0.0`的子文件夹,将`shaders`文件夹及`LICENSE`文件拷贝进去。请务必阅读`LICENSE`文件。
## Optifine 安装说明
需先安装(https://www.java.com/)。 前往(https://optifine.net/home)下载对应的《我的世界》的 optifine 版本。
安装Java后,双击`optifine.jar`文件即可完成安装。
页:
[1]