Yault !
Alors j'ai quand même un p'tit peu persisté avec cette histoire de packing de 2 couleurs dans 4 composantes.
Ce que j'avais oublié de dire c'est que je rendais dans une texture RGBA16F (hé ouais !) donc on a 8 octets par couleur en tout. Désolé, ça aurait pu vous faire avoir plein d'idées mais j'ai zappé.
Bon, bref du coup je me suis dit, c'est quand même con ! On a plein de bytes à disposition.
Alors j'ai décidé de faire le packing suivant :
RGB0 => YUV0
RGB1 => YUV1
Result.Red = YUV0.x // Luminance 0 full resolution
Result.Green = Pack( YUV0.yz ) // UV0 packés sur 1 float16
Result.Blue = YUV1.x // Luminance 1 full resolution
Result.Alpha = Pack( YUV1.yz ) // UV1 packés sur 1 float16
Il faut savoir que les float16 sont codés comme suit :
Sign | Exponent | Mantissa
1 | 5 | 10
Idéalement, si on avait des opérations bitwise en GLSL sans utiliser d'extensions Feng-Shui, il suffirait d'écrire ce code pour décoder un float16 vers un float 32 bits :
UInt16 Value = *((UInt16*) &_fValue);
float fSign = (Value & 0x8000) != 0 ? -1.0 : 1.0;
int Exponent = (Value & 0x7C00) >> 10;
int Mantissa = Value & 0x3FF;
return fSign * exp2( Exponent - 15 ) * (1.0 + Mantissa / 1024.0);
Bon. Mais rien n'empêche d'émuler ces opérations bitwise en flottant !
L'idée est donc d'encoder U et V comme ça :
S|EEEEE|MMMMMMMMMM
U|UUUUU|UUVVVVVVVV
7|65432|1076543210
Voici donc mon code d'encodage :
half2 YUV2Half( float3 _YUV )
{
float U = floor( _YUV.y * 255.0 );
float V = floor( _YUV.z * 255.0 );
half Sign = step( 128.0, U );
U -= Sign * 128.0; // Remove bit 7 (sign)
Sign = 1.0 - 2.0 * Sign;
half Exponent = floor( 0.25 * U ); // Remove 2 LS bits
U -= 4.0 * Exponent; // Remove bits 2-6 (we're left with the 2 LS bits)
half Mantissa = 1.0 + (256.0 * U + V) * INV1024;
half PackedUV = Sign * exp2( Exponent - 15.0 ) * Mantissa;
return half2( _YUV.x, PackedUV );
}
Et le code de décodage :
float3 Half2YUV( half2 _PackedYUV )
{
float UV = _PackedYUV.y;
half Sign = step( UV, 0.0 );
UV = abs( UV ); // Remove sign
half Exponent = floor( log2( UV ) );
UV *= exp2( -Exponent ); // We should have a value in [1,2[
float Mantissa = 1024.0 * (UV - 1.0);
float U = (128.0 * Sign + (Exponent + 15.0) * 4.0 + floor( Mantissa * INV256 )) * INV256;
float V = frac( Mantissa * INV256 );
return float3( _PackedYUV.x, U, V );
}
A noter que UV ne sont pas comme d'habitude en [-0.5,+0.5] mais [0,1]. J'utilise ce code pour la conversion RGB (ça vient de là
http://www.fourcc.org/fccyvrgb.php) :
float3 RGB2YUV( float3 _RGB )
{
float3 YUV;
YUV.x = 0.299 * _RGB.r + 0.587 * _RGB.g + 0.114 * _RGB.b;
YUV.y = saturate( 0.5 + 0.565 * (_RGB.b-YUV.x) );
YUV.z = saturate( 0.5 + 0.713 * (_RGB.r-YUV.x) );
return YUV;
}
float3 YUV2RGB( float3 _YUV )
{
_YUV.yz -= 0.5;
float3 RGB;
RGB.r = _YUV.x + 1.403 * _YUV.z;
RGB.g = _YUV.x - 0.344 * _YUV.y - 0.714 * _YUV.z;
RGB.b = _YUV.x + 1.770 * _YUV.y;
return RGB;
}
Evidemment, l'inconvénient de cette méthode est qu'on ne peut pas sampler le buffer packé autrement qu'en NEAREST. Mais bon ça permet d'utiliser 1 seul buffer au lieu de 2 !
Imaginez l'intérêt quand vous avez besoin, comme moi, de calculer Scattering + Exinction : si votre shader de calcul fait un ray-marching sur 64 steps, vous avez pas forcément envie de faire 2 passes !
