金属:如何在不进行颜色/伽玛转换的情况下将图像附加到着色器?

时间:2018-08-10 13:40:00

标签: shader scenekit metal color-space gamma

是否可以在没有任何颜色/伽玛校正的情况下将图像发送到金属着色器(来自Scenekit)?

我有一个数据纹理,其中每个通道都对应于我想要测试的特定值。这是一张带有绿色通道映射到国家/地区索引的世界地图。如果该国家/地区处于“活跃”状态,则将其显示为红色;否则,将显示为灰色。

这是我使用的代码示例:

sphereNode = SCNNode(geometry: sphereGeometry)
scene.rootNode.addChildNode(sphereNode)

let program = SCNProgram()
program.vertexFunctionName = "sliceVertex"       
program.fragmentFunctionName = "sliceFragment"
sphereNode.geometry?.firstMaterial?.program = program

let imageProperty = SCNMaterialProperty(contents: UIImage(named: "art.scnassets/Textures/earth-data-map-2k.png")!)
mageProperty.mipFilter = SCNFilterMode.none 
sphereNode.geometry?.firstMaterial?.setValue(imageProperty, forKey: "dataTexture")

片段着色器:

fragment float4 sliceFragment(
    Vertex in [[stage_in]],
    texture2d<float, access::sample> dataTexture [[texture(0)]]
){
    constexpr sampler quadSampler(coord::normalized, filter::linear, address::repeat);
    float4 color = dataTexture.sample(quadSampler, in.texCoords);
    int index = int(color.g * 255.0);
    float active = index == 81 ? 1.0 : 0.0;
    float3 col = mix(float3(color.g,color.g,color.g), float3(1.0,0.0,0.0), active);
    return float4(col.r, col.g, col.b, 1);
}

问题是,我用于纹理的UIImage似乎已转换为线性空间,但我想使用着色器中不变的图像数据。

1 个答案:

答案 0 :(得分:4)

金属始终将着色器内的线性RGB用于纹理数据。如果纹理是sRGB而非线性,则读取和采样会从sRGB转换为线性,而写入会从线性转换为sRGB。

纹理像素格式告诉Metal,支持纹理的数据是线性的还是sRGB。大多数像素格式都是线性的,但有些名称以_sRGB结尾并且是sRGB。

SCNMaterialPropertyUIImage在幕后(无双关语)将解释图像数据,并使用源颜色配置文件选择像素格式。如果PNG的颜色配置文件指定的不是线性或sRGB,则可能是转换为其中的一种。如果没有颜色配置文件,则可能是SRGB。

表示非图像数据的PNG应该带有指定其为线性RGB的颜色配置文件。如果您无法修改PNG并将其解释为sRGB,则可能需要遍历CGImage并使用copy(colorSpace:)创建一个具有相同数据的新图像对象,并覆盖其颜色配置文件。您将使用CGColorSpace(.genericRGBLinear)获得所需的色彩空间。

此外,您应该为着色器的采样器使用filter::nearest。合并相邻国家/地区的索引没有任何意义。

比这更好的是,可能是在应用程序的资源中使用数据文件格式,而不是图像文件格式。然后将其传递到缓冲区而不是纹理中的着色器。