2 int to 1 float

How can I pack 1 float into 2 int in order to save my normal.xy into a FBO (RGBA8)?

Like they do in killzone2:

http://www.dimension3.sk/mambo/View-document-details/Deferred-Rendering-In-Killzone.php

(page18)

Cheers!

This old thread does the reverse of packing 3 ints into 1 float:

http://www.opengl.org/discussion_boards/…1236#Post221236

Should not be too hard for you to figure it out from that.

Heuuuu, but how can I do that in GLSL, no bitwise operators…

Ok well, I think that should do the trick and since it’s some normals that should be ok, the pseudo code is in C (for debugging purpose) but… I’ll post the cleaned up version later :wink:

float f = -0.9999f, f1;
float x = 0.0f, y = 0.0f,
	  x1 = 1.0f, y1 = 1.0f / 256.0f, result;

f1 = f;
f *= 256.0f;

x = floor( f );
f = ( f - x ) * 256.0f;

y = floor( f );
x *= 0.00390625;
y *= 0.00390625;

printf("%f %f

", x, y );

result = ( x * x1 ) +
		 ( y * y1 );

printf("%f:%f

", f1, result );

For Nvidia cards, you can use the pack/unpack instructions:

  • through a fragment program
  • through the Cg instructions. You should be able to load Cg files with the GLSL runtime if GL_EXT_Cg_shader is exposed.

N.

So in GLSL the functions to pack/unpack are:



	vec2 pack_float( float f )
	{
		f *= 256.0;
		float x = floor( f );
		f = ( f - x ) * 256.0;
		
		float y = floor( f );
		x *= 0.00390625;
		y *= 0.00390625; 
		
		return vec2( x, y );
	}


	float unpack_float( vec2 v )
	{
		return v.x + ( v.y * 0.00390625 );
	}

With this approach you can use a RGBA8 FBO and pack the normal.x and normal.y and then reconstruct the normal.z using the following code (my guess is that this is exactly what they are doing in killzone2)

Vertex Shader:



...
...
		gl_FragData[???] = vec4( pack_float( normal.x ), pack_float( normal.y ) );
...
...


Fragment Shader:



...
...
		vec4 normal = texture2D( norm_tex, gl_TexCoord[0].st );
		
		normal.x = unpack_float( normal.xy );
		normal.y = unpack_float( normal.zw );
		normal.z = sqrt( 1.0 - ( normal.x * normal.x ) - ( normal.y * normal.y ) );
...
...


Hope this can help somebody… especially if you decide to use deferred rendering, and decide to compromise calculations for space :wink:

Well what I really meant was something like this:


vec2 pack_float( float f )
{
  return fract(vec2(1.0, 256.0) * f);
}

float unpack_float( vec2 v )
{
  return v.x + ( v.y * 0.00390625 );
}

Edit: I just realized that the above packing won’t work correctly if the incoming value is exactly 1.0 - may want to bias the x or y of a normal slightly if they ever equal 1.0.

Could try this that should have the same precision:


vec2 pack_float( float f )
{
  f *= 256.0;
  float x = floor( f );
  float y = f - x;
  x *= 0.00390625;
  
  return vec2( x, y );
}

Uhm, the PS3’s GPU is a derivative of GeForce7x00. With nVidia drivers on a GF6x00-GF8x00, you can use those unpack/pack instructions in both Cg and GLSL (thankfully).
For ATi cards, you have to use maths or tricks.

Edit: Via some tricks with those instructions I think you could do what you want. I’ll try to tackle the problem

Yup, this one does the trick, and I verified the output values:


half2 MyNormal = vec2(1.0,1.0);
float scalar1 = pack_2half(MyNormal);
vec4 OutColor = unpack_4ubyte(scalar1);
gl_FragColor= OutColor;

Checked what values should be seen with this x86 asm code:


;fax1 real4 2314.0
fax1 real4 1.0

; s1 e8 m23
; s1 e5 m10
main proc
	printh fax1
	mov eax,fax1
	mov ebx,fax1
	and eax,11111100000000000000000000000000b
	and ebx,00000000011111111110000000000000b
	
	shr eax,16
	shr ebx,13
	or eax,ebx
	printh eax
	
	movzx ecx,al
	movzx edx,ah
	
	print ecx
	print edx	
	ret
main endp

Or this is the C version:


short half_from_float(float x){
	int y = *((int*)&x);
	return (short)(((y&0xFC000000)>>16) | ((y&0x007FE000)>>13));
}

While searching for the Cg/GLSL function-names of the unpack/pack instructions, I stumbled on some blog, where someone stated that on his GF80 the pack/unpack instructions take 4-10 cycles (“probably internal bit-shifting”). I do not believe on nv40 (and thus the RSX) these instructions are macros, thus are a viable choice for Killzone2.

Otherwise, if you don’t want to use these instructions (i.e writing for ATi cards), I think that the log2 instructions will be part of your equation. Those methods of using multiplications and divisions by 256 (that you started experimenting with) are with extremely limited range. But for normals, since you know the range, you can get away with them :). (and prolly will be faster on non-nv40 cards).

This topic was automatically closed 183 days after the last reply. New replies are no longer allowed.