把 ASCII 着色器提速 169 倍
首页的字符矩阵最初每个像素都要算一遍噪声。换成两遍渲染——先按字符格算,再按像素查表——计算量降到原来的 1/169。
首页背景是一片会流动的字符:噪声越亮的地方,字符越密,从 . 一路变到 @。第一版能跑,但在软件渲染下只有 23 fps。
问题出在哪
第一版把所有事情放在一个片元着色器里:每个像素先算出自己属于哪个字符格,再用格子中心的坐标算一遍域扭曲的 fbm 噪声,最后去字符图集里取对应的字形。
问题是,一个 13×13 像素的字符格里,169 个像素算的是同一个噪声值。最贵的那部分计算被白白重复了 169 次。
两遍渲染
解决办法是把“算噪声”和“画字符”拆开:
- 第一遍渲染到一张很小的离屏纹理,尺寸就是字符格的行列数(桌面上大约 111×70)。每个格子只算一次噪声,结果写进红色通道;绿色通道记录这一格是否要用强调色。
- 第二遍按屏幕像素渲染,每个像素只做两次纹理查找:先查这一格的亮度,再去图集里取字形。
vec2 cell = floor(gl_FragCoord.xy / uCell);
vec2 local = fract(gl_FragCoord.xy / uCell);
vec4 f = texture2D(uField, (cell + 0.5) / uGrid);
float idx = floor(f.r * (uGlyphs - 1.0) + 0.5);
float glyph = texture2D(uAtlas, vec2((idx + local.x) / uGlyphs, 1.0 - local.y)).a;
离屏纹理用 NEAREST 采样,保证一个格子里所有像素读到完全相同的值。WebGL 1 允许非 2 的幂尺寸的纹理,只要不用 mipmap、边缘用 CLAMP_TO_EDGE。
改完之后,同样的软件渲染环境跑到了 60 fps。
竖屏的小坑
手机上字符场一开始又暗又稀。原因是噪声坐标按画面高度归一化:竖屏时宽度只占高度的一半不到,等于只截取了噪声场里很窄的一条。改成按短边归一化之后,竖屏也能看到完整的明暗变化。
字符图集
字形不是字体文件里直接取的,而是运行时用 Canvas 把 .,:;~=+*x%#@ 这 13 个字符画成一张横排的图,再上传成纹理。画之前要先等等宽字体加载完,否则图集里会是回退字体。