Twerdster опубликовал отличный код в своем ответе.Так что все заслуги идут к нему.Я публикую этот новый ответ, так как комментарии не позволяют использовать цветные синтаксические блоки кода, и я хотел бы поделиться некоторым кодом.Но если вам нравится код, пожалуйста, проголосуйте за оригинальный ответ Twerdster.
В предыдущем посте Twerdster он упомянул, что декодирование и кодирование могут работать не для всех значений.
Для дальнейшей проверки и проверкиВ результате я сделал Java-программу.При переносе кода я старался максимально приблизиться к коду шейдера (поэтому я реализовал некоторые вспомогательные функции).Примечание: я также использую функцию сохранения / загрузки, чтобы схожи с тем, что происходит, когда вы пишете / читаете из текстуры.
Я обнаружил, что:
- Вам нужен особый случай для нуля
- Вам также может понадобиться особый случай для бесконечности, но я не реализовал это длясделайте шейдер простым (например: быстрее)
- Из-за ошибок округления иногда результат был неверным, поэтому:
- вычитайте 1 из показателя степени, если из-за округления мантисса не нормализована должным образом (например, мантисса <1) </li>
- Измените
float Mantissa = (exp2(- Exponent) * F);
на float Mantissa = F/exp2(Exponent);
, чтобы уменьшить погрешности точности - Используйте
float Exponent = floor(log2(F));
для вычисления показателя степени.(упрощено новой проверкой мантиссы)
Используя эти небольшие модификации, я получил одинаковый вывод почти на всех входах и получил только небольшие ошибки между исходным и кодированным / декодированным значением, когда всечто-то не так, в то время как в оригинальной реализации Twerdster ошибки округления часто приводили к неверному показателю (таким образом, результат был отклонен в два раза).
Обратите внимание, что это тестовое приложение 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++;
}
}
}