|
楼主 |
发表于 2023-3-1 19:38:13
|
显示全部楼层
本帖最后由 dasasdhba 于 2023-6-27 14:39 编辑
[25] 图像放大算法
下面我们介绍游戏常用的放大算法
再重复一遍,Fragment Shader 不能改变 Texture 本身尺寸,为了得到放大后的结果,一般采取与 Vertex Shader 协助的办法(参考 [9]),因为无论是人为留空还是提前临近处理都会带来不必要的麻烦。
1. HQnx
2003 年就被提出的比较老的算法,实现的不算很漂亮,甚至找不到什么原理解析,下面给出我的个人理解。
HQnx 用于像素风格图像的放大,其中横线和竖线的放大是容易的,关键在于对斜线放大的处理。
(1). 检测斜线
我们简单地考虑每个像素周围 3*3 的区域,不考虑镜像,斜线可分为两类:
a. 双边线
■□□
□■□
□□■
b. 单边线
■■■
□■■
□□■
若考虑镜像则有 6 类。
为了检测斜线,我们使用特定的 3*3 卷积核对灰度(参考 [23])作卷积,这需要对上述黑方格■部分的灰度求均值并与原像素灰度比较,在差距不超过预设的某个阈值时就将其判定为某种类型的斜线。
(2). 使用模板
对于每种类型的斜线,我们将其处理为预先准备好的放大模板。如上述 a 情形可以使用模板:
■■▲□□
■■■▲□
▲■■■▲
□▲■■■
□□▲■■
其中▲表示半透明混合(参考 [8])
然而,在具体实现上,主流的做法是采用 YUV 的方法获取权重,并利用 LUT 查表得到最后的 filter 值,可参考:https://github.com/CrossVR/hqx-shader/blob/master/glsl/hq2x.glsl
2. xBRZ
2011 年被提出的算法,基本思路是首先作边缘检测,然后判定每一个角落的像素是属于直角还是圆角,并基于这一点采取不同的插值达到较为合理的效果。
因此具体如何边缘检测和判定角落类型会比较复杂,好在这个算法比较热门,已经有比较好的具体解析可供参考:https://www.luogu.com.cn/blog/ud2/xbrz-interpolation-explained
3. Super SAI
众所周知的 MF 的 Texture 采用的放大算法,于 1999 年即被提出,这个东西其实挺冷门的,但考虑到圈内应用广泛,我将其从 C# 版本(来自 imageresizer,这玩意其实是开源的)转写为了 GLSL 版本(可在 The book of Shader 网站上测试),并附带了相关注释,可供参考(但其实我也看不懂):
- #ifdef GL_ES
- precision mediump float;
- #endif
- uniform sampler2D u_tex0;
- uniform vec2 u_tex0Resolution;
- uniform vec2 u_resolution;
- // 用于 rgb 转 yuv
- const mat3 yuv_matrix = mat3(
- 0.299, -0.169, 0.5,
- 0.587, -0.331, -0.419,
- 0.114, 0.5, -0.081);
- const vec3 yuv_threshold = vec3(48.0/255.0, 7.0/255.0, 6.0/255.0);
- const vec3 yuv_offset = vec3(0, 0.5, 0.5);
- // 通过 yuv 判断两个颜色的相似度
- bool like(vec4 c1, vec4 c2) {
- vec3 a = yuv_matrix * c1.rgb;
- vec3 b = yuv_matrix * c2.rgb;
- bvec3 res = greaterThan(abs((a + yuv_offset) - (b + yuv_offset)), yuv_threshold);
- return !(res.x || res.y || res.z);
- }
- // 一个莫名其妙的函数
- // a 与 c d 相似返回 -1
- // a 不与 c d 相似且 b 与 c d 相似返回 1
- // 其他情况返回 0
- float cond(vec4 a, vec4 b, vec4 c, vec4 d) {
- bool ac = like(a, c);
- bool ad = like(a, d);
- bool bc = like(b, c);
- bool bd = like(b, d);
- float x = float(ac) + float(ad);
- float y = float(bc && !ac) + float(bd && !ad);
- return float(x <= 1.0) - float(y <= 1.0);
- }
- void main () {
- vec2 uv = gl_FragCoord.xy/u_resolution.xy;
- vec2 unit = 1.0/u_tex0Resolution;
- // c0 c1 c2 d3
- // c3 c4 c5 d4
- // c6 c7 c8 d5
- // d0 d1 d2 d6
- // 其中 c4 在当前 UV 位置
- vec4 c0 = texture2D(u_tex0, uv + vec2(-1.0, -1.0)*unit);
- vec4 c1 = texture2D(u_tex0, uv + vec2( 0.0, -1.0)*unit);
- vec4 c2 = texture2D(u_tex0, uv + vec2( 1.0, -1.0)*unit);
- vec4 d3 = texture2D(u_tex0, uv + vec2( 2.0, -1.0)*unit);
- vec4 c3 = texture2D(u_tex0, uv + vec2(-1.0, 0.0)*unit);
- vec4 c4 = texture2D(u_tex0, uv + vec2( 0.0, 0.0)*unit);
- vec4 c5 = texture2D(u_tex0, uv + vec2( 1.0, 0.0)*unit);
- vec4 d4 = texture2D(u_tex0, uv + vec2( 2.0, 0.0)*unit);
- vec4 c6 = texture2D(u_tex0, uv + vec2(-1.0, 1.0)*unit);
- vec4 c7 = texture2D(u_tex0, uv + vec2( 0.0, 1.0)*unit);
- vec4 c8 = texture2D(u_tex0, uv + vec2( 1.0, 1.0)*unit);
- vec4 d5 = texture2D(u_tex0, uv + vec2( 2.0, 1.0)*unit);
- vec4 d0 = texture2D(u_tex0, uv + vec2(-1.0, 2.0)*unit);
- vec4 d1 = texture2D(u_tex0, uv + vec2( 0.0, 2.0)*unit);
- vec4 d2 = texture2D(u_tex0, uv + vec2( 1.0, 2.0)*unit);
- vec4 d6 = texture2D(u_tex0, uv + vec2( 2.0, 2.0)*unit);
- // e00 e01
- // e10 e11
- // 放大后输出的四个像素的颜色
- vec4 e00 = c4;
- vec4 e01 = c4;
- vec4 e10;
- vec4 e11 = c4;
- // 处理 e01 与 e11
- if (like(c7, c5) && !like(c4, c8)) {
- vec4 c57 = mix(c7, c5, 0.5);
- e11 = c57;
- e01 = c57;
- } else if (like(c4, c8) && !like(c7, c5)) {
- // pass
- } else if (like(c4, c8) && like(c7, c5)) {
- vec4 c57 = mix(c7, c5, 0.5);
- vec4 c48 = mix(c4, c8, 0.5);
- // 谁能告诉我它在干嘛???
- float conc = 0.0;
- conc += cond(c57, c48, c6, d1);
- conc += cond(c57, c48, c3, c1);
- conc += cond(c57, c48, d2, d5);
- conc += cond(c57, c48, c2, d4);
- if (conc > 0.0) {
- e11 = c57;
- e01 = c57;
- } else if (conc == 0.0) {
- e11 = mix(c48, c57, 0.5);
- e01 = e11;
- }
- } else {
- // 地狱绘图.jpg
- if (like(c8, c5) && like(c8, d1) && !like(c7, d2) && !like(c8, d0)) {
- e11 = mix((c8+c5+d1)/3.0, c7, 0.75);
- } else if (like(c7, c4) && like(c7, d2) && !like(c7,d6), !like(c8, d1)) {
- e11 = mix((c7+c4+d2)/3.0, c8, 0.75);
- } else {
- e11 = mix(c7, c8, 0.5);
- }
- if (like(c5, c8) && like(c5, c1) && !like(c5, c0) && !like(c4, c2)) {
- e01 = mix((c5+c8+c1)/3.0, c4, 0.75);
- } else if (like(c4, c7) && like(c4, c2) && !like(c5, c1) && !like(c4, d3)) {
- e01 = mix((c4+c7+c2)/3.0, c5, 0.75);
- } else {
- e01 = mix(c4, c5, 0.5);
- }
- }
- // 处理 e10
- if (like(c4, c8) && like(c4, c3) && !like(c7, c5) && !like(c4, d2)) {
- e10 = mix(c7, (c4+c8+c3)/3.0, 0.5);
- } else if (like(c4, c6) && like(c4, c5) && !like(c7, c3) && !like(c4 ,d0)) {
- e10 = mix(c7, (c4+c6+c5)/3.0, 0.5);
- } else {
- e10 = c7;
- }
- // 处理 e00
- if (like(c7, c5) && like(c7, c6) && !like(c4, c8) && !like(c7, c2)) {
- e00 = mix((c7+c5+c6)/3.0, c4, 0.5);
- } else if (like(c7, c3) && like(c7, c8) && !like(c4, c6) && !like(c7, c0)) {
- e00 = mix((c7+c3+c8)/3.0, c4, 0.5);
- }
- // 混和结果
- vec2 pos = floor(fract(uv * u_tex0Resolution) * 2.0);
- vec4 color = mix(
- mix(e00, e01, pos.x),
- mix(e10, e11, pos.x),
- pos.y);
- gl_FragColor = color;
- }
复制代码
注:此为源代码转写,没有优化性能,比如 YUV 转换这里很明显多算了很多次 |
|