Как преобразовать значение vec4 rgba в число с плавающей точкой? - PullRequest
8 голосов
/ 15 августа 2011

Я упаковал некоторые данные с плавающей точкой в ​​текстуру как unsigned_byte, мой единственный вариант в webgl.Теперь я хотел бы распаковать его в вершинный шейдер.Когда я пробую пиксель, я получаю vec4, который действительно является одним из моих поплавков.Как мне конвертировать из vec4 в float?

Ответы [ 5 ]

13 голосов
/ 30 августа 2011

Следующий код предназначен специально для графического процессора iPhone 4, использующего OpenGL ES 2.0.У меня нет опыта работы с WebGL, поэтому я не могу утверждать, что знаю, как код будет работать в этом контексте.Кроме того, основная проблема здесь в том, что highp float не 32-битный, а 24-битный.

Мое решение для фрагментных шейдеров - я не пробовал его в вершинном шейдере, но он не должен отличаться.Для использования вам необходимо получить тексель RGBA из формы sampler2d и убедиться, что значения каждого канала R, G, B и A находятся в диапазоне от 0,0 до 255,0.Этого легко добиться следующим образом:

highp vec4 rgba = texture2D(textureSamplerUniform, texcoordVarying)*255.0;

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

rgba.rgba=rgba.abgr;

сразу после строки, в которой вы их установили.Или же поменяйте местами индексы на rgba.Я думаю, что приведенная выше строка более интуитивна и менее подвержена небрежным ошибкам.Я не уверен, что это работает для всех данных ввода.Я проверил большой диапазон чисел и обнаружил, что decode32 и encode32 НЕ являются точными противоположностями.Я также пропустил код, который использовал для его проверки.

#pragma STDGL invariant(all) 

highp vec4 encode32(highp float f) {
    highp float e =5.0;

    highp float F = abs(f); 
    highp float Sign = step(0.0,-f);
    highp float Exponent = floor(log2(F)); 
    highp float Mantissa = (exp2(- Exponent) * F);
    Exponent = floor(log2(F) + 127.0) + floor(log2(Mantissa));
    highp vec4 rgba;
    rgba[0] = 128.0 * Sign  + floor(Exponent*exp2(-1.0));
    rgba[1] = 128.0 * mod(Exponent,2.0) + mod(floor(Mantissa*128.0),128.0);  
    rgba[2] = floor(mod(floor(Mantissa*exp2(23.0 -8.0)),exp2(8.0)));
    rgba[3] = floor(exp2(23.0)*mod(Mantissa,exp2(-15.0)));
    return rgba;
}

highp float decode32(highp vec4 rgba) {
    highp float Sign = 1.0 - step(128.0,rgba[0])*2.0;
    highp float Exponent = 2.0 * mod(rgba[0],128.0) + step(128.0,rgba[1]) - 127.0; 
    highp float Mantissa = mod(rgba[1],128.0)*65536.0 + rgba[2]*256.0 +rgba[3] + float(0x800000);
    highp float Result =  Sign * exp2(Exponent) * (Mantissa * exp2(-23.0 )); 
    return Result;
}

void main()  
{  
    highp float result;

    highp vec4 rgba=encode32(-10.01);
    result = decode32(rgba);
}

Вот некоторые ссылки на точность IEEE, которые я нашел полезными. Link1 . Link2 . LINK3 .

3 голосов
/ 22 июня 2012

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

В предыдущем посте Twerdster он упомянул, что декодирование и кодирование могут работать не для всех значений.

Для дальнейшей проверки и проверкиВ результате я сделал Java-программу.При переносе кода я старался максимально приблизиться к коду шейдера (поэтому я реализовал некоторые вспомогательные функции).Примечание: я также использую функцию сохранения / загрузки, чтобы схожи с тем, что происходит, когда вы пишете / читаете из текстуры.

Я обнаружил, что:

  1. Вам нужен особый случай для нуля
  2. Вам также может понадобиться особый случай для бесконечности, но я не реализовал это длясделайте шейдер простым (например: быстрее)
  3. Из-за ошибок округления иногда результат был неверным, поэтому:
    • вычитайте 1 из показателя степени, если из-за округления мантисса не нормализована должным образом (например, мантисса <1) </li>
    • Измените float Mantissa = (exp2(- Exponent) * F); на float Mantissa = F/exp2(Exponent);, чтобы уменьшить погрешности точности
    • Используйте float Exponent = floor(log2(F)); для вычисления показателя степени.(упрощено новой проверкой мантиссы)

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

Обратите внимание, что это тестовое приложение Java, которое я написал для проверки алгоритма,Я надеюсь, что это также будет работать при портировании на GPU.Если кто-нибудь попытается запустить его на GPU, пожалуйста, оставьте комментарий с вашим опытом.

А для кода с простым тестом пробуйте разные числа, пока он не даст сбой.

import java.io.PrintStream;
import java.util.Random;

public class BitPacking {

    public static float decode32(float[] v)
    {
        float[] rgba = mult(255, v);
        float sign = 1.0f - step(128.0f,rgba[0])*2.0f;
        float exponent = 2.0f * mod(rgba[0],128.0f) + step(128.0f,rgba[1]) - 127.0f;    
        if(exponent==-127)
            return 0;           
        float mantissa = mod(rgba[1],128.0f)*65536.0f + rgba[2]*256.0f +rgba[3] + ((float)0x800000);
        return sign * exp2(exponent-23.0f) * mantissa ;     
    }   

    public static float[] encode32(float f) {           
        float F = abs(f); 
        if(F==0){
            return new float[]{0,0,0,0};
        }
        float Sign = step(0.0f,-f);
        float Exponent = floor(log2(F)); 

        float Mantissa = F/exp2(Exponent); 

        if(Mantissa < 1)
            Exponent -= 1;      

        Exponent +=  127;

        float[] rgba = new float[4];
        rgba[0] = 128.0f * Sign  + floor(Exponent*exp2(-1.0f));
        rgba[1] = 128.0f * mod(Exponent,2.0f) + mod(floor(Mantissa*128.0f),128.0f);  
        rgba[2] = floor(mod(floor(Mantissa*exp2(23.0f -8.0f)),exp2(8.0f)));
        rgba[3] = floor(exp2(23.0f)*mod(Mantissa,exp2(-15.0f)));
        return mult(1/255.0f, rgba);
    }

    //shader build-in's

    public static float exp2(float x){
        return (float) Math.pow(2, x);
    }

    public static float[] step(float edge, float[] x){
        float[] result = new float[x.length];
        for(int i=0; i<x.length; i++)
            result[i] = x[i] < edge ? 0.0f : 1.0f;
        return result;      
    }

    public static float step(float edge, float x){
        return x < edge ? 0.0f : 1.0f;
    }

    public static float mod(float x, float y){
        return x-y * floor(x/y);
    }

    public static float floor(float x){
        return (float) Math.floor(x);
    }

    public static float pow(float x, float y){
        return (float)Math.pow(x, y);
    }

    public static float log2(float x)
    {
        return (float) (Math.log(x)/Math.log(2));
    }

    public static float log10(float x)
    {
        return (float) (Math.log(x)/Math.log(10));
    }

    public static float abs(float x)
    {
        return (float)Math.abs(x);
    }   

    public static float log(float x)
    {
        return (float)Math.log(x);
    }

    public static float exponent(float x)
    {
        return floor((float)(Math.log(x)/Math.log(10)));
    }

    public static float mantissa(float x)
    {
        return floor((float)(Math.log(x)/Math.log(10)));
    }

    //shorter matrix multiplication 
    private static float[] mult(float scalar, float[] w){
        float[] result = new float[4];
        for(int i=0; i<4; i++)
            result[i] = scalar * w[i];
        return result;
    }

    //simulate storage and retrieval in 4-channel/8-bit texture 
    private static float[] load(int[] v)
    {
        return new float[]{v[0]/255f, v[1]/255f, v[2]/255f, v[3]/255f};
    }

    private static int[] store(float[] v)
    {
        return new int[]{((int) (v[0]*255))& 0xff, ((int) (v[1]*255))& 0xff, ((int) (v[2]*255))& 0xff, ((int) (v[3]*255))& 0xff};       
    }

    //testing until failure, and some specific hard-cases separately
    public static void main(String[] args) {

        //for(float v : new float[]{-2097151.0f}){ //small error here 
        for(float v : new float[]{3.4028233e+37f, 8191.9844f, 1.0f, 0.0f, 0.5f, 1.0f/3, 0.1234567890f, 2.1234567890f, -0.1234567890f, 1234.567f}){
            float output = decode32(load(store(encode32(v))));
            PrintStream stream = (v==output) ?  System.out : System.err;
            stream.println(v + " ?= " + output);
        }   

        //System.exit(0);

        Random r = new Random();
        float max = 3200000f;
        float min = -max;
        boolean error = false;
        int trials = 0;
        while(!error){
            float fin = min + r.nextFloat() * ((max - min) + 1);
            float fout = decode32(load(store(encode32(fin))));
            if(trials % 10000 == 0)
                System.out.print('.');
            if(trials % 1000000 == 0)
                System.out.println();
            if(fin != fout){
                System.out.println();
                System.out.println("correct trials = " + trials);
                System.out.println(fin + " vs " + fout);                
                error = true;
            }
            trials++;
        }       
    }
}
2 голосов
/ 06 февраля 2013

Я попробовал решение Arjans, но оно вернуло недопустимые значения для 0, 1, 2, 4. Была ошибка с упаковкой экспоненты, которую я изменил, так что exp занимает один 8-битный float, а знак упакован с мантисса:

//unpack a 32bit float from 4 8bit, [0;1] clamped floats
float unpackFloat4( vec4 _packed)
{
    vec4 rgba = 255.0 * _packed;
    float sign =  step(-128.0, -rgba[1]) * 2.0 - 1.0;
    float exponent = rgba[0] - 127.0;    
    if (abs(exponent + 127.0) < 0.001)
        return 0.0;           
    float mantissa =  mod(rgba[1], 128.0) * 65536.0 + rgba[2] * 256.0 + rgba[3] + (0x800000);
    return sign *  exp2(exponent-23.0) * mantissa ;     


}

//pack a 32bit float into 4 8bit, [0;1] clamped floats
vec4 packFloat(float f) 
{
    float F = abs(f); 
    if(F == 0.0)
    {
        return  vec4(0,0,0,0);
    }
    float Sign =  step(0.0, -f);
    float Exponent = floor( log2(F)); 

    float Mantissa = F/ exp2(Exponent); 
    //std::cout << "  sign: " << Sign << ", exponent: " << Exponent << ", mantissa: " << Mantissa << std::endl;
    //denormalized values if all exponent bits are zero
    if(Mantissa < 1.0)
        Exponent -= 1;      

    Exponent +=  127;

    vec4 rgba;
    rgba[0] = Exponent;
    rgba[1] = 128.0 * Sign +  mod(floor(Mantissa * float(128.0)),128.0);
    rgba[2] = floor( mod(floor(Mantissa* exp2(float(23.0 - 8.0))), exp2(8.0)));
    rgba[3] = floor( exp2(23.0)* mod(Mantissa, exp2(-15.0)));
    return (1 / 255.0) * rgba;
}
2 голосов
/ 15 августа 2011

Поскольку вы не удосужились дать нам точный код, который вы использовали для создания и загрузки своей текстуры, я могу только догадываться о том, что вы делаете.

Похоже, вы создаете массив JavaScript счисла с плавающей точкой.Затем вы создаете Uint8Array, передавая этот массив конструктору.

Согласно спецификации WebGL (точнее, спецификации, на которую ссылается спецификация WebGL, когда якобы указываете это поведение), преобразование из числа с плавающей запятой в байты без знака происходит одним из двух способов в зависимости от места назначения.Если пункт назначения считается «зажатым», он фиксирует номер в диапазоне пункта назначения, а именно [0, 255] для вашего случая.Если пункт назначения не считается «зажатым», то он берется по модулю 2 8 .«Спецификация» WebGL достаточно плохая, поэтому не совсем понятно, считается ли конструкция Uint8Array фиксированной или нет.Зафиксировано ли оно или взято по модулю 2 8 , десятичная точка обрезается и сохраняется целочисленное значение.

Однако, когда вы передаете эти данные Open WebGL, высказал WebGL интерпретировать байты как нормализованные целочисленные значения без знака.Это означает, что входные значения в диапазоне [0, 255] будут доступны пользователям текстуры как значения [0, 1] с плавающей запятой.

Так что, если ваш входной массив имеет значение 183.45, значениев Uint8Array будет 183. Значение в текстуре будет 183/255 или 0,718.Если ваше входное значение было 0,45, Uint8Array будет содержать 0, а результат текстуры будет 0,0.

Теперь, поскольку вы передали данные как GL_RGBA, это означает, что каждые 4 байта без знака будут приниматься как одинтекселей.Таким образом, каждый вызов texture будет извлекать эти конкретные четыре значения (по заданной координате текстуры, используя заданные параметры фильтрации), возвращая, таким образом, vec4.

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

Кстати, Khronos действительно должно быть стыдно за то, что даже назвал WebGL спецификацией.Это едва определяет что-либо;это просто набор ссылок на другие спецификации, что делает поиск эффектов чего-либо чрезвычайно трудным.

0 голосов
/ 15 августа 2011

Вы не сможете просто интерпретировать 4 неподписанных байта как биты значения с плавающей запятой (что, я полагаю, вам нужно) в шейдере (по крайней мере, не в GLES или WebGL, я думаю).То, что вы можете сделать, это не сохранить битовое представление с плавающей запятой в 4-х байтах, а биты мантиссы (или представление с фиксированной точкой).Для этого вам нужно знать приблизительный диапазон поплавков (для простоты я приму здесь [0,1], в противном случае, конечно, вам придется масштабировать по-разному):

r = clamp(int(2^8 * f), 0, 255);
g = clamp(int(2^16 * f), 0, 255);
b = clamp(int(2^24 * f), 0, 255);     //only have 24 bits of precision anyway

Конечно, вы можететакже работать напрямую с битами мантиссы.И затем в шейдере вы можете просто восстановить его таким образом, используя тот факт, что компоненты vec4 все находятся в [0,1]:

f = (v.r) + (v.g / 2^8) + (v.b / 2^16);

Хотя я не уверен, если этоприведет к точно такому же значению, здесь должны помочь две степени.

...