【问题标题】:Efficient Bicubic filtering code in GLSL?GLSL中的高效双三次过滤代码?
【发布时间】:2012-11-10 03:58:41
【问题描述】:

我想知道是否有人拥有完整、有效且有效的代码来在 glsl 中进行双三次纹理过滤。有这个:

http://www.codeproject.com/Articles/236394/Bi-Cubic-and-Bi-Linear-Interpolation-with-GLSL 要么 https://github.com/visionworkbench/visionworkbench/blob/master/src/vw/GPU/Shaders/Interp/interpolation-bicubic.glsl

但两者都进行了 16 次纹理读取,而只需要 4 次:

https://groups.google.com/forum/#!topic/comp.graphics.api.opengl/kqrujgJfTxo

但是,上面的方法使用了一个我不知道它应该做什么的缺失的“cubic()”函数,并且还采用了一个无法解释的“texscale”参数。

还有NVidia版本:

https://developer.nvidia.com/gpugems/gpugems2/part-iii-high-quality-rendering/chapter-20-fast-third-order-texture-filtering

但我相信这使用了 CUDA,它是 NVidia 显卡特有的。我需要 glsl。

我可能可以将 nvidia 版本移植到 glsl,但我想我会先询问是否有人已经拥有完整的、工作的 glsl 双三次着色器。

【问题讨论】:

  • "但两者都进行了 16 次纹理读取,而只有 4 次是必需的:" 那篇文章是在骗你。双三次插值进行 4 次双线性采样;这只是更大规模的线性过滤。双三次插值需要对值进行 cubic 插值,而不是线性插值。而且你不能通过做一堆线性插值来做三次插值。这就像贝塞尔曲线和通过连接 4 个贝塞尔点创建的线之间的差异。这不完全一样,是吗?
  • 我发布的着色器做了类似 texcoord =cubic(lerp(texcoord)) 的操作,因此它基本上在纹理坐标插值之上应用了额外的功能。这种类型的过滤可用于调整图像大小。
  • @NicolBolas:你错了;正如GPUGems 2 chapter 中所述以及下面由 JAre 和 Maf 编写的,完全可以使用 4 个线性插值执行双三次查找。
  • @NicolBolas 你不能通过做一堆线性插值来做三次插值。这在技术上是不正确的,这就是 De Casteljau 算法的工作原理
  • GPUGems 文章不是 CUDA。它是 Cg 或 HLSL。

标签: glsl fragment-shader bicubic


【解决方案1】:

(编辑)

  • Texscale 是采样窗口大小系数。您可以从 1.0 值开始。

vec4 filter(sampler2D texture, vec2 texcoord, vec2 texscale)
{
    float fx = fract(texcoord.x);
    float fy = fract(texcoord.y);
    texcoord.x -= fx;
    texcoord.y -= fy;

    vec4 xcubic = cubic(fx);
    vec4 ycubic = cubic(fy);

    vec4 c = vec4(texcoord.x - 0.5, texcoord.x + 1.5, texcoord.y -
0.5, texcoord.y + 1.5);
    vec4 s = vec4(xcubic.x + xcubic.y, xcubic.z + xcubic.w, ycubic.x +
ycubic.y, ycubic.z + ycubic.w);
    vec4 offset = c + vec4(xcubic.y, xcubic.w, ycubic.y, ycubic.w) /
s;

    vec4 sample0 = texture2D(texture, vec2(offset.x, offset.z) *
texscale);
    vec4 sample1 = texture2D(texture, vec2(offset.y, offset.z) *
texscale);
    vec4 sample2 = texture2D(texture, vec2(offset.x, offset.w) *
texscale);
    vec4 sample3 = texture2D(texture, vec2(offset.y, offset.w) *
texscale);

    float sx = s.x / (s.x + s.y);
    float sy = s.z / (s.z + s.w);

    return mix(
        mix(sample3, sample2, sx),
        mix(sample1, sample0, sx), sy);
}

Source

【讨论】:

  • 这与我在原始帖子中提供的链接完全相同(请参阅第 3 个链接)。这会很好......如果你能解释我需要为缺少的cubic()函数写什么,以及在texscale参数中传递什么。如果您能解释清楚,我很乐意将您的答案标记为最终答案。
  • 但是,我不确定如何用 C 语言编写一个三次函数,给出一个输入参数。我在谷歌搜索时找到的所有示例都需要许多输入参数。
  • @VernJensen 它很可能是通用三次样条曲线(如图片所示)+ 您可以调整比例。在高斯模糊着色器中,查找矩阵通常也是通用常量值。
  • 请注意,利用线性硬件插值仅适用于非负样条曲线(例如 Codeproject 示例中的 Catmull-Rom 样条曲线除外),因为您无法以偏向纹理像素的方式调整纹理坐标低于零。
  • 这里的'fract'的目的是什么?
【解决方案2】:

JAre's answer 中缺少的函数 cubic() 可能如下所示:

vec4 cubic(float x)
{
    float x2 = x * x;
    float x3 = x2 * x;
    vec4 w;
    w.x =   -x3 + 3*x2 - 3*x + 1;
    w.y =  3*x3 - 6*x2       + 4;
    w.z = -3*x3 + 3*x2 + 3*x + 1;
    w.w =  x3;
    return w / 6.f;
}

它返回三次 B 样条的四个权重。

NVidia Gems里都有解释。

【讨论】:

    【解决方案3】:

    对于任何对 GLSL 代码进行 tri-cubic 插值感兴趣的人,可以在 examples/glCubicRayCast 文件夹中找到使用三次插值的光线投射代码: http://www.dannyruijters.nl/cubicinterpolation/CI.zip

    编辑:三次插值代码现已在 github 上提供:CUDA 版本和 WebGL 版本,以及 GLSL 示例。

    【讨论】:

      【解决方案4】:

      哇。我在 2011 年初提出了上面的代码(我无法评论声誉

      我是这样想的:好的,所以如果我用线性过滤器处理每个四边形(2x2),剩下的问题是行和列可以共享权重吗?这是我着手编写代码时唯一想到的问题。当然可以共享它们;每列和每行的权重相同;完美!

      现在我有四个样本。剩下的问题是如何正确组合样本。这是要克服的最大障碍。用铅笔和纸花了大约 10 分钟。我用颤抖的手输入了代码,它工作了,很好。然后我把二进制文件上传给那个答应在他的 T42 上检查它的人(?),他报告说它有效。结束。 :)

      我可以确保方程式检查并给出数学上相同的结果来单独计算样本。仅供参考:使用 CPU,分别进行水平和垂直扫描会更快。使用 GPU 多次传递并不是什么好主意,尤其是在典型用例中它可能无论如何都不可行时。

      深思熟虑:可以为cubic() 函数使用纹理查找。哪个更快取决于 GPU,但一般来说,ALU 端的采样器很轻,只是做算术会平衡事情。 YMMV。

      【讨论】:

        【解决方案5】:

        我决定花一点时间挖掘我以前的 Perforce 活动,并找到了缺失的 cube() 函数;请享用! :)

        vec4 cubic(float v)
        {
            vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v;
            vec4 s = n * n * n;
            float x = s.x;
            float y = s.y - 4.0 * s.x;
            float z = s.z - 4.0 * s.y + 6.0 * s.x;
            float w = 6.0 - x - y - z;
            return vec4(x, y, z, w);
        }
        

        【讨论】:

          【解决方案6】:

          我已经使用 @Maf 的三次样条配方一年多了,如果三次 B 样条满足您的需求,我推荐它。

          但我最近意识到,对于我的特定应用,强度在采样点处完全匹配非常重要。所以我改用了 Catmull-Rom 样条,它使用了稍微不同的配方,如下所示:

          // Catmull-Rom spline actually passes through control points
          vec4 cubic(float x) // cubic_catmullrom(float x)
          {
              const float s = 0.5; // potentially adjustable parameter
              float x2 = x * x;
              float x3 = x2 * x;
              vec4 w;
              w.x =    -s*x3 +     2*s*x2 - s*x + 0;
              w.y = (2-s)*x3 +   (s-3)*x2       + 1;
              w.z = (s-2)*x3 + (3-2*s)*x2 + s*x + 0;
              w.w =     s*x3 -       s*x2       + 0;
              return w;
          }
          

          我在以下的讲义中找到了这些系数,以及其他一些三次样条曲线的系数: http://www.cs.cmu.edu/afs/cs/academic/class/15462-s10/www/lec-slides/lec06.pdf

          【讨论】:

          • 有趣且有用的线程。不幸的是,在更高级别的 textureBicubic 中将纹理查找次数从 16 减少到 4 的技巧依赖于以下事实:cubic 返回的所有权重都是非负的。 textureBicubic和cubic_catmullrom的组合几乎相当于线性插值。
          • 被“仅编辑 5 分钟规则”捕获....有趣且有用的线程。对旧线程的评论很晚......不幸的是,更高级别的 textureBicubic 中将纹理查找次数从 16 减少到 4 的技巧依赖于以下事实:cubic() 返回的所有权重都是非负的。对于cubic_catmullrom 并非如此,对于通过点的替代平滑插值也不会如此。这可以防止 textureBicubic 和cubic_catmullrom 的组合正常工作;它退化为(几乎?)相当于线性插值。
          • @StephenTodd 你是对的。当我写这个答案时,我不明白,我现在没有时间正确更新这个答案。如果我没记错的话,Catmull-Rom 可以使用 9 次线性查找而不是 16 次来完成,但这种方法涉及的修改不仅仅是这个cubic() 函数。
          【解决方案7】:

          我发现这个实现可以用作 texture() 的替代品(来自 http://www.java-gaming.org/index.php?topic=35123.0(一个错字已修复)):

          // from http://www.java-gaming.org/index.php?topic=35123.0
          vec4 cubic(float v){
              vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v;
              vec4 s = n * n * n;
              float x = s.x;
              float y = s.y - 4.0 * s.x;
              float z = s.z - 4.0 * s.y + 6.0 * s.x;
              float w = 6.0 - x - y - z;
              return vec4(x, y, z, w) * (1.0/6.0);
          }
          
          vec4 textureBicubic(sampler2D sampler, vec2 texCoords){
          
             vec2 texSize = textureSize(sampler, 0);
             vec2 invTexSize = 1.0 / texSize;
          
             texCoords = texCoords * texSize - 0.5;
          
          
              vec2 fxy = fract(texCoords);
              texCoords -= fxy;
          
              vec4 xcubic = cubic(fxy.x);
              vec4 ycubic = cubic(fxy.y);
          
              vec4 c = texCoords.xxyy + vec2 (-0.5, +1.5).xyxy;
          
              vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
              vec4 offset = c + vec4 (xcubic.yw, ycubic.yw) / s;
          
              offset *= invTexSize.xxyy;
          
              vec4 sample0 = texture(sampler, offset.xz);
              vec4 sample1 = texture(sampler, offset.yz);
              vec4 sample2 = texture(sampler, offset.xw);
              vec4 sample3 = texture(sampler, offset.yw);
          
              float sx = s.x / (s.x + s.y);
              float sy = s.z / (s.z + s.w);
          
              return mix(
                 mix(sample3, sample2, sx), mix(sample1, sample0, sx)
              , sy);
          }
          

          示例:最近、双线性、双三次:

          这张图片的ImageData是

          {{{0.698039, 0.996078, 0.262745}, {0., 0.266667, 1.}, {0.00392157, 
             0.25098, 0.996078}, {1., 0.65098, 0.}}, {{0.996078, 0.823529, 
             0.}, {0.498039, 0., 0.00392157}, {0.831373, 0.00392157, 
             0.00392157}, {0.956863, 0.972549, 0.00784314}}, {{0.909804, 
             0.00784314, 0.}, {0.87451, 0.996078, 0.0862745}, {0.196078, 
             0.992157, 0.760784}, {0.00392157, 0.00392157, 0.498039}}, {{1., 
             0.878431, 0.}, {0.588235, 0.00392157, 0.00392157}, {0.00392157, 
             0.0666667, 0.996078}, {0.996078, 0.517647, 0.}}}
          

          我试图重现这一点(许多其他插值技术)

          但他们有固定的填充,而我有重复(包装)边界。因此并不完全相同。

          似乎这种双三次业务不是正确的插值,即它不会在定义数据的点处采用原始值。

          【讨论】:

          • 与 Maf 和 JAre 的答案中提供的两个函数的组合相比,我发现这个算法是无伪影的。如果您想亲眼看看,最简单的方法是转到此着色器shadertoy.com/view/XtKfRV 并将 BicubicTexture() 和cubic() 函数换成本文中的函数。前者产生了相当明显的块状伪影,而这个非常平滑。谢谢,masterxilo! +1
          • 确实,这不是插值,它看起来更像是对线性插值图像施加的一种模糊。
          【解决方案8】:

          我认为 Catmull 版本有可能通过 4 次纹理查找来完成,方法是 (a) 将输入纹理排列为像棋盘一样,交替槽保存为正数和负数,以及 (b) 对 textureBicubic 进行相关修改。这将取决于贡献/权重 w.x/w.w 始终为负,而贡献 w.y/w.z 始终为正。我没有仔细检查这是否属实,或者修改后的 textureBicubic 的外观。

          ...我已经验证 w 贡献确实满足 +ve -ve 规则。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-07-09
            • 2023-03-27
            • 2011-06-19
            • 2010-09-23
            • 1970-01-01
            • 2022-06-23
            相关资源
            最近更新 更多