1

我有一个 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()
}

我希望.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));
}
4

2 回答 2

5

在处理金属纹理或一般金属时,需要从以下几个方面着手:

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

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

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

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

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

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

    1. 您要渲染的纹理的纵横比大于解析纹理(Metal 渲染到的纹理)的纵横比,这意味着您要渲染的纹理的宽度大于解析纹理的宽度。在这种情况下,我们y保持坐标值不变。纹理坐标的x值将被更改:

      x_left  = 0 + ((tex.width - r_tex.width) / 2.0)
      x_right = tex_width - ((tex.width - r_tex_width) / 2.0)
      

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

      x_left  = x_left / tex.width
      x_right = x_right / tex.width
      

      我们有了新的纹理坐标:

      topLeft = float2(x_left,0)
      topRight = float2(x_right,0)
      bottomLeft = float2(x_left,1)
      bottomRight = float2(x_right,1)
      

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

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

这应该渲染您的纹理,以便完全填充解析纹理,并且在 x 轴上保持纹理的纵横比。维护 y 轴的工作方式类似。此外,您必须检查纹理的哪一侧更大/更小,并将其纳入您的计算中。这将像使用scaleAspectFill. 请注意,上述解决方案未经测试。但我希望它会有所帮助。请务必不时访问Metal Best Practices文档,正确掌握基本概念非常有帮助。与金属玩得开心!

于 2018-01-04T22:09:35.177 回答
0

因此,您的顶点着色器非常直接地指示源纹理被拉伸到视口的尺寸。您正在渲染一个填充视口的四边形,因为它的坐标在水平和垂直方向上的标准化设备坐标系的极值 ([-1, 1])。

并且您正在将源纹理角到角映射到同一范围内。这是因为您为纹理坐标指定了纹理坐标空间的极值 ([0, 1])。

有多种方法可以实现您想要的。您可以通过缓冲区将顶点坐标传递给着色器,而不是对它们进行硬编码。这样,您可以在应用程序代码中计算适当的值。您将在渲染目标中计算所需的目标坐标,以 NDC 表示。因此,从概念上讲,类似left_ndc = (left_pixel / target_width) * 2 - 1等。

或者,可能更容易,您可以保留着色器原样并更改绘制操作的视口以仅针对渲染目标的适当部分。

于 2018-01-04T18:32:23.433 回答