Render Texture や Custom Render Texture で GPU パーティクルのシミュレーションを行うとき,ごく稀に float4 のテクスチャをバイト列として扱いたい場合があります (任意のデータ型を埋め込みたい場合など).
ただ,単純にバイト列と思って扱うとうまくいかないので,扱い方を簡単に説明します.
単精度浮動小数点 float
float は,最上位ビットが符号,次の8ビットが指数,下位23ビットが仮数です.
そして,float には非正規化数という表現があります.浮動小数点の通常の指数表現で表せる最小の値よりも小さい値を扱う表現です.指数部8ビットがゼロで,かつ仮数部が非ゼロの場合に非正規化数として扱われます.1
HLSL の float
HLSL の float は IEEE754 ですが,基本的に非正規化数は使えません.2
Denorms are flushed to sign-preserved zero on input and output of any floating-point mathematical operation. Exceptions are made for any I/O or data movement operation that doesn't manipulate the data.
非正規化数は、浮動小数点の数学演算の入力および出力時に、符号を保持したゼロに丸められます。 例外は、データを操作しない I/O またはデータ移動操作に対して行われます。
そのため,指数部がゼロになるようなバイト列を asfloat() してテクスチャに保存しようとしても,次に取り出したときにはすべてゼロになってしまいます.
バイト列を float として保存する
それでも float にバイト列を詰め込みたいのであれば,指数部がゼロにならないように調整しなければなりません.32ビット全部をデータに使うのはあきらめて,指数部がゼロにならないように指数部の1ビットを使って調整します.
ここでは,指数部の最上位ビット (31ビット目) を,指数部の上から2番目のビット (30ビット目) の反転にすることで,指数部がゼロになるのを防ぎます.
1固定ではなく反転にしているのは,指数部が全部1になるケースをなくすためです.検証してないですが,指数部が全部1は inf や nan なので,ハード依存でおかしくなるかもと思ったからです.
実装
#include "UnityCG.cginc"
#define FLOAT_TO_UINT( from, to ) \
to = asuint( from ); \
to = ( ( to >> 1 ) & 0x40000000 ) | ( to & 0x3FFFFFFF ); \
#define UINT_TO_FLOAT( from, to ) \
to = asfloat( ( ( from << 1 ) & 0x80000000 ) | ( ( ~ ( from << 1 ) ) & 0x40000000 ) | ( from & 0x3FFFFFFF ) ); \
Texture2D<float4> tex;
SamplerState sampler_point_clamp;
float4 flag( v2f_img IN ) : SV_Target
{
float4 cell = tex.SampleLevel( sampler_point_clamp, IN.uv.xy, 0 );
uint4 data;
FLOAT_TO_UINT( cell, data )
...
UINT_TO_FLOAT( data, cell )
return cell;
}
Render Texture (Custom Render Texture) の方で,該当のテクスチャの Filter を Point にするのを忘れないようにしてください.シェーダー側で Sampler point を指定しているので Windows なら関係ないですが,Android だとなんかうまくいかないことがあります.
雑記
普通は uint 型のテクスチャを使えばいいので,わざわざ float テクスチャを使うのはナンセンスなのですが,Quest 対応の時にいろいろあってこの形に落ち着きました...
uint 型のテクスチャはデータを扱うときに便利なんですが,サンプラーでのサンプルができません.Texture<uint4> tex で宣言して,tex.Load() とかで読み出しますよね.ただ,Android だとこの tex.Load() が使えないので,結局 uint 型のテクスチャは扱えません.(宣言はできるのに...)
できないものは仕方ないので,1ビット分犠牲にしつつ,float テクスチャで何とかすることにしたというわけです.

