Как мне конвертировать между 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