【问题标题】:Understanding image dithering and how they help blending CSM了解图像抖动以及它们如何帮助混合 CSM
【发布时间】:2020-04-15 17:35:57
【问题描述】:

所以我希望在我的级联阴影贴图分割之间实现抖动作为混合模式。

我不知道它们是什么,所以我看了这个video 来尝试理解它。
据我了解,这是一种在尝试维护的同时将图像颜色映射到有限托盘的方法不同颜色像素之间令人信服的渐变。

现在从这个视频中,我了解了如何根据抖动模式的权重计算我的眼睛会看到什么颜色。我不明白的是我们如何拍摄具有 4 字节像素数据的图像,例如尝试将其映射到 1 字节像素数据。如果我们基本上受到限制,我们如何将原始图像中的每个像素颜色映射到其加权平均看起来就像是原始颜色的抖动模式?假设我们仅限于 5 种颜色,我猜测并非使用这 5 种托盘颜色的抖动模式的所有可能加权平均组合都可以产生原始像素颜色,那么如何实现呢?是否也为每个像素计算了抖动模式以实现抖动图像?

除了关于图像抖动的这些一般性问题之外,我仍然难以理解这种技术如何帮助我们在级联拆分之间进行混合,就在代码中实际实现它而言,我已经看到了一个使用空间坐标的示例片段并计算抖动(不确定它实际上在计算什么,因为它不返回一个矩阵它返回一个浮点数):

float GetDither2(ivec2 p)
{
    float d = 0.0;

    if((p.x & 1) != (p.y & 1))
        d += 2.0;
    if((p.y & 1) == 1)
        d += 1.0;

    d *= 0.25;

    return d;
}

float GetDither4(ivec2 p)
{
    float d = GetDither2(p);
    d = d * 0.25 + GetDither2(p >> 1);
    return d;
}

float threshold = GetDither4(ivec2(gl_FragCoord.xy));

if(factor <= threshold)
{
    // sample current cascade
}
else
{
    // sample next cascade
}

然后它会根据返回的浮点数对任一级联图进行采样。 所以我的大脑无法将我了解到的你可以有一个抖动模式来模拟大颜色模式的知识转化为这个使用返回的浮点数作为阈值因子并将其与某个混合因子进行比较的示例,以从任一阴影贴图中进行采样。所以这让我更加困惑。

希望对此有一个很好的解释????

编辑:

好的,我看到我提供的算法与wikipedia article 关于有序抖动的相关性,据我所知,这是首选的抖动算法,因为根据文章:

另外,因为抖动模式的位置总是 相对于显示框架保持不变,不太容易发生 抖动比误差扩散方法,使其适用于 动画。

现在我看到代码尝试获取给定空间坐标的此阈值,尽管在我看来它有点错误,因为以下阈值计算如下: Mpre(i,j) = (Mint(i,j)+1) / n^2

如果我没记错的话,它需要设置:float d = 1.0 而不是 float d = 0.0。 其次,我不确定ivec2 空间坐标如何左移(我什至不确定 glsl 中向量的按位移位行为是什么……)但我假设它只是组件按位运算,我尝试了插件(头部计算)对于给定的空间坐标(2,1)(根据我对按位运算的假设)并得到不同的阈值结果,该阈值应该是 4x4 拜耳矩阵中该位置的阈值。

所以我对这段代码实现有序抖动算法的效果持怀疑态度。

其次,我仍然不确定这个阈值与在阴影贴图 1 或 2 之间进行选择有什么关系,而不仅仅是减少给定像素的颜色托盘,这个逻辑还没有在我的脑海中解决,因为我不了解如何使用给定空间坐标的抖动阈值来选择正确的地图进行采样。

最后不会选择空间坐标会导致抖动吗?给定片段在世界位置(x,y,z) 被阴影。给定帧的片段空间坐标是(i,j)。如果相机移动,这个片段空间坐标是否会发生变化,使得为这个片段计算的抖动阈值随着每次移动而变化,从而导致抖动模式的抖动?

EDIT2: 尝试将地图混合如下,尽管结果看起来不太好,有什么想法吗?

const int indexMatrix8x8[64] = int[](
    0, 32, 8, 40, 2, 34, 10, 42,
    48, 16, 56, 24, 50, 18, 58, 26,
    12, 44, 4, 36, 14, 46, 6, 38,
    60, 28, 52, 20, 62, 30, 54, 22,
    3, 35, 11, 43, 1, 33, 9, 41,
    51, 19, 59, 27, 49, 17, 57, 25,
    15, 47, 7, 39, 13, 45, 5, 37,
    63, 31, 55, 23, 61, 29, 53, 21
);

for (int i = 0; i < NR_LIGHT_SPACE; i++) {
        if (fs_in.v_FragPosClipSpaceZ <= u_CascadeEndClipSpace[i]) {
            shadow = isInShadow(fs_in.v_FragPosLightSpace[i], normal, lightDirection, i) * u_ShadowStrength;
                int x = int(mod(gl_FragCoord.x, 8));
                int y = int(mod(gl_FragCoord.y, 8));
                float threshold = (indexMatrix8x8[(x + y * 8)] + 1) / 64.0;
                if (u_CascadeBlend >= threshold)
                {
                    shadow = isInShadow(fs_in.v_FragPosLightSpace[i + 1], normal, lightDirection, i + 1) * u_ShadowStrength;
                }
            }
            break;
        }
    }

基本上,如果我知道我正在做的是从矩阵中获取阴影像素的每个空间坐标的阈值,并且如果它(使用概率)高于混合因子,而不是我对第二张地图进行采样。

结果如下: 较大的红色框是地图之间发生分割的地方。
较小的红色框表示存在一些抖动模式,但图像并没有像我认为的那样混合。

【问题讨论】:

    标签: opengl graphics glsl shadow-mapping dithering


    【解决方案1】:

    首先,我对 CSM 一无所知,所以我专注于抖动和混合。先看看这些:

    他们基本上回答了你关于如何计算抖动模式/像素的问题。

    拥有良好的抖动调色板也很重要,可以将 24/32 bpp 降低到 8 bpp(或更少)。有两种基本方法

    1. 减少颜色(颜色量化)

      因此计算原始图像的直方图并从中选择或多或少覆盖整个图像信息的重要颜色。欲了解更多信息,请参阅:

    2. 抖动调色板

      抖动使用像素的平均来生成所需的颜色,因此我们需要能够生成我们想要的所有可能颜色的颜色。因此,每种基色(R,G,B,C,M,Y)和一些(> = 4)灰色阴影的阴影很少(2..4)很好。从这些你可以组合任何你想要的颜色和强度(如果你有足够的像素)

    #1 是最好的,但它与每个图像相关,因此您需要为每个图像计算调色板。这可能是个问题,因为计算是令人讨厌的 CPU 消耗量。此外,在旧的 256 色模式下,您无法同时显示 2 个不同的调色板(真彩色不再是问题),因此抖动通常是更好的选择。

    您甚至可以将两者结合起来以获得令人印象深刻的结果。

    使用的调色板越好,结果颗粒越少......

    标准 VGA 16 和 256 调色板专为抖动而设计,因此使用它们是个好主意...

    标准 VGA 16 调色板:

    标准 VGA 256 调色板:

    这里还有 256 种颜色的 C++ 代码:

    //---------------------------------------------------------------------------
    //--- EGA VGA pallete -------------------------------------------------------
    //---------------------------------------------------------------------------
    #ifndef _vgapal_h
    #define _vgapal_h
    //---------------------------------------------------------------------------
    unsigned int vgapal[256]=
        {
        0x00000000,0x00220000,0x00002200,0x00222200,
        0x00000022,0x00220022,0x00001522,0x00222222,
        0x00151515,0x00371515,0x00153715,0x00373715,
        0x00151537,0x00371537,0x00153737,0x00373737,
        0x00000000,0x00050505,0x00000000,0x00030303,
        0x00060606,0x00111111,0x00141414,0x00101010,
        0x00141414,0x00202020,0x00242424,0x00202020,
        0x00252525,0x00323232,0x00303030,0x00373737,
        0x00370000,0x00370010,0x00370017,0x00370027,
        0x00370037,0x00270037,0x00170037,0x00100037,
        0x00000037,0x00001037,0x00001737,0x00002737,
        0x00003737,0x00003727,0x00003717,0x00003710,
        0x00003700,0x00103700,0x00173700,0x00273700,
        0x00373700,0x00372700,0x00371700,0x00371000,
        0x00371717,0x00371727,0x00371727,0x00371737,
        0x00371737,0x00371737,0x00271737,0x00271737,
        0x00171737,0x00172737,0x00172737,0x00173737,
        0x00173737,0x00173737,0x00173727,0x00173727,
        0x00173717,0x00273717,0x00273717,0x00373717,
        0x00373717,0x00373717,0x00372717,0x00372717,
        0x00372525,0x00372531,0x00372536,0x00372532,
        0x00372537,0x00322537,0x00362537,0x00312537,
        0x00252537,0x00253137,0x00253637,0x00253237,
        0x00253737,0x00253732,0x00253736,0x00253731,
        0x00253725,0x00313725,0x00363725,0x00323725,
        0x00373725,0x00373225,0x00373625,0x00373125,
        0x00140000,0x00140007,0x00140006,0x00140015,
        0x00140014,0x00150014,0x00060014,0x00070014,
        0x00000014,0x00000714,0x00000614,0x00001514,
        0x00001414,0x00001415,0x00001406,0x00001407,
        0x00001400,0x00071400,0x00061400,0x00151400,
        0x00141400,0x00141500,0x00140600,0x00140700,
        0x00140606,0x00140611,0x00140615,0x00140610,
        0x00140614,0x00100614,0x00150614,0x00110614,
        0x00060614,0x00061114,0x00061514,0x00061014,
        0x00061414,0x00061410,0x00061415,0x00061411,
        0x00061406,0x00111406,0x00151406,0x00101406,
        0x00141406,0x00141006,0x00141506,0x00141106,
        0x00141414,0x00141416,0x00141410,0x00141412,
        0x00141414,0x00121414,0x00101414,0x00161414,
        0x00141414,0x00141614,0x00141014,0x00141214,
        0x00141414,0x00141412,0x00141410,0x00141416,
        0x00141414,0x00161414,0x00101414,0x00121414,
        0x00141414,0x00141214,0x00141014,0x00141614,
        0x00100000,0x00100004,0x00100000,0x00100004,
        0x00100010,0x00040010,0x00000010,0x00040010,
        0x00000010,0x00000410,0x00000010,0x00000410,
        0x00001010,0x00001004,0x00001000,0x00001004,
        0x00001000,0x00041000,0x00001000,0x00041000,
        0x00101000,0x00100400,0x00100000,0x00100400,
        0x00100000,0x00100002,0x00100004,0x00100006,
        0x00100010,0x00060010,0x00040010,0x00020010,
        0x00000010,0x00000210,0x00000410,0x00000610,
        0x00001010,0x00001006,0x00001004,0x00001002,
        0x00001000,0x00021000,0x00041000,0x00061000,
        0x00101000,0x00100600,0x00100400,0x00100200,
        0x00100303,0x00100304,0x00100305,0x00100307,
        0x00100310,0x00070310,0x00050310,0x00040310,
        0x00030310,0x00030410,0x00030510,0x00030710,
        0x00031010,0x00031007,0x00031005,0x00031004,
        0x00031003,0x00041003,0x00051003,0x00071003,
        0x00101003,0x00100703,0x00100503,0x00100403,
        0x00000000,0x00000000,0x00000000,0x00000000,
        0x00000000,0x00000000,0x00000000,0x00000000,
        };
    //---------------------------------------------------------------------------
    class _vgapal_init_class
            {
    public: _vgapal_init_class();
            } vgapal_init_class;
    //---------------------------------------------------------------------------
    _vgapal_init_class::_vgapal_init_class()
            {
            int i;
            BYTE a;
            union { unsigned int dd; BYTE db[4]; } c;
            for (i=0;i<256;i++)
                {
                c.dd=vgapal[i];
                c.dd=c.dd<<2;
                a      =c.db[0];
                c.db[0]=c.db[2];
                c.db[2]=      a;
                vgapal[i]=c.dd;
                }
            }
    //---------------------------------------------------------------------------
    #endif
    //---------------------------------------------------------------------------
    //--- end. ------------------------------------------------------------------
    //---------------------------------------------------------------------------
    

    现在回到关于抖动混合的问题

    混合是将 2 个相同分辨率的图像按一定数量(权重)合并在一起。所以每个像素的颜色是这样计算的:

    color = w0*color0 + w1*color1;
    

    其中color? 是源图像中的像素,w? 是所有权重加起来等于 1 的权重:

    w0 + w1 = 1;
    

    这里的例子:

    和预览(点在我的 GIF 编码器中抖动):

    但是通过抖动进行混合的方式有所不同。我们不是使用混合颜色,而是使用一张图像的一定百分比的像素和第二张图像的其他像素。所以:

    if (Random()<w0) color = color0;
     else            color = color1;
    

    其中Random() 返回&lt;0,1&gt; 范围内的伪随机数。正如您所看到的,您只需从复制像素的图像中选择没有完成颜色组合...这里预览:

    现在这些点是由抖动混合引起的,因为图像的强度彼此相距很远,所以看起来不太好,但是如果您抖动相对相似的图像(如阴影贴图图层),结果应该是足够好(几乎没有性能损失)。

    为了加快速度,通常会预先计算某些框(8x8、16x16、...)的 Random() 输出并将其用于整个图像(它有点块状,但这是一种有趣的效果。 ..)。这样它也可以无分支地完成(如果您存储指向源图像的指针而不是随机值)。如果权重是整数,例如&lt;0..255&gt;...

    现在从image0image1 进行级联/转换,或者只是简单地做这样的事情:

    for (w0=1.0;w0>=0.0;w0-=0.05)
     {
     w1=1.0-w0;
     render blended images;
     Sleep(100);
     }
    render image1;
    

    【讨论】:

    • its a bit blocky but that is sort of used as a fun effect ... 这种模式不只是一种“有趣的效果”吗?据我了解,如果我们要将灰度抖动为黑色或白色,那么如果我们以任何其他方式对拜耳矩阵进行排序,那么如果没有这些独特的图案,我们的眼睛将无法从远处感知到它们之间差异最大的连续数字那是眼睛总结和平均得到原始颜色不是吗?还尝试使用拜耳 8x8 矩阵进行混合,但结果看起来不太乐观,我将编辑操作。
    • @Jorayen 图案的形状或多或少与人眼的颜色感知无关(随机点主要用于避免对齐和/或干涉图案,这些图案会立即被感知,您知道您只发现了一些颗粒状的点如果你寻找它们)。重要的是,如果您有 8x8 框,您将获得 64 个像素,这会将您的 Alpha 通道限制为 64 个可能的阴影/级别。如果你想要更精确的 alpha,你需要更大的盒子。我写的有趣的效果更多是关于幻灯片、网站、游戏等图像之间的过渡......
    • @Jorayen 缩放图像后,它会清除您的矩阵和/或抖动不是随机的,并且会发生干扰图案,我们将其视为纹理属性......不确定 GLSL 是否有任何可用的 PRNG 人们想要伪造它来自片段和/或纹理坐标,但我可能会使用 CPU 端预先计算的 white noise texture 来代替......如果没有 PRNG 可供使用
    • @Jorayen 但是,您为什么不通过标准混合将地图混合在一起?这比抖动方法对 GLSL 更友好,因为您在 GLSL 中没有可能的片段静态变量(至少据我所知)......似乎没有......
    • @Jorayen 您正在使用权重,您只是看不到它...u_CascadeBlend 是概率,也是权重(相同的东西)...threshold 是您的随机数按权重缩放的值...从片段位置(作为 PRNG 种子)和一组数学(mod)生成,这些数学创建随机整数,您可以使用这些整数来查看各个源图像之间切换的预定义概率分布(indexMatrix8x8 ) ...至少我是这么看的...这只是 GLSL 中缺少 PRNG 的一种解决方法
    【解决方案2】:

    我让抖动混合在我的代码中工作,如下所示:

    for (int i = 0; i < NR_LIGHT_SPACE; i++) {
            if (fs_in.v_FragPosClipSpaceZ <= u_CascadeEndClipSpace[i])
            {
                float fade = fadedShadowStrength(fs_in.v_FragPosClipSpaceZ, 1.0 / u_CascadeEndClipSpace[i], 1.0 / u_CascadeBlend);
                if (fade < 1.0) {
                    int x = int(mod(gl_FragCoord.x, 8));
                    int y = int(mod(gl_FragCoord.y, 8));
                    float threshold = (indexMatrix8x8[(x + y * 8)] + 1) / 64.0;
                    if (fade < threshold)
                    {
                        shadow = isInShadow(fs_in.v_FragPosLightSpace[i + 1], normal, lightDirection, i + 1) * u_ShadowStrength;
                    }
                    else
                    {
                        shadow = isInShadow(fs_in.v_FragPosLightSpace[i], normal, lightDirection, i) * u_ShadowStrength;
                    }
                }
                else
                {
                    shadow = isInShadow(fs_in.v_FragPosLightSpace[i], normal, lightDirection, i) * u_ShadowStrength;
                }
                break;
            }
    }
    

    首先检查我们是否接近通过衰落因子分割的级联,考虑到片段位置剪辑空间和级联剪辑空间的末尾fadedShadowStrength(我使用此函数在级联之间进行正常混合以了解什么时候开始混合,基本上如果混合因子 u_CascadeBlend 设置为 0.1,那么当我们至少 90% 进入当前级联(z 剪辑空间)时我们混合。

    然后,如果我们需要淡入淡出 (if (fade &lt;1.0)),我只需将淡入淡出因子与矩阵中的阈值进行比较,然后相应地选择阴影贴图。 结果:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-24
      • 2017-07-21
      • 2018-12-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多