Help us understand the problem. What is going on with this article?

floatの値を4つの8bit intに変換して保持する

More than 1 year has passed since last update.

概要

テクスチャにデータを詰めて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) );
}

めちゃめちゃスマート。すごい。

edo_m18
現在はUnity ARエンジニア。 主にARのコンテンツ制作をしています。 最近は機械学習にも興味が出て勉強中です。 Unityに関するブログは別で書いています↓ https://edom18.hateblo.jp/
http://edom18.hateblo.jp/
unity-game-dev-guild
趣味・仕事問わずUnityでゲームを作っている開発者のみで構成されるオンラインコミュニティです。Unityでゲームを開発・運用するにあたって必要なあらゆる知見を共有することを目的とします。
https://unity-game-dev-guild.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away