Передача текстур с типом компонента UInt8 в вычислительный шейдер Metal

У меня есть изображение, которое я генерирую программно, и я хочу отправить это изображение в качестве текстуры в вычислительный шейдер. То, как я генерирую это изображение, - это то, что я вычисляю каждый из компонентов 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-битным, и я хочу генерировать 32-битные пиксели точно так же, как я делаю на CPU, добавляя 4 8-битных значения. Тем не менее, я не могу сделать это на металле, так как нет byte типа, что я могу просто сложить вместе и сделать uint32, Итак, мой вопрос в том, как правильно обрабатывать 2D-текстуры и устанавливать 32-битные пиксели в металлическом вычислительном шейдере?

Бонусный вопрос: Кроме того, я видел пример кодов шейдеров с texture2d<float, access::read> в качестве входного типа текстуры. Я предполагаю, что это представляет значение между 0.0 и 1.0, но какое преимущество это имеет по сравнению с беззнаковым int со значениями между 0 и 255?

Изменить: чтобы уточнить, выходной текстуры шейдера, environmentMap, имеет те же свойства (ширина, высота, pixelFormat и т. д.), что и входные текстуры. Почему я думаю, что это противоречит интуитивно понятным тем, что мы устанавливаем uint4 как пиксель, что означает, что он состоит из 4 32-битных значений, тогда как каждый пиксель должен быть 32-битным. С этим текущим кодом, {255, 127, 63, 255} имеет тот же результат, что и {2550, 127, 63, 255}Это означает, что значения каким-то образом фиксируются в диапазоне 0-255 перед записью в выходную текстуру. Но это крайне нелогично.

1 ответ

Решение

В игре немного больше магии, чем вы, кажется, знакомы, поэтому я попытаюсь объяснить.

Прежде всего, в дизайне существует слабая связь между форматом хранения текстур в металле и типом, который вы получаете, когда читаете / пробуете. Вы можете иметь текстуру в .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);
}

Напротив, если ваша текстура имеет формат .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);
}

Я понимаю, что это игрушечный пример, но я надеюсь, что с помощью вышеизложенной информации вы сможете выяснить, как правильно хранить и выбирать значения для достижения того, чего вы хотите.

Другие вопросы по тегам