概要
テクスチャにデータを詰めてGPUに送り、色ではなくデータとして扱いたいケースはままあると思います。
ただ、通常のテクスチャは各要素が8bitの精度しかなく、32bitのfloatを送ろうとしても当然、ひとつの要素では表現しきれません。
しかしテクスチャはRGBAと4つの要素があり、合計で32bitを保持することができます。
今回は32bitのfloatの値を、4つの8bit intに変換して保持する方法をメモしておこうと思います。(毎回忘れるので)
ちなみに以前にも似た記事([Shader] floatをfixedに変換し、RGBAに格納する)を書いていたんですが、少し違った方法なので改めてメモです。
コードサンプル
public class Hello
{
public static void Main()
{
const float test = 0.93104938143193145f;
float v1 = test * 255;
int r = (int)v1;
float v2 = (v1 - r) * 255;
int g = (int)v2;
float v3 = (v2 - g) * 255;
int b = (int)v3;
float v4 = (v3 - b) * 255;
int a = (int)v4;
float r1 = (float)r / 255f;
float r2 = (float)g / (255f * 255f);
float r3 = (float)b / (255f * 255f * 255f);
float r4 = (float)a / (255f * 255f * 255f * 255f);
float result = r1 + r2 + r3 + r4;
System.Console.WriteLine(test - result);
}
}
これの実際に動くC#のデモは以下になります。
アイデア自体はこちらの記事で書かれていたものをC#で書いただけのものです。
解説
やっていることは比較的シンプルです。
float
の値を255
倍することで8bitに収まるように整数を取り出す、という操作を繰り返すだけです。
参考にした記事では 0.42889250633567028616881555337687
を保存していく過程が書かれています。
まず、これを255
倍してみましょう。すると、
0.42889250633567028616881555337687 * 255 = 109.3675891155959229730479661111
となります。
そして整数部分だけを取り出すので109
を保存しておきます。
試しにこれを255
で割ると109 / 255 = 0.42745098039215686274509803921569
という数値になります。
元のfloat
の値と比較してみると誤差は0.001441525943513423423717514161
となります。
ここの精度をもっと上げるために、他のテクスチャの要素にさらに値を保存していくことで達成します。
具体的には109.3675891155959229730479661111
の小数点以下だけを取り出し、最初に行った操作と同じことをします。
つまり、
0.3675891155959229730479661111 * 255 = 93.7352244769603581272313583305
ということですね。そしてまたこの整数部分の93
を保持します。
そしてこれを要素分(つまり4回)繰り返し、それぞれの整数部分を保持しておきます。
保存した4つの要素を使って元の32bit floatの値を復元するためには、保存している整数を255
のべき乗で割ったものを足しわせることで行います。
つまり、
109/255^1 + 93/255^2 + 187/255^3 = 0.42889247725233884403434576444957
という感じです。(参考にした記事では24bitの3要素なので3つ分だけ計算しています)
元の数値と比べてみると、
元の数値: 0.42889250633567028616881555337687
復元した数値: 0.42889247725233884403434576444957
だいぶ小さい誤差まで復元できていることが分かるかと思います。
シェーダでコンパクトに計算
なお、シェーダでこれを実行する場合はとてもスマートに変換する方法がこちらの記事で書かれていました。
inline float4 EncodeFloatRGBA( float v ) {
float4 enc = float4(1.0, 255.0, 65025.0, 16581375.0) * v;
enc = frac(enc);
enc -= enc.yzww * float4(1.0/255.0,1.0/255.0,1.0/255.0,0.0);
return enc;
}
inline float DecodeFloatRGBA( float4 rgba ) {
return dot( rgba, float4(1.0, 1/255.0, 1/65025.0, 1/16581375.0) );
}
めちゃめちゃスマート。すごい。