Как мне конвертировать между float и vec4,vec3,vec2?

Этот вопрос очень связан с вопросом здесь ( Как мне преобразовать значение vec4 rgba в число с плавающей точкой?).

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

  • нормированный поплавок без знака
  • нормированный поплавок
  • поплавок со знаком (плавающее значение я могу найти ограничение диапазона)
  • беззнаковый поплавок
  • беззнаковый поплавок
  • подписанный поплавок

Тем не менее, это всего лишь 2 случая на самом деле. Другая упаковка / распаковка может быть обработана этими двумя способами.

  • беззнаковый поплавок (я могу упаковать / распаковать простым переносом битов)
  • подписанный поплавок

Я хочу также упаковать и распаковать подписанные плавающие значения в vec3 или vec2.

В моем случае плавающее значение не гарантируется для нормализации, поэтому я не могу использовать простой способ сдвига битов.

3 ответа

Решение

Если вам известен максимальный диапазон значений, которые вы хотите сохранить, скажем, от +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(zeroToOne, 256.0), zeroToOne / 256.0);
}

Чтобы положить его обратно, вы делаете наоборот. Соберите части, разделите, чтобы вернуться к значению zeroToOne, затем увеличьте диапазон.

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(zeroToOne, 256.0), mod(zeroToOne / 256.0, 256.0), zeroToOne / 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;
}

Несколько дней назад я написал небольшой пример с шадертой: https://www.shadertoy.com/view/XdK3Dh

Он хранит float как RGB или float из пикселя. Существует также проверка, что функции являются точными инверсиями (во многих других функциях, которые я видел, в некоторых диапазонах есть ошибка из-за плохой точности).

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

Еще одна вещь, значения, которые переполняются, будут округлены до минимального / максимального значения.

Чтобы упаковать значение с плавающей запятой в vec2, vec3 или же vec4либо диапазон значений источника должен быть ограничен и хорошо задан, либо показатель также должен быть сохранен каким-либо образом.
В общем, если значащие цифры числа с плавающей запятой должны быть упакованы в байтах, последовательно 8-битные пакеты должны быть извлечены из значащих цифр и должны быть сохранены в байте.


Кодировать число с плавающей запятой в ограниченном и предопределенном диапазоне

Диапазон значений [minVal, maxVal] должен быть определен, который включает в себя все значения, которые должны быть закодированы, а диапазон значений должен быть сопоставлен с диапазоном от [0.0, 1.0].

Кодирование числа с плавающей запятой в диапазоне [minVal, maxVal] к vec2, vec3 а также vec4:

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

Расшифровка vec2, vec3 а также vec4 к числу с плавающей запятой в диапазоне [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 имеет только 24 значащих цифры, вполне достаточно кодировать число в 3 байта.


Кодировать значащие цифры и показатель степени числа с плавающей запятой

Кодирование значащих цифр числа с плавающей запятой и его показатель степени в vec2, vec3 а также vec4:

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

Расшифровка vec2, vec3 а также vec4 чтобы он значил цифры числа с плавающей запятой и его показатель степени:

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


Смотрите также ответы на следующие вопросы:

Я протестировал решение gman и обнаружил, что коэффициент масштабирования был неправильным, и это приводило к ошибкам округления, и необходимо дополнительное деление на 255.0, если вы хотите сохранить результат в текстуре RGB. Итак, это мое исправленное решение:

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

Пример:

  • Если вы упаковываете 0,25, используя min=0 и max=1, вы получите (1,0, 1,0, 0,247059)
  • Если распаковать этот вектор, получится 0,249999970197678
Другие вопросы по тегам