我正在寻找并行化一些复杂的数学,而 webgl 看起来是实现它的完美方式。问题是,您只能从纹理中读取 8 位整数。理想情况下,我希望从纹理中获得 32 位数字。我的想法是使用 4 个颜色通道来获得每像素 32 位,而不是 4 乘以 8 位。
我的问题是,glsl 没有“%”运算符或任何位运算符!
TLDR:如何使用 glsl 中的运算符将 32 位数字转换为 4 个 8 位数字。
有关该技术的一些额外信息(使用按位运算符):
我正在寻找并行化一些复杂的数学,而 webgl 看起来是实现它的完美方式。问题是,您只能从纹理中读取 8 位整数。理想情况下,我希望从纹理中获得 32 位数字。我的想法是使用 4 个颜色通道来获得每像素 32 位,而不是 4 乘以 8 位。
我的问题是,glsl 没有“%”运算符或任何位运算符!
TLDR:如何使用 glsl 中的运算符将 32 位数字转换为 4 个 8 位数字。
有关该技术的一些额外信息(使用按位运算符):
您可以通过乘以/除以 2 的幂来进行位移。
正如评论中所指出的,我最初发布的方法有效但不正确,这是 Aras Pranckevičius 的方法,请注意帖子中的源代码本身包含一个错字并且是 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] 范围内的浮点值打包到 a 中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
从 a 中提取 [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 );
}
另一种可能性是将有效数字编码为 RGB 值的 3 * 8 位,并将指数编码为 alpha 通道的 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 位的两个值进行一些比较:
// Generate a list of random 16bit integers
let data16bit = new Uint16Array(1000000);
for(let i=0; i < data16bit.length; i+=2){
data16bit[i] = Math.random()*(2**16);
data16bit[i+1] = Math.random()*(2**16);
}
// Read them one byte at a time, for writing to
// WebGL
let texture = new Uint8Array(data16bit.buffer);
现在,当您在片段着色器中获取值时,您可以获取数字进行操作:
vec4 here = texture2D(u_image, v_texCoord);
// Read the "red" byte and the "green" byte together (as a single thing)
// as well as the "blue" byte and the "alpha" byte together as a single
// thing
vec2 a = here.rg;
vec2 b = here.ba;
// now compare the things
if(a == b){
here.a = 1;
}
else{
here.a = 0;
}
// return the boolean value
gl_FragColor = here;
要点只是提醒您,您可以将同一块 JavaScript 内存视为不同大小:Uint16Array
, 和Uint8Array
(而不是尝试进行位移和分解)。
为了响应更多详细信息的请求,该代码非常接近直接从此代码和说明中剪切/粘贴。
可以在GitLab上的相应示例中找到它的确切用法(同一文件的两个部分)