如何在 Metal 的四边形中正确渲染纹理?

How do I correctly render a texture in a quad in Metal?

我的问题是因为四边形只是两个三角形。纹理在每个三角形上的渲染不一致,并且纹理在两个三角形之间的边界处断开。我正在使用我可爱的 ​​Minecraft 房子的屏幕截图作为示例纹理:

Rendered textured quad

如你所见,从截图的左上角和右下角来看,它似乎被切割或折叠了。它只是扭曲了。我所说的失真不是因为它被应用于梯形,而是它不一致地应用于构成梯形的两个三角形。

Original screenshot

那么我该如何解决这个问题?

在 viewDidLoad 中:

let VertexDescriptor = MTLVertexDescriptor()
let Attribute1Offset = MemoryLayout<simd_float3>.stride
let Attribute2Offset = Attribute1Offset+MemoryLayout<simd_float4>.stride
VertexDescriptor.attributes[0].format = .float3
VertexDescriptor.attributes[1].format = .float4
VertexDescriptor.attributes[1].offset = Attribute1Offset
VertexDescriptor.attributes[2].format = .float2
VertexDescriptor.attributes[2].offset = Attribute2Offset
VertexDescriptor.layouts[0].stride = Attribute2Offset+MemoryLayout<simd_float2>.stride
PipelineDescriptor.vertexDescriptor = VertexDescriptor
let TextureLoader = MTKTextureLoader(device: Device)
Texture = try? TextureLoader.newTexture(URL: Bundle.main.url(forResource: "Texture.png", withExtension: nil)!)

顶点:

//First four = position, second four = color, last two = texture coordinates
let Vertices: [Float] = [-0.5, 0.5, 0, 0, 1, 1, 0, 1, 0, 0,
                         0.5, 0.5, 0, 0, 0, 1, 1, 1, 1, 0,
                         1, -1, 0, 0, 0, 1, 0, 1, 1, 1,
                         -1, -1, 0, 0, 0, 0, 1, 1, 0, 1]

键入 Shaders.metal

typedef struct {
    float4 Position [[attribute(0)]];
    float4 Color [[attribute(1)]];
    float2 TexCoord [[attribute(2)]];
} VertexIn;
typedef struct {
    float4 Position [[position]];
    float4 Color;
    float2 TexCoord;
} VertexOut;

请耐心等待,我使用 PascalCase 是因为我认为 camelCase 很难看。我只是不喜欢它。无论如何,我如何正确地将纹理放置在由两个三角形组成的四边形中,这样它看起来才不会很奇怪?

如您所知,Metal 通过使用顶点位置的 z 坐标提供的深度信息代表您执行 perspective-correct vertex attribute interpolation

您通过扭曲四边形的 "projected" 形状而不向图形管道提供透视信息来颠覆此过程。这意味着您需要传递一些额外的信息才能获得正确的插值。具体来说,您需要在纹理坐标中包含 "depth" 信息,并在片段着色器中手动执行 "perspective" 划分。

对于完全通用的解决方案,请参阅 this answer,但对于关于四边形垂直轴对称缩放的简单修复,请使用 float3 纹理坐标而不是 float2 并设置 xz 坐标,使得 z 是你引入的比例因子伪透视投影,当 x 除以 z,结果就是 x没有鸿沟。

例如,如果顶部两个顶点之间的距离是底部两个顶点之间的距离的一半(如您的屏幕截图所示),则将左上角纹理坐标设置为 (0, 0, 0.5 ) 和右上纹理坐标为 (0.5, 0, 0.5)。将这些“3D”纹理坐标传递给片段着色器,然后在采样前除以 z

half4 color = myTexture.sample(mySampler, in.texCoords.xy / in.texCoords.z);