[英]Passing textures with UInt8 component type to Metal compute shader
我有一個以編程方式生成的圖像,我想將此圖像作為紋理發送到計算着色器。 我生成這個圖像的方法是我將每個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()
其中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);
}
這段代碼的問題是我的紋理類型是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
具有與輸入紋理完全相同的屬性(width,height,pixelFormat等)。 為什么我認為這是反直覺的是我們將uint4
設置為像素,這意味着它由4個32位值組成,而每個像素應該是32位。 使用此當前代碼, {255, 127, 63, 255}
具有與{2550, 127, 63, 255}
{255, 127, 63, 255}
完全相同的結果,這意味着在寫入輸出紋理之前,值以某種方式被鉗位在0-255之間。 但這非常違反直覺。
玩起來比你似乎更熟悉,所以我會試着澄清一下。
首先,通過設計,Metal中紋理的存儲格式與讀取/采樣時獲得的類型之間存在松散的聯系。 您可以使用.bgra8Unorm
格式的紋理,當通過綁定為texture2d<float, access::sample>
的紋理進行采樣時texture2d<float, access::sample>
將為您提供一個float4
,其組件處於RGBA順序。 從那些壓縮字節到具有混合組件的浮點向量的轉換遵循金屬着色語言規范中規定的記錄良好的轉換規則。
還有一種情況是,當寫入存儲(例如)每個組件8位的紋理時,將鉗制值以適合底層存儲格式。 這還受紋理是否為norm
類型的影響:如果格式包含norm
,則將值解釋為它們指定的值介於0和1之間。否則,您讀取的值不會標准化。
例如:如果紋理是.bgra8Unorm
並且給定的像素包含字節值[0, 64, 128, 255]
.bgra8Unorm
[0, 64, 128, 255]
,那么當在請求float
組件的着色器中讀取時,您將得到[0.5, 0.25, 0, 1.0]
當你采樣它。 相反,如果格式為.rgba8Uint
,則會得到[0, 64, 128, 255]
.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);
}
相比之下,如果您的紋理格式為.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);
}
我知道這是一個玩具示例,但我希望通過上述信息,您可以弄清楚如何正確存儲和取樣值以實現您想要的效果。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.