你能改变金属着色器中采样器的边界吗?

Posted

技术标签:

【中文标题】你能改变金属着色器中采样器的边界吗?【英文标题】:Can you change the bounds of a Sampler in a Metal Shader? 【发布时间】:2019-01-21 17:44:26 【问题描述】:

在金属着色器文件的片段函数中,有没有办法重新定义纹理的“边界”,以使样本认为它的归一化坐标是什么?

默认情况下,样本的值 0,0 是左上角的“像素”,而 1,1 是纹理的右下角“像素”。但是,我正在重新使用纹理进行绘图,并且在任何给定的渲染过程中,只有一部分纹理包含相关数据。

例如,在宽度:500 和高度:500 的纹理中,我可能只将数据复制到 0,0,250,250 区域。在我的片段函数中,我希望采样器将 1.0 的归一化坐标解释为 250 而不是 500。这可能吗?

我意识到我可以将采样器更改为使用像素寻址,但这会带来一些限制,如金属着色器规范中所述。

【问题讨论】:

你能创建另一个纹理与完整纹理共享相同的缓冲区,使用相同的bytesPerRow,但更小的widthheight 好吧,我想我可以创建新的纹理,但这是我希望避免的。这些纹理是从CVMetalTextureCache 回收的,CVPixelBufferRefiosurface 支持,IOSurface 的宽度和高度可以是任何值。 IOSurface 用于从 XPC 服务传送数据,而 XPC 服务只会写入它需要的区域内的 IOSurface。所以我希望我可以轻松地将我的采样器夹在纹理的那个区域,而不是创建一个新的纹理。 CVMetalTextureCacheCreateTextureFromImage 是不是太贵了?您可以使用相同的CVPixelBufferRef 来创建多个纹理。 好点。我没有考虑从同一个 CVPixelBuffer 制作多个纹理,但是再次查看 API,我没有看到一种方法来指定新纹理相对于源像素缓冲区的原点。 (有一个 widthheight 属性用于设置缓冲区的尺寸,但没有来源。)我想我只是假设使用 CVPixelBuffer 支持创建的任何纹理都应该具有相同的尺寸。 【参考方案1】:

不,但是如果您知道要从中采样的区域,则可以很容易地在着色器中进行一些数学运算来修正您的采样坐标。这通常与texture atlases 一起使用。

假设您有一个 500x500 的图像,并且您想要对右下角的 125x125 区域进行采样(只是为了让事情变得更有趣)。您可以将此采样区域作为float4 传入,将边界存储为xyzw 组件中的(左、上、宽、高)。在这种情况下,边界将是 (375, 375, 125, 125)。您传入的纹理坐标相对于该正方形已“标准化”。着色器只是将这些坐标缩放并偏置为纹理坐标,然后将它们归一化为整个纹理的尺寸:

fragment float4 fragment_main(FragmentParams in [[stage_in]],
                              texture2d<float, access::sample> tex2d [[texture(0)]],
                              sampler sampler2d [[sampler(0)]],
                              // ...
                              constant float4 &spriteBounds [[buffer(0)]])

    // original coordinates, normalized with respect to subimage
    float2 texCoords = in.texCoords;

    // texture dimensions
    float2 texSize = float2(tex2d.get_width(), tex2d.get_height());

    // adjusted texture coordinates, normalized with respect to full texture
    texCoords = (texCoords * spriteBounds.zw + spriteBounds.xy) / texSize;

    // sample color at modified coordinates
    float4 color = tex2d.sample(sampler2d, texCoords);
    // ...

【讨论】:

谢谢,这基本上是我在想知道是否有更好的方法之前强制执行的。有没有办法确定任何舍入将如何影响最终使用的像素?如果我有一个 450 像素宽的纹理,但我只使用前 250 个像素,我如何确定光栅化器选择的“最后一个”像素是第 250 个像素并且不会“向上取整”到第 251 个像素? (250 / 450 = 0.555555) 同样适用于任何可能偏移到纹理中的具有浮动值的原点,该浮动值可以向下舍入到区域外的值或区域内的值 +1。 大纹理会出现问题,但在这种情况下,请一直进行数学运算:(1 * 250 + 200) / 450 = 1.0。 W^5.

以上是关于你能改变金属着色器中采样器的边界吗?的主要内容,如果未能解决你的问题,请参考以下文章

在着色器中解释纹理数据

金属着色器中的线条更宽更平滑

像素着色器中的 SamplerState 问题

LWJGL 无法在着色器中采样帧缓冲纹理

GL_POINTS 着色器中的示例纹理

GLSL 像素化着色器中的边框伪影