My issue is because a quad is just two triangles. The texture is not rendered consistently on each triangle, and the texture is broken across the border between the two triangles. I'm using a screenshot of my lovely Minecraft house as an example texture:
As you see, from the top left of the screenshot and the bottom right, it seem to have been cut or folded or something. It's just distorted. And the distortion I speak of is NOT from the fact that it's being applied to a trapezoid, it's that it's inconsistently being applied to the two triangles that constitute the trapezoid.
So how can I fix this?
In 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)!)
Vertices:
//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]
Types in 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;
Bear with me, I use PascalCase because I think camelCase is ugly. I just don't like it. Well anyways, how do I correctly place a texture in a quad made of two triangles so it won't look all weird?
As you know, Metal performs perspective-correct vertex attribute interpolation on your behalf by using the depth information provided by the z coordinate of your vertex positions.
You're subverting this process by distorting the "projected" shape of the quad without providing perspective information to the graphics pipeline. This means that you need to pass along a little extra information in order to get correct interpolation. Specifically, you need to include "depth" information in your texture coordinates and perform the "perspective" divide manually in the fragment shader.
For a fully-general solution, consult this answer, but for a simple fix in the case of symmetrical scaling about the vertical axis of the quad, use float3
texture coordinates instead of float2
and set the x and z coordinates such that z is the scale factor introduced by your pseudo-perspective projection and when x is divided by z, the result is what x would have been without the divide.
For example, if the distance between the top two vertices is half that of the bottom two vertices (as appears to be the case in your screenshot), set the upper-left texture coordinate to (0, 0, 0.5) and the upper-right texture coordinate to (0.5, 0, 0.5). Pass these "3D" texture coordinates through to your fragment shader, then divide by z
before sampling:
half4 color = myTexture.sample(mySampler, in.texCoords.xy / in.texCoords.z);