|  | 
 
 
 楼主|
发表于 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 转换这里很明显多算了很多次
 | 
 |