我在纹理中打包了一些浮点数据作为unsigned_byte,这是我在webgl中的唯一选择。现在我想在顶点着色器中解压缩它。当我采样一个像素时,我得到一个vec4,它实际上是我的一个浮点数。如何从vec4转换为浮动?
答案 0 :(得分:13)
以下代码专门针对使用OpenGL ES 2.0的iPhone 4 GPU。我没有使用WebGL的经验,因此我无法声称知道代码在该上下文中的工作方式。此外,这里的主要问题是highp float不是32位而是24位。
我的解决方案是片段着色器 - 我没有在顶点着色器中尝试它,但它不应该有任何不同。为了使用你需要从sampler2d统一得到RGBA纹素,并确保每个R,G,B和A通道的值在0.0到255.0之间。这很容易实现如下:
highp vec4 rgba = texture2D(textureSamplerUniform, texcoordVarying)*255.0;
您应该知道,机器的字节顺序将决定字节的正确顺序。上面的代码假定浮点数以big-endian顺序存储。如果您看到结果错误,则只需通过编写
来交换数据的顺序rgba.rgba=rgba.abgr;
紧接在您设置它的行之后。或者交换rgba上的索引。我认为上述内容更具有直观性,并且不容易出现粗心错误。 我不确定它是否适用于所有给定的输入。我测试了大量的数字,发现decode32和encode32不是完全相反的。我也遗漏了用来测试它的代码。
#pragma STDGL invariant(all)
highp vec4 encode32(highp float f) {
highp float e =5.0;
highp float F = abs(f);
highp float Sign = step(0.0,-f);
highp float Exponent = floor(log2(F));
highp float Mantissa = (exp2(- Exponent) * F);
Exponent = floor(log2(F) + 127.0) + floor(log2(Mantissa));
highp vec4 rgba;
rgba[0] = 128.0 * Sign + floor(Exponent*exp2(-1.0));
rgba[1] = 128.0 * mod(Exponent,2.0) + mod(floor(Mantissa*128.0),128.0);
rgba[2] = floor(mod(floor(Mantissa*exp2(23.0 -8.0)),exp2(8.0)));
rgba[3] = floor(exp2(23.0)*mod(Mantissa,exp2(-15.0)));
return rgba;
}
highp float decode32(highp vec4 rgba) {
highp float Sign = 1.0 - step(128.0,rgba[0])*2.0;
highp float Exponent = 2.0 * mod(rgba[0],128.0) + step(128.0,rgba[1]) - 127.0;
highp float Mantissa = mod(rgba[1],128.0)*65536.0 + rgba[2]*256.0 +rgba[3] + float(0x800000);
highp float Result = Sign * exp2(Exponent) * (Mantissa * exp2(-23.0 ));
return Result;
}
void main()
{
highp float result;
highp vec4 rgba=encode32(-10.01);
result = decode32(rgba);
}
答案 1 :(得分:3)
Twerdster在他的回答中发布了一些优秀的代码。所以所有的功劳归于他。我发布这个新的答案,因为评论不允许很好的语法彩色代码块,我想分享一些代码。但是如果您喜欢这些代码,请提交Twerdster原始答案。
在Twerdster上一篇文章中,他提到解码和编码可能不适用于所有值。
为了进一步测试这个,并验证结果我做了一个java程序。在移植代码时,我试图尽可能接近着色器代码(因此我实现了一些辅助函数)。 注意:我还使用存储/加载函数来模拟从纹理写入/读取时发生的情况。
我发现了:
float Mantissa = (exp2(- Exponent) * F);
更改为float Mantissa = F/exp2(Exponent);
以减少精度错误float Exponent = floor(log2(F));
计算指数。 (通过新的尾数检查简化)使用这些小修改,我几乎在所有输入上得到相同的输出,并且当出现问题时在原始和编码/解码值之间只有很小的误差,而在Twerdster的原始实现中,舍入误差经常导致错误的指数(因此结果是因子2)。
请注意,这是我为测试算法而编写的Java测试应用程序。我希望这在移植到GPU时也能正常工作。如果有人试图在GPU上运行它,请根据您的经验发表评论。
对于使用简单测试的代码来尝试不同的数字,直到它失效。
import java.io.PrintStream;
import java.util.Random;
public class BitPacking {
public static float decode32(float[] v)
{
float[] rgba = mult(255, v);
float sign = 1.0f - step(128.0f,rgba[0])*2.0f;
float exponent = 2.0f * mod(rgba[0],128.0f) + step(128.0f,rgba[1]) - 127.0f;
if(exponent==-127)
return 0;
float mantissa = mod(rgba[1],128.0f)*65536.0f + rgba[2]*256.0f +rgba[3] + ((float)0x800000);
return sign * exp2(exponent-23.0f) * mantissa ;
}
public static float[] encode32(float f) {
float F = abs(f);
if(F==0){
return new float[]{0,0,0,0};
}
float Sign = step(0.0f,-f);
float Exponent = floor(log2(F));
float Mantissa = F/exp2(Exponent);
if(Mantissa < 1)
Exponent -= 1;
Exponent += 127;
float[] rgba = new float[4];
rgba[0] = 128.0f * Sign + floor(Exponent*exp2(-1.0f));
rgba[1] = 128.0f * mod(Exponent,2.0f) + mod(floor(Mantissa*128.0f),128.0f);
rgba[2] = floor(mod(floor(Mantissa*exp2(23.0f -8.0f)),exp2(8.0f)));
rgba[3] = floor(exp2(23.0f)*mod(Mantissa,exp2(-15.0f)));
return mult(1/255.0f, rgba);
}
//shader build-in's
public static float exp2(float x){
return (float) Math.pow(2, x);
}
public static float[] step(float edge, float[] x){
float[] result = new float[x.length];
for(int i=0; i<x.length; i++)
result[i] = x[i] < edge ? 0.0f : 1.0f;
return result;
}
public static float step(float edge, float x){
return x < edge ? 0.0f : 1.0f;
}
public static float mod(float x, float y){
return x-y * floor(x/y);
}
public static float floor(float x){
return (float) Math.floor(x);
}
public static float pow(float x, float y){
return (float)Math.pow(x, y);
}
public static float log2(float x)
{
return (float) (Math.log(x)/Math.log(2));
}
public static float log10(float x)
{
return (float) (Math.log(x)/Math.log(10));
}
public static float abs(float x)
{
return (float)Math.abs(x);
}
public static float log(float x)
{
return (float)Math.log(x);
}
public static float exponent(float x)
{
return floor((float)(Math.log(x)/Math.log(10)));
}
public static float mantissa(float x)
{
return floor((float)(Math.log(x)/Math.log(10)));
}
//shorter matrix multiplication
private static float[] mult(float scalar, float[] w){
float[] result = new float[4];
for(int i=0; i<4; i++)
result[i] = scalar * w[i];
return result;
}
//simulate storage and retrieval in 4-channel/8-bit texture
private static float[] load(int[] v)
{
return new float[]{v[0]/255f, v[1]/255f, v[2]/255f, v[3]/255f};
}
private static int[] store(float[] v)
{
return new int[]{((int) (v[0]*255))& 0xff, ((int) (v[1]*255))& 0xff, ((int) (v[2]*255))& 0xff, ((int) (v[3]*255))& 0xff};
}
//testing until failure, and some specific hard-cases separately
public static void main(String[] args) {
//for(float v : new float[]{-2097151.0f}){ //small error here
for(float v : new float[]{3.4028233e+37f, 8191.9844f, 1.0f, 0.0f, 0.5f, 1.0f/3, 0.1234567890f, 2.1234567890f, -0.1234567890f, 1234.567f}){
float output = decode32(load(store(encode32(v))));
PrintStream stream = (v==output) ? System.out : System.err;
stream.println(v + " ?= " + output);
}
//System.exit(0);
Random r = new Random();
float max = 3200000f;
float min = -max;
boolean error = false;
int trials = 0;
while(!error){
float fin = min + r.nextFloat() * ((max - min) + 1);
float fout = decode32(load(store(encode32(fin))));
if(trials % 10000 == 0)
System.out.print('.');
if(trials % 1000000 == 0)
System.out.println();
if(fin != fout){
System.out.println();
System.out.println("correct trials = " + trials);
System.out.println(fin + " vs " + fout);
error = true;
}
trials++;
}
}
}
答案 2 :(得分:2)
由于你没有给我们提供你用来创建和上传纹理的确切代码,我只能猜测你正在做什么。
您似乎正在创建一个浮点数的JavaScript数组。然后创建一个Uint8Array,将该数组传递给构造函数。
根据WebGL规范(或者更确切地说,WebGL规范在表面上指定此行为时引用的规范),从浮点数到无符号字节的转换以两种方式之一发生,基于目标。如果目标被视为“钳制”,则它会将数字钳位到目标范围,即[0,255]。如果目的地不被视为“钳制”,那么它取模2 8 。 WebGL“规范”足够差,以至于Uint8Array的结构是否被认为是钳制的并不完全清楚。无论是钳位还是取模2 8 ,小数点都会被截断并存储整数值。
但是,当您将此数据提供给打开 WebGL时,您告诉WebGL将字节解释为规范化的无符号整数值。这意味着范围[0,255]上的输入值将被纹理的用户作为[0,1]浮点值访问。
因此,如果输入数组的值为183.45,则Uint8Array中的值为183.纹理中的值为183/255或0.718。如果您的输入值为0.45,则Uint8Array将保持为0,纹理结果将为0.0。
现在,因为您将数据作为GL_RGBA传递,这意味着每4个无符号字节将被视为单个纹素。因此,对texture
的每次调用都将获取这些特定的四个值(在给定的纹理坐标处,使用给定的过滤参数),从而返回vec4
。
目前尚不清楚您打算如何处理此浮点数据,因此很难就如何最好地将浮点数据传递到着色器提出建议。但是,一般的解决方案是使用OES_texture_float扩展并实际创建存储浮点数据的纹理。当然,如果它不可用,你仍然必须找到一种方法来做你想要的。
顺便说一下,Khronos真的应该感到羞耻,因为他们甚至称WebGL为规范。它几乎没有指明任何东西;它只是对其他规范的一堆引用,这使得找到任何非常困难的效果。答案 3 :(得分:2)
我尝试了Arjans解决方案,但是它返回了0,1,2,4的无效值。指数的打包存在一个错误,我改变了所以exp需要一个8位浮点数并且符号包含了尾数:
//unpack a 32bit float from 4 8bit, [0;1] clamped floats
float unpackFloat4( vec4 _packed)
{
vec4 rgba = 255.0 * _packed;
float sign = step(-128.0, -rgba[1]) * 2.0 - 1.0;
float exponent = rgba[0] - 127.0;
if (abs(exponent + 127.0) < 0.001)
return 0.0;
float mantissa = mod(rgba[1], 128.0) * 65536.0 + rgba[2] * 256.0 + rgba[3] + (0x800000);
return sign * exp2(exponent-23.0) * mantissa ;
}
//pack a 32bit float into 4 8bit, [0;1] clamped floats
vec4 packFloat(float f)
{
float F = abs(f);
if(F == 0.0)
{
return vec4(0,0,0,0);
}
float Sign = step(0.0, -f);
float Exponent = floor( log2(F));
float Mantissa = F/ exp2(Exponent);
//std::cout << " sign: " << Sign << ", exponent: " << Exponent << ", mantissa: " << Mantissa << std::endl;
//denormalized values if all exponent bits are zero
if(Mantissa < 1.0)
Exponent -= 1;
Exponent += 127;
vec4 rgba;
rgba[0] = Exponent;
rgba[1] = 128.0 * Sign + mod(floor(Mantissa * float(128.0)),128.0);
rgba[2] = floor( mod(floor(Mantissa* exp2(float(23.0 - 8.0))), exp2(8.0)));
rgba[3] = floor( exp2(23.0)* mod(Mantissa, exp2(-15.0)));
return (1 / 255.0) * rgba;
}
答案 4 :(得分:0)
您将无法在着色器中将4个无符号字节解释为浮点值(我假设您想要)的位(我认为至少不在GLES或WebGL中)。您可以做的不是将浮点数的位表示存储在4 ubytes中,而是存储尾数(或固定点表示)的位。为此你需要知道浮动的大致范围(为简单起见,我假设[0,1],否则你必须以不同的方式扩展):
r = clamp(int(2^8 * f), 0, 255);
g = clamp(int(2^16 * f), 0, 255);
b = clamp(int(2^24 * f), 0, 255); //only have 24 bits of precision anyway
当然你也可以直接使用尾数位。然后在着色器中,您可以使用vec4
的所有组件都在[0,1]中的事实来重建它:
f = (v.r) + (v.g / 2^8) + (v.b / 2^16);
虽然我不确定这是否会产生完全相同的值,但两者的力量应该有所帮助。