Рендеринг различных форматов цвета текстур в Metal

Я использовал MTKTextureLoader для загрузки предоставленных пользователем изображений в текстуры для рендеринга. Я визуализирую эти предоставленные текстуры в промежуточную текстуру, а затем визуализирую промежуточную текстуру в отображаемом MTKView. И промежуточная текстура, и растягиваемая имеют одинаковый цветовой формат.

У меня возникли проблемы с некоторыми изображениями. Все изображения являются файлами PNG, но похоже, что я могу получить различные базовые данные из MTKTextureLoader.

Первый выпуск:

Я загружаю PNG с альфой и без альфы. Кажется, это фактор, но это не на 100% ясно. Оба свойства текстуры кажутся одинаковыми.

PNG с альфа-каналом:

Texture: <BronzeMtlTexture: 0x1015484b0>
    label = 512x512.png 
    textureType = MTLTextureType2D 
    pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB 
    width = 512 
    height = 512 
    depth = 1 
    arrayLength = 1 
    mipmapLevelCount = 10 
    sampleCount = 1 
    cpuCacheMode = MTLCPUCacheModeDefaultCache 
    storageMode = MTLStorageModeManaged 
    resourceOptions = MTLResourceCPUCacheModeDefaultCache MTLResourceStorageModeManaged  
    usage = MTLTextureUsageShaderRead  
    framebufferOnly = 0 
    purgeableState = MTLPurgeableStateNonVolatile 
    parentTexture = <null> 
    parentRelativeLevel = 0 
    parentRelativeSlice = 0 
    buffer = <null> 
    bufferOffset = 0 
    bufferBytesPerRow = 0 
    iosurface = 0x0 
    iosurfacePlane = 0
    label = 512x512.png

PNG без альфа:

Texture: <BronzeMtlTexture: 0x10164a9b0>
    label = 016 - jKsgTpt.png 
    textureType = MTLTextureType2D 
    pixelFormat = MTLPixelFormatBGRA8Unorm_sRGB 
    width = 1685 
    height = 815 
    depth = 1 
    arrayLength = 1 
    mipmapLevelCount = 11 
    sampleCount = 1 
    cpuCacheMode = MTLCPUCacheModeDefaultCache 
    storageMode = MTLStorageModeManaged 
    resourceOptions = MTLResourceCPUCacheModeDefaultCache MTLResourceStorageModeManaged  
    usage = MTLTextureUsageShaderRead  
    framebufferOnly = 0 
    purgeableState = MTLPurgeableStateNonVolatile 
    parentTexture = <null> 
    parentRelativeLevel = 0 
    parentRelativeSlice = 0 
    buffer = <null> 
    bufferOffset = 0 
    bufferBytesPerRow = 0 
    iosurface = 0x0 
    iosurfacePlane = 0
    label = 016 - jKsgTpt.png

В приведенном выше случае загружается PNG с альфа-каналом с замененными компонентами R&B. Есть ли способ обнаружить это, чтобы я мог правильно настроить шейдер по мере необходимости?

Вторая проблема:

Один из файлов PNG, который я тестировал, закончил загрузку как MTLPixelFormatRGBA16Unorm. Моя промежуточная текстура и возможность рисования в MTKView обычно MTLPixelFormatBGRA8Unorm. Это можно обнаружить, но как мне правильно визуализировать эту текстуру для промежуточной текстуры? В этом случае я получаю очень неприятную картину.


Я чувствую, что мне не хватает некоторых нюансов MTKTextureLoader, или что, возможно, он не предназначен для использования так, как я хочу его использовать.


Обновление 1

С загрузчиком текстур ничего особенного не делаю. Настроить особо нечего:

let textureLoader = MTKTextureLoader(device: metalDevice)

let options: [MTKTextureLoader.Option:Any] = [
    .generateMipmaps : true,
    .SRGB: true
]

textureLoader.newTexture(URL: url, options: options) { (texture, error) in
    // Store the texture here
}

Как показано в первом выпуске, я получу две разные текстуры, помеченные как BGRA8, но обычно пиксели с прозрачностью имеют порядок RGBA. Во втором выпуске у меня есть один конкретный PNG, который загружается в RGBA16.


Обновление 2

Настройка трубопровода:

let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = self.library.makeFunction(name: "instance_vertex")
pipelineDescriptor.fragmentFunction = self.library.makeFunction(name: "instance_fragment")
pipelineDescriptor.colorAttachments[0].pixelFormat = newTexture.pixelFormat
pipelineDescriptor.colorAttachments[0].isBlendingEnabled = true
pipelineDescriptor.colorAttachments[0].rgbBlendOperation = .add
pipelineDescriptor.colorAttachments[0].alphaBlendOperation = .add
pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha
pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha
pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha

newTexture в данном случае - это текстура, загруженная из MTKTextureLoader.

Настройка прохода рендеринга:

let renderPassDescriptor = MTLRenderPassDescriptor()
renderPassDescriptor.colorAttachments[0].texture = canvasTexture
renderPassDescriptor.colorAttachments[0].loadAction = .clear
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(
    red: Double(red),
    green: Double(green),
    blue: Double(blue),
    alpha: Double(alpha)
)
renderPassDescriptor.colorAttachments[0].storeAction = .store

let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)!

canvasTexture был сделан с тем же типом текстуры, что и MTKView. Я пробовал BGRA8 и BGRA8 SRGB, в зависимости от флага загрузчика для SRGB, установленного выше в загрузчике.

Рендеринг:

encoder.setRenderPipelineState(pipelineState)
encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
encoder.setVertexBuffer(uniformBuffer, offset: memorySize * offset, index: 1)
encoder.setFragmentTexture(newTexture, index: 0)
encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)

Фрагментный шейдер:

fragment half4 face_instance_fragment(VertexOut v [[stage_in]], texture2d<float, access::sample> texture [[ texture(0) ]])
{
    constexpr sampler textureSampler(mag_filter::linear,
                                     min_filter::linear,
                                     s_address::clamp_to_edge,
                                     t_address::clamp_to_edge,
                                     r_address::clamp_to_edge);

    return (half4)texture.sample(textureSampler, v.texturePosition);
}

Добавление .zyxw в сэмплер выше исправит цвета одной текстуры, но сломает другую, поэтому я знаю, что цвета правильные, только в неправильном порядке.


person Stephen H. Gerstacker    schedule 09.09.2018    source источник
comment
Вы когда-нибудь обращались к текстурам как к данным (байтам)? В любом случае отредактируйте свой вопрос, чтобы показать код, в котором вы загружаете PNG и создаете из них текстуры, а также все, что вы делаете с текстурами.   -  person Ken Thomases    schedule 10.09.2018
comment
Попробуй без .SRGB: true. Также покажите код, который использует текстуры.   -  person Ken Thomases    schedule 11.09.2018
comment
Я пробовал использовать .SRGB как true, так и false, при необходимости настраивая вид и целевой формат текстуры. Те же результаты.   -  person Stephen H. Gerstacker    schedule 11.09.2018
comment
Просто оставьте .SRGB. Если вы укажете его (истина или ложь), вы переопределите интерпретацию изображения по умолчанию. Кроме того, можно также объявить параметр текстуры во фрагментном шейдере как texture2d<half, ...>, так как в любом случае вы просто выполняете приведение к половине.   -  person Ken Thomases    schedule 11.09.2018
comment
Было много экспериментов, чтобы увидеть, как это исправить, так что да, .SRGB был во всех трех состояниях. Дело half в том, что я просто играл в какой-то момент.   -  person Stephen H. Gerstacker    schedule 11.09.2018


Ответы (1)


На этот вопрос будет сложно ответить, не увидев кода (как приложения, так и шейдера) и не получив подробностей о том, что вы наблюдаете и как. Например, как вы определяете, что в PNG без альфы компоненты R и B поменялись местами?

В любом случае шейдерам не нужно заботиться о порядке компонентов в формате пикселей. Чтения / выборки из текстуры всегда возвращают компонент R в компоненте .r вывода, компонент G в .g, компонент B в .b и альфа в .a, независимо от базового формата пикселей.

Точно так же шейдерам не нужно заботиться о том, является ли формат пикселей текстуры sRGB или нет. Шейдеры всегда работают с линейным RGBA. Metal выполняет автоматическое преобразование между текстурами sRGB и значениями шейдеров.

Формат пикселей действительно влияет на то, какой тип данных используется для чтения, выборки и записи. Нормализованные (знаковые или беззнаковые) форматы пикселей используют half или float. В форматах пикселей с плавающей запятой также используются half или float. В форматах целых пикселей без знака используются символы ushort или uint. В знаковых целочисленных форматах пикселей используются short или int. В форматах пикселей глубины (с трафаретом или без него) используется float.

person Ken Thomases    schedule 09.09.2018
comment
Во-первых, одна из текстур отображается с неправильными цветами. Если я переключаю сэмплер на возврат zyxw, если исправляет цвета, поэтому загрузчик текстур дает мне две текстуры, обе помечены как BGRA8, но шейдер должен поменять местами значения R и B, чтобы сделать один рендеринг должным образом, что нарушит рендеринг другой. - person Stephen H. Gerstacker; 10.09.2018