在 MTKView 上渲染 MTLTexture 不保持宽高比

uti*_*nia 2 ios swift metal metalkit

我有一个 1080x1920 像素的纹理。MTKView我正在尝试以不同的纵横比渲染它。(即iPad/iPhone X全屏)。

这就是我渲染纹理的方式MTKView

private func render(_ texture: MTLTexture, withCommandBuffer commandBuffer: MTLCommandBuffer, device: MTLDevice) {
    guard let currentRenderPassDescriptor = metalView?.currentRenderPassDescriptor,
            let currentDrawable = metalView?.currentDrawable,
            let renderPipelineState = renderPipelineState,
            let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: currentRenderPassDescriptor) else {
                semaphore.signal()
                return
        }

    encoder.pushDebugGroup("RenderFrame")
    encoder.setRenderPipelineState(renderPipelineState)
    encoder.setFragmentTexture(texture, index: 0)
    encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4, instanceCount: 1)
    encoder.popDebugGroup()
    encoder.endEncoding()

    // Called after the command buffer is scheduled
    commandBuffer.addScheduledHandler { [weak self] _ in
        guard let strongSelf = self else {
            return
        }
        strongSelf.didRender(texture: texture)
        strongSelf.semaphore.signal()
    }

    commandBuffer.present(currentDrawable)
    commandBuffer.commit()
}
Run Code Online (Sandbox Code Playgroud)

我希望纹理像.scaleAspectFill在 a 上一样渲染UIView,并且我正在尝试学习Metal,所以我不确定应该在哪里寻找它(.metal文件、管道、视图本身、编码器等)

谢谢!

编辑:这是着色器代码:

#include <metal_stdlib> using namespace metal;

typedef struct {
    float4 renderedCoordinate [[position]];
    float2 textureCoordinate; } TextureMappingVertex;

vertex TextureMappingVertex mapTexture(unsigned int vertex_id [[ vertex_id ]]) {
    float4x4 renderedCoordinates = float4x4(float4( -1.0, -1.0, 0.0, 1.0 ),
                                            float4(  1.0, -1.0, 0.0, 1.0 ),
                                            float4( -1.0,  1.0, 0.0, 1.0 ),
                                            float4(  1.0,  1.0, 0.0, 1.0 ));

    float4x2 textureCoordinates = float4x2(float2( 0.0, 1.0 ),
                                           float2( 1.0, 1.0 ),
                                           float2( 0.0, 0.0 ),
                                           float2( 1.0, 0.0 ));
    TextureMappingVertex outVertex;
    outVertex.renderedCoordinate = renderedCoordinates[vertex_id];
    outVertex.textureCoordinate = textureCoordinates[vertex_id];

    return outVertex; }

fragment half4 displayTexture(TextureMappingVertex mappingVertex [[ stage_in ]],texture2d<float, access::sample> texture [[ texture(0) ]]) {
    constexpr sampler s(address::clamp_to_edge, filter::linear);

    return half4(texture.sample(s, mappingVertex.textureCoordinate));
}
Run Code Online (Sandbox Code Playgroud)

1aw*_*ose 6

处理金属纹理或一般金属时需要注意的一些一般事项:

\n\n
    \n
  • 您应该考虑像素之间的差异,请参阅此处的文档。UIView 子类的frame属性(MTKView 就是其中之一)始终为您提供视图的宽度和高度(以点为单位

  • \n
  • 从点到实际像素的映射是通过contentScaleFactor选项控制的。MTKView 会自动选择具有与设备实际像素相匹配的合适纵横比的纹理。例如,iPhone X 上 MTKView 的底层纹理的分辨率为 2436 x 1125(实际显示尺寸以像素为单位)。此处记录了这一点:“MTKView 类自动支持本机屏幕缩放。默认情况下,始终保证 view\xe2\x80\x99s 当前可绘制对象的大小与视图本身的大小相匹配。”

  • \n
  • 如此处所述,.scaleAspectFill选项“缩放内容以填充视图的大小。内容的某些部分可能会被剪切以填充视图\xe2\x80\x99s 边界”。您想要模拟这种行为。

  • \n
  • 使用 Metal 渲染无非是“绘制”到解析纹理,该纹理由 MTKView 自动设置。但是,您仍然拥有完全的控制权,并且可以通过手动创建纹理并将它们设置在您的renderPassDescriptor. 但你现在不需要关心这个。您应该关心的一件事是您想要在解析纹理中渲染解析纹理中 1080x1920 像素纹理的内容、位置和部分(可能具有不同的纵横比)。我们想要完全填充(“scaleAspectFill”)解析纹理,因此我们将其保留renderedCoordinates在片段着色器中。它们在整个解析纹理上定义一个矩形,这意味着解析纹理中的每个像素都会调用片段着色器。接下来,我们将简单地更改纹理坐标。

  • \n
  • 让我们将纵横比定义为ratio = width / height,将解析纹理定义为r_tex,将要渲染的纹理定义为tex

  • \n
  • 因此,假设您的解析纹理不具有相同的纵横比,则有两种可能的情况:

    \n\n
      \n
    1. 您要渲染的纹理的长宽比大于解析纹理(Metal 渲染到的纹理)的长宽比,这意味着您要渲染的纹理的宽度比解析纹理更大。在这种情况下,我们保留y坐标值不变。纹理坐标的值x将发生变化:

      \n\n
      x_left  = 0 + ((tex.width - r_tex.width) / 2.0)\nx_right = tex_width - ((tex.width - r_tex_width) / 2.0)\n
      Run Code Online (Sandbox Code Playgroud)\n\n

      这些值必须标准化,因为纹理样本需要 0 到 1 范围内的坐标:

      \n\n
      x_left  = x_left / tex.width\nx_right = x_right / tex.width\n
      Run Code Online (Sandbox Code Playgroud)\n\n

      我们有了新的纹理坐标:

      \n\n
      topLeft = float2(x_left,0)\ntopRight = float2(x_right,0)\nbottomLeft = float2(x_left,1)\nbottomRight = float2(x_right,1)\n
      Run Code Online (Sandbox Code Playgroud)\n\n

      这将产生这样的效果:纹理的顶部或底部不会被剪切掉,但左侧和右侧的一些外部部分将被剪切,即不可见。

    2. \n
    3. 您要渲染的纹理的长宽比小于解析纹理的长宽比。过程与第一个场景相同,但这次我们将更改y坐标

    4. \n
  • \n
\n\n

这应该渲染您的纹理,以便解析纹理完全填充,并且纹理的纵横比保持在 x 轴上。维护 y 轴的工作原理类似。此外,您必须检查纹理的哪一侧较大/较小,并将其纳入您的计算中。这将剪切部分纹理,就像使用 时一样scaleAspectFill。请注意,上述解决方案未经测试。但我希望它有帮助。请务必时常访问Metal 最佳实践文档,这对于掌握正确的基本概念非常有帮助。享受金属带来的乐趣!

\n