如何将vec4 rgba值转换为浮点值?

时间:2011-08-14 21:51:32

标签: opengl-es webgl

我在纹理中打包了一些浮点数据作为unsigned_byte,这是我在webgl中的唯一选择。现在我想在顶点着色器中解压缩它。当我采样一个像素时,我得到一个vec4,它实际上是我的一个浮点数。如何从vec4转换为浮动?

5 个答案:

答案 0 :(得分:13)

以下代码专门针对使用OpenGL ES 2.0的iPhone 4 GPU。我没有使用WebGL的经验,因此我无法声称知道代码在该上下文中的工作方式。此外,这里的主要问题是highp float不是32位而是24位。

我的解决方案是片段着色器 - 我没有在顶点着色器中尝试它,但它不应该有任何不同。为了使用你需要从sampler2d统一得到RGBA纹素,并确保每个R,G,B和A通道的值在0.0到255.0之间。这很容易实现如下:

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

您应该知道,机器的字节顺序将决定字节的正确顺序。上面的代码假定浮点数以big-endian顺序存储。如果您看到结果错误,则只需通过编写

来交换数据的顺序
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精度的一些链接。 Link1Link2Link3

答案 1 :(得分:3)

Twerdster在他的回答中发布了一些优秀的代码。所以所有的功劳归于他。我发布这个新的答案,因为评论不允许很好的语法彩色代码块,我想分享一些代码。但是如果您喜欢这些代码,请提交Twerdster原始答案。

在Twerdster上一篇文章中,他提到解码和编码可能不适用于所有值。

为了进一步测试这个,并验证结果我做了一个java程序。在移植代码时,我试图尽可能接近着色器代码(因此我实现了一些辅助函数)。 注意:我还使用存储/加载函数来模拟从纹理写入/读取时发生的情况。

我发现了:

  1. 您需要一个零
  2. 的特殊情况
  3. 您可能还需要无限的特殊情况,但我没有实现这一点以保持着色器简单(例如:更快)
  4. 因为舍入错误,有时结果是错误的:
    • 从指数中减去1,因为舍入尾数未正确归一化(例如尾数<1)
    • float Mantissa = (exp2(- Exponent) * F);更改为float Mantissa = F/exp2(Exponent);以减少精度错误
    • 使用float Exponent = floor(log2(F));计算指数。 (通过新的尾数检查简化)
  5. 使用这些小修改,我几乎在所有输入上得到相同的输出,并且当出现问题时在原始和编码/解码值之间只有很小的误差,而在Twerdster的原始实现中,舍入误差经常导致错误的指数(因此结果是因子2)。

    请注意,这是我为测试算法而编写的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 :(得分:2)

由于你没有给我们提供你用来创建和上传纹理的确切代码,我只能猜测你正在做什么。

您似乎正在创建一个浮点数的JavaScript数组。然后创建一个Uint8Array,将该数组传递给构造函数。

根据WebGL规范(或者更确切地说,WebGL规范在表面上指定此行为时引用的规范),从浮点数到无符号字节的转换以两种方式之一发生,基于目标。如果目标被视为“钳制”,则它会将数字钳位到目标范围,即[0,255]。如果目的地不被视为“钳制”,那么它取模2 8 。 WebGL“规范”足够差,以至于Uint8Array的结构是否被认为是钳制的并不完全清楚。无论是钳位还是取模2 8 ,小数点都会被截断并存储整数值。

但是,当您将此数据提供给打开 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为规范。它几乎没有指明任何东西;它只是对其他规范的一堆引用,这使得找到任何非常困难的效果。

答案 3 :(得分:2)

我尝试了Arjans解决方案,但是它返回了0,1,2,4的无效值。指数的打包存在一个错误,我改变了所以exp需要一个8位浮点数并且符号包含了尾数:

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

答案 4 :(得分:0)

您将无法在着色器中将4个无符号字节解释为浮点值(我假设您想要)的位(我认为至少不在GLES或WebGL中)。您可以做的不是将浮点数的位表示存储在4 ubytes中,而是存储尾数(或固定点表示)的位。为此你需要知道浮动的大致范围(为简单起见,我假设[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);

虽然我不确定这是否会产生完全相同的值,但两者的力量应该有所帮助。