Почему MTLTexture с 2D Array не работает? - PullRequest
0 голосов
/ 02 декабря 2018

Я пытаюсь воспроизвести технику карты сплат из учебника по Unity.Они используют Texture2DArray, поэтому я создал MTLTexture с этим типом:

private func createTerrainTexture(_ bundle: Bundle) -> MTLTexture {
    guard let device = MTLCreateSystemDefaultDevice() else {
        fatalError()
    }

    let names = ["sand", "grass", "earth", "stone", "snow"]

    let loader = MTKTextureLoader(device: device)
    let array = names.map { name -> MTLTexture in
        do {
            return try loader.newTexture(name: name, scaleFactor: 1.0, bundle: bundle, options: nil)
        } catch {
            fatalError()
        }
    }

    guard let queue = device.makeCommandQueue() else {
        fatalError()
    }
    guard let commandBuffer = queue.makeCommandBuffer() else {
        fatalError()
    }
    guard let encoder = commandBuffer.makeBlitCommandEncoder() else {
        fatalError()
    }

    let descriptor = MTLTextureDescriptor()
    descriptor.textureType = .type2DArray
    descriptor.pixelFormat = array[0].pixelFormat
    descriptor.width = array[0].width
    descriptor.height = array[0].height
    descriptor.mipmapLevelCount = array[0].mipmapLevelCount
    descriptor.arrayLength = 5

    guard let texture = device.makeTexture(descriptor: descriptor) else {
        fatalError()
    }

    var slice = 0
    array.forEach { item in
        encoder.copy(from: item,
                     sourceSlice: 0,
                     sourceLevel: 0,
                     sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0),
                     sourceSize: MTLSize(width: item.width, height: item.height, depth: 1),
                     to: texture,
                     destinationSlice: slice,
                     destinationLevel: 0,
                     destinationOrigin: MTLOrigin(x: 0, y: 0, z: 0))
        slice += 1
    }

    encoder.endEncoding()

    commandBuffer.commit()
    commandBuffer.waitUntilCompleted()

    return texture
}

Вот моя функция фрагментного шейдера:

fragment half4 terrainFragment(TerrainVertexOutput in [[stage_in]],
                               texture2d_array<float> terrainTexture [[texture(0)]])
{
    constexpr sampler sampler2d(coord::normalized, filter::linear, address::repeat);
    float2 uv = in.position.xz * 0.02;
    float4 c1 = terrainTexture.sample(sampler2d, uv, 0);
    return half4(c1);
}

Вот шейдер Unity из учебника:

void surf (Input IN, inout SurfaceOutputStandard o) {
    float2 uv = IN.worldPos.xz * 0.02;
    fixed4 c = UNITY_SAMPLE_TEX2DARRAY(_MainTex, float3(uv, 0));
    Albedo = c.rgb * _Color;
    o.Metallic = _Metallic;
    o.Smoothness = _Glossiness;
    o.Alpha = c.a;
}

По какой-то причине я получаю неправильный результат, когда текстура повторяется в столбцах.

Wrong texture

Результат, который я хочу получить:

enter image description here

Обновление.Вот как выглядит текстура в GPU Frame Debugger:

enter image description here

Когда я копирую mipmaps следующим образом:

var slice = 0
array.forEach { item in
    print(item.width, item.height, item.mipmapLevelCount)
    for i in 0..<descriptor.mipmapLevelCount {
        encoder.copy(from: item,
                     sourceSlice: 0,
                     sourceLevel: i,
                     sourceOrigin: MTLOrigin(x: 0, y: 0, z: 0),
                     sourceSize: MTLSize(width: item.width, height: item.height, depth: 1),
                     to: texture,
                     destinationSlice: slice,
                     destinationLevel: i,
                     destinationOrigin: MTLOrigin(x: 0, y: 0, z: 0))
    }

    slice += 1
}

I 'получаю ошибку:

-[MTLDebugBlitCommandEncoder validateCopyFromTexture:sourceSlice:sourceLevel:sourceOrigin:sourceSize:toTexture:destinationSlice:destinationLevel:destinationOrigin:options:]:254: failed assertion `(sourceOrigin.x + sourceSize.width)(512) must be <= width(256).'

1 Ответ

0 голосов
/ 03 декабря 2018

Проблема была в неправильном портировании входной переменной фрагмента шейдера.В исходном вводе использовался worldPos, но я использовал float4 position [[position]], и в соответствии со спецификацией металла это означает

Описывает значения относительной координаты окна (x, y, z, 1 / w) дляфрагмент.

Так что позиция была неправильной.Вот как выглядит правильный фрагментный шейдерный ввод:

struct TerrainVertexOutput
{
    float4 position [[position]];
    float3 p;
};

И функция вершины:

vertex TerrainVertexOutput terrainVertex(TerrainVertexInput in [[stage_in]],
                                         constant SCNSceneBuffer& scn_frame [[buffer(0)]],
                                         constant MyNodeBuffer& scn_node [[buffer(1)]])
{
    TerrainVertexOutput v;

    v.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);
    v.p = (scn_node.modelTransform * float4(in.position, 1.0)).xyz;

    return v;
}
...