Считывание значений с плавающей запятой из текстуры RGBAFloat в Unity 3D

#unity3d #gpgpu

#unity3d #gpgpu

Вопрос:

Кажется, люди мало обсуждают текстуры с плавающей запятой. Я использовал их для выполнения некоторых вычислений, а затем переслал результат в другой поверхностный шейдер (для получения некоторых конкретных деформаций), и это круто, у меня всегда работает, если я перевариваю результаты в шейдере, но на этот раз мне нужно получить эти значения на стороне процессора, поэтому я получаю массив float[] с результатами (сразу после вызова Graphics.Блит, который заполняет текстуру с плавающей запятой). Как этого можно достичь?

На заметку: единственный парень, которого я видел, использующим этот метод до сих пор, это Кейджиро, например, в его стене Kvant; если у вас есть другие источники, я был бы признателен, если бы вы дали мне знать.

Кстати, я знаю, что существуют вычислительные шейдеры, а также OpenCL и CUDA. Это метод, который мне нужен сейчас.

Комментарии:

1. Возможно, я что-то упускаю, но Texture2D.readPixels не предоставляет вам необходимую функциональность?

2. Привет, @Bart Я использую readPixels для получения результатов рендер-текстуры в Texture2D (все еще формат FloatARGB), но что потом? Я не могу использовать getPixels, поскольку он возвращает массив цветов, и значения будут обрезаны. Сейчас я смотрю на GetRawTextureData, думаю, у меня что-нибудь получится с этим.

3. Хм, я должен посмотреть, но разве getPixels (в отличие от альтернативы Color32) не дает вам результатов с плавающей запятой полной точности?

4. Так ли это? Я пытался в прошлом. Я получил неожиданные результаты, но, возможно, я что-то пропустил.

Ответ №1:

Итак, я придумал это решение.

  float[] DecodeFloatTexture()
{
    Texture2D decTex = new Texture2D(resultBuffer.width, resultBuffer.height, TextureFormat.RGBAFloat, false);
    RenderTexture.active = resultBuffer;
    decTex.ReadPixels(new Rect(0, 0, resultBuffer.width, resultBuffer.height), 0, 0);
    decTex.Apply();
    RenderTexture.active = null;
    Color[] colors = decTex.GetPixels();
    // HERE YOU CAN GET ALL 4 FLOATS OUT OR JUST THOSE YOU NEED.
    // IN MY CASE ALL 4 VALUES HAVE A MEANING SO I'M GETTING THEM ALL.
    float[] results = new float[colors.Length*4];
    for(int i=0; i<colors.Length; i  )
    {
        results[i * 4] = colors[i].r;
        results[i * 4   1] = colors[i].g;
        results[i * 4   2] = colors[i].b;
        results[i * 4   3] = colors[i].a;
    }
    return results;
}
  

В качестве альтернативы, если нам нужно не значение с плавающей запятой, можно использовать GetRawTextureData для последующего преобразования байтов в новый тип с помощью System.BitConverter, который дает некоторую гибкость в отношении данных, которые вы передаете из шейдера (например, если ваш фрагментный шейдер выводит half4). Однако, если вам нужен float, первый метод лучше.

  float[] DecodeFloatTexture()
{
    Texture2D decTex = new Texture2D(resultBuffer.width, resultBuffer.height, TextureFormat.RGBAFloat, false);
    RenderTexture.active = resultBuffer;
    decTex.ReadPixels(new Rect(0, 0, resultBuffer.width, resultBuffer.height), 0, 0);
    decTex.Apply();
    RenderTexture.active = null;
    byte[] bytes = decTex.GetRawTextureData();
    float[] results = new float[resultBuffer.width * resultBuffer.height];
    for (int i = 0; i < results.Length; i  )
    {
        int byteIndex = i * 4;
        byte[] localBytes = new byte[] { bytes[i], bytes[i   1], bytes[i   2], bytes[i   3] }; // converts 4 bytes to a float
        results[i] = System.BitConverter.ToSingle(localBytes, 0);
    }
    return results;
}