如何在float、vec4、vec3、vec2之间进行转换?

4
这个问题与这里的问题(如何将vec4 rgba值转换为float?)非常相关。
已经有一些与此问题相关的文章或问题,但我想大多数文章都没有确定浮点值的类型。 据我所知,下面是一些浮点值打包/解包公式。
  • 无符号归一化浮点数
  • 有符号归一化浮点数
  • 有符号范围浮点数(我可以找到浮点值的范围限制)
  • 无符号范围浮点数
  • 无符号浮点数
  • 有符号浮点数
然而,实际上只有这两种情况。其他的打包/解包可以通过这两种方法处理。
  • 无符号范围浮点数(我可以通过简单的位移打包/解包)
  • 有符号浮点数
我还想将有符号浮点值打包和解包成vec3或vec2。
对于我的情况,浮点值不能保证被归一化,因此我不能使用简单的位移方法。

我现在有些糊涂了,所以确认一下:您提供了一个GLfloat作为属性,并希望它成为两个或三个其他浮点值的包,然后需要对其进行解包?因此,您的值在glVertexAttributePointer中不是标准化的,这并不意味着您创建了一个未经标准化的浮点数(在尾数/指数方面)?如果是这样,那么在CPU领域中原始向量是否存在任何已知的范围限制? - Tommy
如果存在范围限制,我可以通过将范围除以来进行归一化,并且我可以使用相同的打包/解包方式来处理归一化的浮点值。 但是,在我的情况下,实际上没有限制...因此需要使用尾数/指数等方法来实现它。 - kyasbal
4个回答

9

如果你知道想要存储的数值范围(例如+5到-5),那么最简单的方法就是选择某些将该范围转换为0到1之间值的转换方式。扩展该范围到您拥有的比特数,然后将其分成部分。

vec2 packFloatInto8BitVec2(float v, float min, float max) {
   float zeroToOne = (v - min) / (max - min);
   float zeroTo16Bit = zeroToOne * 256.0 * 255.0;
   return vec2(mod(zeroTo16Bit, 256.0), zeroTo16Bit / 256.0);
}

要将其放回,请执行相反操作。组装零件,除以得到零到一之间的值,然后通过范围进行扩展。

float unpack8BitVec2IntoFloat(vec2 v, float min, float max) {
   float zeroTo16Bit = v.x + v.y * 256.0;
   float zeroToOne = zeroTo16Bit / 256.0 / 255.0;
   return zeroToOne * (max - min) + min;
}

对于vec3,只需将其扩展即可。

vec3 packFloatInto8BitVec3(float v, float min, float max) {
   float zeroToOne = (v - min) / (max - min);
   float zeroTo24Bit = zeroToOne * 256.0 * 256.0 * 255.0;
   return vec3(mod(zeroTo24Bit, 256.0), mod(zeroTo24Bit / 256.0, 256.0), zeroTo24Bit / 256.0 / 256.0);
}

float unpack8BitVec3IntoFloat(vec3 v, float min, float max) {
   float zeroTo24Bit = v.x + v.y * 256.0 + v.z * 256.0 * 256.0;
   float zeroToOne = zeroTo24Bit / 256.0 / 256.0 / 256.0;
   return zeroToOne * (max - min) + min;
}

在WebGL 1中,百分号(%)是有效的运算符吗?我猜它只能在WebGL 2(GLSL ES 3.00)中使用。 - Entity Black
我猜如果我用mod重写%,这段代码就能很好地工作。 但是,这只适用于我可以找到范围的变量。 - kyasbal
1
mod和%只是一个脑抽。感谢指出。在ES 3.0中,有内置的打包/解包函数以及保证浮点目标(不需要打包)的功能。至于不知道范围,你会如何建议将32位浮点数打包到16位vec2中而不丢失任何东西? - gman
2
packFloatInto... 函数中有两个错别字: 在返回语句中应该使用 zeroTo16BitzeroTo24Bit - Thomas Fischer

5

几天前我用shadertoy写了一个小例子:

https://www.shadertoy.com/view/XdK3Dh

它将浮点数存储为RGB或从像素中读取浮点数。还有一个测试函数是精确反函数(我看到的许多其他函数由于精度不足在某些范围内存在错误)。

整个示例假定您想要在缓冲区中保存值并在下一次绘制时读取它。只有256种颜色,这限制了您可以获得16777216个不同的值。大部分时间我不需要更大的比例。我还将其转换为带符号的浮点数,而不是区间<-8388608;8388608>中的无符号浮点数。

float color2float(in vec3 c) {
    c *= 255.;
    c = floor(c); // without this value could be shifted for some intervals
    return c.r*256.*256. + c.g*256. + c.b - 8388608.;
}

// values out of <-8388608;8388608> are stored as min/max values
vec3 float2color(in float val) {
    val += 8388608.; // this makes values signed
    if(val < 0.) {
        return vec3(0.);
    }
    if(val > 16777216.) {
        return vec3(1.);
    }
    vec3 c = vec3(0.);
    c.b = mod(val, 256.);
    val = floor(val/256.);
    c.g = mod(val, 256.);
    val = floor(val/256.);
    c.r = mod(val, 256.);
    return c/255.;
}

另外需要注意的是,数值溢出将会被舍入为最小/最大值。


3
为了在vec2vec3vec4中打包浮点值,要么必须限制和明确定义源值的范围,要么必须以某种方式存储指数。通常,如果浮点数的有效数字应该按字节打包,则必须从有效数字中提取连续的8位数据包,并将其存储在一个字节中。

将浮点数编码为受限和预定义范围内的值

必须定义一个包含所有要编码的值的值范围[minVal, maxVal],并且该值范围必须映射到从[0.0, 1.0]的范围内。
将浮点数编码为范围[minVal, maxVal]的vec2vec3vec4
vec2 EncodeRangeV2( in float value, in float minVal, in float maxVal )
{
    value        = clamp( (value-minVal) / (maxVal-minVal), 0.0, 1.0 );
    value       *= (256.0*256.0 - 1.0) / (256.0*256.0);
    vec3 encode  = fract( value * vec3(1.0, 256.0, 256.0*256.0) );
    return encode.xy - encode.yz / 256.0 + 1.0/512.0;
}

vec3 EncodeRangeV3( in float value, in float minVal, in float maxVal )
{
    value        = clamp( (value-minVal) / (maxVal-minVal), 0.0, 1.0 );
    value       *= (256.0*256.0*256.0 - 1.0) / (256.0*256.0*256.0);
    vec4 encode  = fract( value * vec4(1.0, 256.0, 256.0*256.0, 256.0*256.0*256.0) );
    return encode.xyz - encode.yzw / 256.0 + 1.0/512.0;
}

vec4 EncodeRangeV4( in float value, in float minVal, in float maxVal )
{
    value        = clamp( (value-minVal) / (maxVal-minVal), 0.0, 1.0 );
    value       *= (256.0*256.0*256.0 - 1.0) / (256.0*256.0*256.0);
    vec4 encode  = fract( value * vec4(1.0, 256.0, 256.0*256.0, 256.0*256.0*256.0) );
    return vec4( encode.xyz - encode.yzw / 256.0, encode.w ) + 1.0/512.0;
}

vec2vec3vec4解码为范围在[minVal, maxVal]内的浮点数:

float DecodeRangeV2( in vec2 pack, in float minVal, in float maxVal )
{
    float value  = dot( pack, 1.0 / vec2(1.0, 256.0) );
    value       *= (256.0*256.0) / (256.0*256.0 - 1.0);
    return mix( minVal, maxVal, value );
}

float DecodeRangeV3( in vec3 pack, in float minVal, in float maxVal )
{
    float value  = dot( pack, 1.0 / vec3(1.0, 256.0, 256.0*256.0) );
    value       *= (256.0*256.0*256.0) / (256.0*256.0*256.0 - 1.0);
    return mix( minVal, maxVal, value );
}

float DecodeRangeV4( in vec4 pack, in float minVal, in float maxVal )
{
    float value  = dot( pack, 1.0 / vec4(1.0, 256.0, 256.0*256.0, 256.0*256.0*256.0) );
    value       *= (256.0*256.0*256.0) / (256.0*256.0*256.0 - 1.0);
    return mix( minVal, maxVal, value );
}

注意,由于标准的32位[IEEE 754][2]数字只有24个有效数字,因此在3个字节中编码数字完全足够。


将浮点数的有效数字和指数进行编码

将浮点数的有效数字和指数编码为vec2vec3vec4

vec2 EncodeExpV2( in float value )
{
    int exponent  = int( log2( abs( value ) ) + 1.0 );
    value        /= exp2( float( exponent ) );
    value         = (value + 1.0) * 255.0 / (2.0*256.0);
    vec2 encode   = fract( value * vec2(1.0, 256.0) );
    return vec2( encode.x - encode.y / 256.0 + 1.0/512.0, (float(exponent) + 127.5) / 256.0 );
}

vec3 EncodeExpV3( in float value )
{
    int exponent  = int( log2( abs( value ) ) + 1.0 );
    value        /= exp2( float( exponent ) );
    value         = (value + 1.0) * (256.0*256.0 - 1.0) / (2.0*256.0*256.0);
    vec3 encode   = fract( value * vec3(1.0, 256.0, 256.0*256.0) );
    return vec3( encode.xy - encode.yz / 256.0 + 1.0/512.0, (float(exponent) + 127.5) / 256.0 );
}

vec4 EncodeExpV4( in float value )
{
    int exponent  = int( log2( abs( value ) ) + 1.0 );
    value        /= exp2( float( exponent ) );
    value         = (value + 1.0) * (256.0*256.0*256.0 - 1.0) / (2.0*256.0*256.0*256.0);
    vec4 encode   = fract( value * vec4(1.0, 256.0, 256.0*256.0, 256.0*256.0*256.0) );
    return vec4( encode.xyz - encode.yzw / 256.0 + 1.0/512.0, (float(exponent) + 127.5) / 256.0 );
}

vec2vec3vec4 解码为浮点数的有效数字和指数:
float DecodeExpV2( in vec2 pack )
{
    int exponent = int( pack.z * 256.0 - 127.0 );
    float value  = pack.x * (2.0*256.0) / 255.0 - 1.0;
    return value * exp2( float(exponent) );
}

float DecodeExpV3( in vec3 pack )
{
    int exponent = int( pack.z * 256.0 - 127.0 );
    float value  = dot( pack.xy, 1.0 / vec2(1.0, 256.0) );
    value        = value * (2.0*256.0*256.0) / (256.0*256.0 - 1.0) - 1.0;
    return value * exp2( float(exponent) );
}

float DecodeExpV4( in vec4 pack )
{
    int exponent = int( pack.w * 256.0 - 127.0 );
    float value  = dot( pack.xyz, 1.0 / vec3(1.0, 256.0, 256.0*256.0) );
    value        = value * (2.0*256.0*256.0*256.0) / (256.0*256.0*256.0 - 1.0) - 1.0;
    return value * exp2( float(exponent) );
}


请参考以下问题的答案:


1
"flaot" -> "浮点数" - gunfulker
1
下一个参数缺少整数和浮点数。 - gunfulker

1
我测试了gman的解决方案,发现比例因子不正确,并且会产生舍入误差,如果要将结果存储在RGB纹理中,还需要除以255.0。所以这是我的修订方案:
#define SCALE_FACTOR (256.0 * 256.0 * 256.0 - 1.0)

vec3 packFloatInto8BitVec3(float v, float min, float max) {
   float zeroToOne = (v - min) / (max - min);
   float zeroTo24Bit = zeroToOne * SCALE_FACTOR;
   return floor(
        vec3(
            mod(zeroTo24Bit, 256.0),
            mod(zeroTo24Bit / 256.0, 256.0),
            zeroTo24Bit / 256.0 / 256.0
        )
    ) / 255.0;
}

float unpack8BitVec3IntoFloat(vec3 v, float min, float max) {
   vec3 scaleVector = vec3(1.0, 256.0, 256.0 * 256.0) / SCALE_FACTOR * 255.0;
   float zeroToOne = dot(v, scaleVector);
   return zeroToOne * (max - min) + min;
}

示例:

  • 如果您使用min=0和max=1打包0.25,您将获得(1.0, 1.0, 0.247059)
  • 如果您解压该向量,您将得到0.249999970197678

我的解决方案也会产生舍入误差。该死! - Matthias Bohlen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接