Как упаковать один 32-битный int в 4, 8-битный int в glsl / webgl?

Я пытаюсь распараллелить некоторую сложную математику, и webgl выглядит как идеальный способ сделать это. Проблема в том, что вы можете читать только 8-битные целые числа из текстур. В идеале я хотел бы получить 32-битные числа из текстуры. У меня была идея использовать 4 цветовых канала для получения 32 бит на пиксель вместо 4 раз по 8 бит.

Моя проблема в том, что у glsl нет оператора "%" или любого побитового оператора!

TLDR: как преобразовать 32-битное число в 4 8-битное число с помощью операторов в glsl.

Некоторая дополнительная информация о технике (с использованием побитовых операторов):

Как сохранить 64-битное целое число в двух 32-битных целых и преобразовать обратно

2 ответа

Решение

Вы можете сдвинуть биты путем умножения / деления на степени двух.

Как отмечено в комментариях, подход, который я первоначально опубликовал, работал, но неверный, вот один из них Араса Пранккявичюса, обратите внимание, что исходный код в посте содержит опечатку и является HLSL, это порт GLSL с исправленной опечаткой:

const vec4 bitEnc = vec4(1.,255.,65025.,16581375.);
const vec4 bitDec = 1./bitEnc;
vec4 EncodeFloatRGBA (float v) {
    vec4 enc = bitEnc * v;
    enc = fract(enc);
    enc -= enc.yzww * vec2(1./255., 0.).xxxy;
    return enc;
}
float DecodeFloatRGBA (vec4 v) {
    return dot(v, bitDec);
}

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

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

Чтобы упаковать значение с плавающей запятой в 4 * 8-битные буферы, сначала необходимо указать диапазон значений источника.
Если вы определили диапазон значений [minVal, maxVal], он должен быть сопоставлен с диапазоном [0.0, 1.0]:

float mapVal = clamp((value-minVal)/(maxVal-minVal), 0.0, 1.0);

Функция Encode упаковывает значение с плавающей запятой в диапазоне [0.0, 1.0] в vec4:

vec4 Encode( in float value )
{
    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;
}

Функция Decode извлекает значение с плавающей запятой в диапазоне [0.0, 1.0] из vec4:

float Decode( in vec4 pack )
{
    float value = dot( pack, 1.0 / vec4(1.0, 256.0, 256.0*256.0, 256.0*256.0*256.0) );
    return value * (256.0*256.0*256.0) / (256.0*256.0*256.0 - 1.0);
}

Следующие функции упаковывают и извлекают значение с плавающей запятой в и из диапазона [minVal, maxVal]:

vec4 EncodeRange( in float value, flaot minVal, 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;
}

float DecodeRange( in vec4 pack, flaot minVal, maxVal )
{
    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 );
}

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

Другая возможность состоит в том, чтобы закодировать значащие цифры в 3 * 8 битов значений RGB и экспоненту в 8 битов альфа-канала:

vec4 EncodeExp( 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 );
}

float DecodeExp( 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) );
}

Обратите внимание, поскольку стандартное 32-разрядное число IEEE 754 имеет только 24 значащих цифры, вполне достаточно кодировать число в 3 байта.

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

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

Предполагая, что вы хотите провести некоторое сравнение двух значений, которые умещаются в 16 бит:

let data16bit = new Uint16Array(1000000);
for(let i=0; i < data16bit.length; i+=2){
    data16[i]   = Math.random()*(2**16);
    data16[i+1] = Math.random()*(2**16);
}
let texture = new Uint8Array(data16bit.buffer);

Теперь, когда вы получите свои значения в своем фрагментном шейдере, вы можете подобрать числа для манипуляции:

vec4 here = texture2D(u_image, v_texCoord);
vec2 a = here.rg;
vec2 b = here.ba;
if(a == b){
    here.a = 1;
}
else{
    here.a = 0;
}
gl_FragColor = here;

Это просто напоминание о том, что вы можете обращаться с памятью JavaScript как с разными размерами (вместо того, чтобы пытаться сдвигать биты и разбивать их).

Другие вопросы по тегам