首页 > 解决方案 > 在 Metal 中渲染不同的纹理颜色格式

问题描述

我一直在使用 MTKTextureLoader 将用户提供的图像加载到纹理中进行渲染。我将这些提供的纹理渲染到中间纹理,然后将中间纹理渲染到 MTKView 可绘制对象。中间纹理和可绘制对象都具有相同的颜色格式。

我遇到了某些图像的一些问题。所有图像都是 PNG 文件,但似乎我可以从 MTKTextureLoader 获得不同的基础数据。

首要问题:

我加载了一个带 alpha 和不带 alpha 的 PNG。这似乎是一个因素,但这并不是 100% 清楚的。两种纹理属性似乎相同。

带 alpha 的 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

没有 alpha 的 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

在上面的例子中,带有 alpha 的 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 顺序的像素。在第二期中,我有一个在 RGBA16 中加载的特定 PNG。


更新 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到上面的采样器将修复一个纹理的颜色,但破坏另一个,这就是我知道颜色正确的方式,只是顺序错误。

标签: metalmetalkit

解决方案


如果不查看代码(应用程序和着色器)并获得有关您正在观察的内容和方式的详细信息,这将很难回答。例如,您如何确定没有 alpha 的 PNG 交换了 R 和 B 分量?

无论如何,着色器不需要关心像素格式的组件顺序。无论底层像素格式如何,从纹理读取/采样始终返回.r输出分量中的 R 分量、 中的 G 分量.g、 中的 B 分量.b和 中的 alpha 。.a

同样,着色器不需要关心纹理的像素格式是否为 sRGB。着色器始终使用线性 RGBA。Metal 在 sRGB 纹理和着色器值之间进行自动转换。

像素格式确实会影响用于读取、采样和写入的数据类型。归一化(有符号或无符号)像素格式使用halffloat。浮点像素格式也使用halfor float。无符号整数像素格式使用ushortor uint。有符号整数像素格式使用shortor int。深度(带或不带模板)像素格式使用float.


推荐阅读