我有一个以编程方式生成的图像,我想将此图像作为纹理发送到计算着色器.我生成这个图像的方法是我将每个RGBA组件计算为UInt8值,并将它们组合成一个UInt32并将其存储在图像的缓冲区中.我使用以下代码执行此操作:
guard let cgContext = CGContext(data: nil,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: 0,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: RGBA32.bitmapInfo) else {
print("Unable to create CGContext")
return
}
guard let buffer = cgContext.data else {
print("Unable to create textures")
return
}
let pixelBuffer = buffer.bindMemory(to: RGBA32.self, capacity: width * height)
let heightFloat = Float(height)
let widthFloat = Float(width)
for i in 0 ..< height {
let latitude = Float(i + 1) / heightFloat
for j in 0 ..< width {
let longitude = Float(j + 1) / widthFloat
let x = UInt8(((sin(longitude * Float.pi * 2) * cos(latitude * Float.pi) + 1) / 2) * 255)
let y = UInt8(((sin(longitude * Float.pi * 2) * sin(latitude * Float.pi) + 1) / 2) * 255)
let z = UInt8(((cos(latitude * Float.pi) + 1) / 2) * 255)
let offset = width * i + j
pixelBuffer[offset] = RGBA32(red: x, green: y, blue: z, alpha: 255)
}
}
let coordinateConversionImage = cgContext.makeImage()
Run Code Online (Sandbox Code Playgroud)
哪里RGBA32是一个小结构,可以进行转移并创造UInt32价值.这个图像结果很好,因为我可以将其转换为UIImage并保存到我的照片库.
当我尝试将此图像作为纹理发送到计算着色器时,会出现问题.以下是我的着色器代码:
kernel void updateEnvironmentMap(texture2d<uint, access::read> currentFrameTexture [[texture(0)]],
texture2d<uint, access::read> coordinateConversionTexture [[texture(1)]],
texture2d<uint, access::write> environmentMap [[texture(2)]]
uint2 gid [[thread_position_in_grid]])
{
const uint4 pixel = {255, 127, 63, 255};
environmentMap.write(pixel, gid);
}
Run Code Online (Sandbox Code Playgroud)
这段代码的问题是我的纹理类型uint是32位,我想通过附加4个8位值来生成32位像素,就像我在CPU上一样.但是,我似乎无法在Metal上做到这一点,因为没有byte类型我可以一起追加并组成一个uint32.所以,我的问题是,在Metal计算着色器上处理2D纹理和设置32位像素的正确方法是什么?
奖金问题:另外,我已经看到示例着色器代码texture2d<float, access::read>作为输入纹理类型.我假设它代表一个介于0.0和1.0之间的值,但是这对于一个值为0到255之间的unsigned int有什么好处呢?
编辑:为了澄清,着色器的输出纹理environmentMap具有与输入纹理完全相同的属性(宽度,高度,像素格式等).为什么我认为这是反直觉的是我们将a设置uint4为像素,这意味着它由4个32位值组成,而每个像素应为32位.使用此当前代码,{255, 127, 63, 255}具有完全相同的结果{2550, 127, 63, 255},意味着在写入输出纹理之前,值以某种方式被钳位在0-255之间.但这非常违反直觉.
war*_*enm 14
玩起来比你似乎更熟悉,所以我会试着澄清一下.
首先,通过设计,Metal中纹理的存储格式与读取/采样时获得的类型之间存在松散的联系.您可以使用.bgra8Unorm格式的纹理,当通过纹理绑定采样时,texture2d<float, access::sample>将为您float4提供RGBA顺序的组件.从那些压缩字节到具有混合组件的浮点向量的转换遵循金属着色语言规范中规定的记录良好的转换规则.
还有一种情况是,当写入存储(例如)每个组件8位的纹理时,将钳制值以适合底层存储格式.这还受纹理是否为norm类型的影响:如果格式包含norm,则将值解释为它们指定的值介于0和1之间.否则,您读取的值不会标准化.
例如:如果纹理是.bgra8Unorm并且给定的像素包含字节值[0, 64, 128, 255],那么当在请求float组件的着色器中读取时,您将[0.5, 0.25, 0, 1.0]在对其进行采样时获得.相比之下,如果格式是.rgba8Uint,你会得到[0, 64, 128, 255].纹理的存储格式对其内容在采样时如何解释具有主要影响.
我假设纹理的像素格式是这样的.rgba8Unorm.如果是这种情况,您可以通过编写内核来实现您想要的目标:
kernel void updateEnvironmentMap(texture2d<float, access::read> currentFrameTexture [[texture(0)]],
texture2d<float, access::read> coordinateConversionTexture [[texture(1)]],
texture2d<float, access::write> environmentMap [[texture(2)]]
uint2 gid [[thread_position_in_grid]])
{
const float4 pixel(255, 127, 63, 255);
environmentMap.write(pixel * (1 / 255.0), gid);
}
Run Code Online (Sandbox Code Playgroud)
相比之下,如果您的纹理格式为.rgba8Uint,您可以通过这样编写来获得相同的效果:
kernel void updateEnvironmentMap(texture2d<float, access::read> currentFrameTexture [[texture(0)]],
texture2d<float, access::read> coordinateConversionTexture [[texture(1)]],
texture2d<float, access::write> environmentMap [[texture(2)]]
uint2 gid [[thread_position_in_grid]])
{
const float4 pixel(255, 127, 63, 255);
environmentMap.write(pixel, gid);
}
Run Code Online (Sandbox Code Playgroud)
我知道这是一个玩具示例,但我希望通过上述信息,您可以弄清楚如何正确存储和取样值以实现您想要的效果.