【问题标题】:How do you pack one 32bit int Into 4, 8bit ints in glsl / webgl?如何在 glsl / webgl 中将一个 32 位整数打包成 4、8 位整数?
【发布时间】:2013-08-29 11:37:28
【问题描述】:

我希望并行化一些复杂的数学运算,而 webgl 看起来是实现它的完美方式。 问题是,您只能从纹理中读取 8 位整数。 理想情况下,我希望从纹理中获得 32 位数字。 我的想法是使用 4 个颜色通道来获得每像素 32 位,而不是 4 乘以 8 位。

我的问题是,glsl 没有“%”运算符或任何位运算符!

TLDR: 如何使用 glsl 中的运算符将 32 位数字转换为 4 个 8 位数字。

有关该技术的一些额外信息(使用按位运算符):

How to store a 64 bit integer in two 32 bit integers and convert back again

【问题讨论】:

  • OpenGL ES 没有按位运算符,因为图形硬件没有实现整数运算。
  • @randmat11:这根本不是真的。所有现代 GPU 都有 ALU。 OpenGL ES 没有实现按位运算符,因为旧的着色器模型没有公开该功能。从 Shader Model 4.0 (dx10) 开始,引入了位运算符。 OpenGL 3.0+ 中的 GLSL 具有按位运算符,只有 OpenGL ES 的淡化 GLSL 没有。

标签: javascript webgl glsl glsles


【解决方案1】:

您可以通过乘以/除以 2 的幂来进行位移。

正如 cmets 中所指出的,我最初发布的方法有效但不正确,here's one by 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);
}

【讨论】:

  • 这很聪明,我想知道为什么我没有想到只使用乘法来移位。
  • 与这个 glsl 代码等效的 Javascript 是什么?我需要在 javascript glsl 之间共享位数据作为纹理并且不能让它在 javascript 中工作:/
  • 这并不适合我,除非我移动了一些东西,return vec4(comp.yzw, floor(depth)) 以恢复所有四个组件。
  • 我认为这对于第一个组件来说不是很准确,因为浮点数的存储方式。假设 IEEE754 单精度,您将获得 24 位精度和 8 位指数,当您开始获得高于 2^24 (16,777,216) 的数字时,您将开始失去整数精度。因此,随着 w 的增加,您的 x 会失去精度。如果 w > 1/256,则 x 误差高达 1/256;如果 w > 1/128,则 x 误差最大为 1/128; ...如果 w > 0.25,最大 x 误差为 0.25;如果 w > 0.5,最大 x 误差为 0.5!
  • 很好的解决方案,谢谢!但是在 iOS 平台上,我必须在 DecodeFloatRGBA 函数的开头添加此代码:v = floor(v * 255.0 + 0.5) / 255.0; 以确保 v 包含精确的 0、1/255、2/255、... ,1 值。这是必要的,因为通常“vec4 v”来自 texture2D 函数,并且在某些 GPU(例如在 iOS 设备中)上,texture2D 返回例如“0.000001”而不是精确的 0.0。
【解决方案2】:

一般来说,如果要将浮点数的有效数字打包成字节,则必须连续提取有效数字的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;
}

函数Decodevec4中提取[0.0, 1.0]范围内的浮点值:

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 个字节就足够了。

另见How do I convert between float and vec4,vec3,vec2?

【讨论】:

  • 指数可以直接编码:floor(log2(abs(value))) + 127.0
【解决方案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 内存视为不同大小:Uint16ArrayUint8Array(而不是尝试进行位移和分解)。

更新

为了响应更多详细信息的请求,该代码非常接近直接来自this code and explanation 的剪切/粘贴。

这个的具体用法可以在GitLab上对应的samples找到(同一个文件的两部分)

【讨论】:

  • 您能否扩展您对运行示例的回答?
  • @mix3d 代码最初是从我正在处理的一个更大的程序中剪切/粘贴/调整的。后来我写了一个较小的演示供课堂使用。我正在链接到较小的演示。
猜你喜欢
  • 2019-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-07
  • 2012-03-16
  • 1970-01-01
相关资源
最近更新 更多