metal - 在 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
到上面的采样器将修复一个纹理的颜色,但破坏另一个,这就是我知道颜色正确的方式,只是顺序错误。
解决方案
如果不查看代码(应用程序和着色器)并获得有关您正在观察的内容和方式的详细信息,这将很难回答。例如,您如何确定没有 alpha 的 PNG 交换了 R 和 B 分量?
无论如何,着色器不需要关心像素格式的组件顺序。无论底层像素格式如何,从纹理读取/采样始终返回.r
输出分量中的 R 分量、 中的 G 分量.g
、 中的 B 分量.b
和 中的 alpha 。.a
同样,着色器不需要关心纹理的像素格式是否为 sRGB。着色器始终使用线性 RGBA。Metal 在 sRGB 纹理和着色器值之间进行自动转换。
像素格式确实会影响用于读取、采样和写入的数据类型。归一化(有符号或无符号)像素格式使用half
或float
。浮点像素格式也使用half
or float
。无符号整数像素格式使用ushort
or uint
。有符号整数像素格式使用short
or int
。深度(带或不带模板)像素格式使用float
.