2017-12-10 221 views
9

Mam obraz, który generuję programowo i chcę wysłać ten obraz jako teksturę do modułu cieniującego Compute. Sposób generowania tego obrazu polega na tym, że obliczam każdy z komponentów RGBA jako wartości UInt8 i łączę je w UInt32 i zapisuję w buforze obrazu. Czynię to z następującego fragmentu kodu:Przekazywanie tekstur o typie komponentu UInt8 do shadera obliczeniowego Metal

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() 

gdzie RGBA32 jest trochę struct, który nie do przesuwania i tworzenia wartości UInt32. Obraz ten okazuje się świetny, ponieważ mogę go przekonwertować na UIImage i zapisać go w mojej bibliotece zdjęć.

Problem pojawia się, gdy próbuję wysłać ten obraz jako teksturę do modułu cieniującego Compute. Poniżej jest mój kod shaderów:

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); 
} 

Problem z tym kodem jest, że rodzaj moich tekstur jest uint, który jest 32-bitów, a chcę, aby wygenerować 32-bitowy pikselach samo jak ja na Procesor, dołączając 4 wartości 8-bitowe. Jednak nie mogę tego zrobić na Metalie, ponieważ nie ma takiego typu, który mogę dodać do siebie i utworzyć uint32. Moje pytanie brzmi: jaki jest prawidłowy sposób obsługi tekstur 2D i ustawiania 32-bitowych pikseli na shaderze obliczeniowym Metal?

Dodatkowe pytanie: Widziałem również przykładowe kody modułu cieniującego z texture2d<float, access::read> jako typ tekstury wejściowej. Zakładam, że reprezentuje ona wartość między 0.0 a 1.0, ale jaką ma to zaletę, która ma nad unsigned int z wartościami od 0 do 255?

Edycja: Aby wyjaśnić, tekstura wyjściowa modułu cieniującego, environmentMap, ma dokładnie takie same właściwości (szerokość, wysokość, format pikselu itp.), Co tekstury wejściowe. Dlaczego uważam, że jest to sprzeczne z intuicją, to ustawienie uint4 jako piksela, co oznacza, że ​​składa się z 4 32-bitowych wartości, podczas gdy każdy piksel powinien mieć 32 bity. Przy obecnym kodzie {255, 127, 63, 255} ma dokładnie taki sam wynik, jak {2550, 127, 63, 255}, co oznacza, że ​​wartości zostają w jakiś sposób zaciśnięte między 0-255 przed zapisaniem w teksturach wyjściowych. Jest to jednak skrajnie sprzeczne z intuicją.

Odpowiedz

8

Jest trochę więcej magii niż ci się wydaje, więc spróbuję wyjaśnić.

Po pierwsze, zgodnie z projektem, istnieje luźne połączenie między formatem przechowywania tekstur w Metal i typem, jaki można uzyskać podczas czytania/próbkowania. Możesz mieć teksturę w formacie .bgra8Unorm, która po próbkowaniu przez teksturę oprawioną jako texture2d<float, access::sample> da Ci float4 z jej komponentami w kolejności RGBA. Konwersja z tych spakowanych bajtów do wektora swobodnego z elementami swizzled odbywa się zgodnie z dobrze udokumentowanymi regułami konwersji określonymi w specyfikacji cieniowania metalu.

Jest również tak, że podczas zapisywania do tekstury, której pamięć jest (na przykład) 8 bitów na komponent, wartości zostaną zaciśnięte w celu dopasowania do podstawowego formatu przechowywania. Na to ma wpływ fakt, czy tekstura jest typu norm: jeśli format zawiera norm, wartości są interpretowane tak, jakby określały wartość między 0 a 1. W przeciwnym razie odczytane wartości nie są znormalizowane.

Przykład: jeśli faktura jest .bgra8Unorm i dany piksel zawiera bajt wartości [0, 64, 128, 255], a następnie, gdy przeczytał w cieniującego, który żąda float komponenty, dostaniesz [0.5, 0.25, 0, 1.0] kiedy spróbować go. Natomiast jeśli formatem jest .rgba8Uint, otrzymasz [0, 64, 128, 255].Format przechowywania tekstury ma decydujący wpływ na to, jak jego zawartość zostanie zinterpretowana podczas próbkowania.

Zakładam, że format piksela tekstury jest podobny do .rgba8Unorm. Jeśli to przypadek, można osiągnąć to, co chcesz, pisząc jądro tak:

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); 
} 

Natomiast jeśli struktura ma format .rgba8Uint, można uzyskać ten sam efekt, pisząc tak:

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); 
} 

rozumiem, że jest to przykład zabawki, ale mam nadzieję, że z powyższych informacji, można dowiedzieć się, jak prawidłowo przechowywać i przykładowe wartości, aby osiągnąć to, co chcesz.

+0

Dzięki Warren, to wyjaśniło wiele rzeczy. Ponadto, jeśli ktoś chce uzyskać więcej informacji na ten temat, należy zapoznać się z rozdziałem 7.7 dotyczącym teksturowania i reguł konwersji w kategorii cieniowania metalu. – halileohalilei