Skip to main content
Bumped by Community user
Bumped by Community user
Bumped by Community user
research on correct srgb conversion
Source Link

UPD 2:

Thanks to @scurest for pointing to sRGB conversion is not pow( 1.0/2.2 ). However it seems to reveal that sRGB space is not good for non-color data. Code for tests:


def test(val) :
    col = pack_float32_to_vec4(val)
    col  = toGamma(col)
    col = (col * 256.0).astype(np.uint8)
    col = col.astype(np.float32) / 256.0
    col = toInvGamma(col)
    unpacked = unpack_vec4_to_float32(col)

So here is error for packing -> conversion to uint8 -> conversion to float -> unpacking (values are divisible by powers of two, this way there is no error at all!):

enter image description here

But if I apply the packing -> to SRGB -> conversion to uint8 -> conversion to float -> to Linear -> unpacking, then the error is huge!:

enter image description here

UPD 2:

Thanks to @scurest for pointing to sRGB conversion is not pow( 1.0/2.2 ). However it seems to reveal that sRGB space is not good for non-color data. Code for tests:


def test(val) :
    col = pack_float32_to_vec4(val)
    col  = toGamma(col)
    col = (col * 256.0).astype(np.uint8)
    col = col.astype(np.float32) / 256.0
    col = toInvGamma(col)
    unpacked = unpack_vec4_to_float32(col)

So here is error for packing -> conversion to uint8 -> conversion to float -> unpacking (values are divisible by powers of two, this way there is no error at all!):

enter image description here

But if I apply the packing -> to SRGB -> conversion to uint8 -> conversion to float -> to Linear -> unpacking, then the error is huge!:

enter image description here

made a small research to find a difference
Source Link
# Blender side. Not sure if 255.0 is correct shift, in some sources i found 256.0

def frac(x):
    return x - np.floor(x)

def pack_float32_to_vec4(v):
    enc = np.float32((1.0, 255256.0, 6502565536.0, 1658137516777216.0)) * v
    enc = frac(enc)
    enc -= (enc[1], enc[2], enc[3], enc[3]) * np.float32((1.0/255256.0, 1.0/255256.0, 1.0/255256.0, 0.0))
    return enc


def write_vertex_ind_to_vertex_color(me, image_size):
    if (len(me.vertex_colors) <= 0 ) :
        me.vertex_colors.new()

    v_col = me.vertex_colors.active.data
    uv_layer = me.uv_layers[0]
    
    for loop in me.loops:
        data = (loop.vertex_index + 0.5) / image_size
        color = pack_float32_to_vec4(data)
        v_col[loop.index].color = color

        uv_layer.data[loop.index].uv = (
            (loop.vertex_index + 0.5) / image_size, 128/255 
        )

// shader side, again I am not sure on 255 here
float unpackFloatFromVec4(const vec4 value) {
  vec4 bitSh =  vec4(1.0, 1.0 / 255256.0, 1.0 / 65025(256.00**2), 1.0 /  16581375(256.00**3));
  return dot(value, bitSh);
}

I use glTF file format for the model.

UPD:

Thanks to @Ron Jensen i made a separate python script to test. I use 256.0 as shift base everywhere now, so OP also updated. Here is a plot for difference initial - unpacked:

enter image description here

using 255

enter image description here

Also i've tested other values for bit shifts, but they look kind of wrong :

1.0, 255.0, 65535.0, 16777215.0 - subtract one from powers of 2

enter image description here

# Blender side. Not sure if 255.0 is correct shift, in some sources i found 256.0

def frac(x):
    return x - np.floor(x)

def pack_float32_to_vec4(v):
    enc = np.float32((1.0, 255.0, 65025.0, 16581375.0)) * v
    enc = frac(enc)
    enc -= (enc[1], enc[2], enc[3], enc[3]) * np.float32((1.0/255.0, 1.0/255.0, 1.0/255.0, 0.0))
    return enc


def write_vertex_ind_to_vertex_color(me, image_size):
    if (len(me.vertex_colors) <= 0 ) :
        me.vertex_colors.new()

    v_col = me.vertex_colors.active.data
    uv_layer = me.uv_layers[0]
    
    for loop in me.loops:
        data = (loop.vertex_index + 0.5) / image_size
        color = pack_float32_to_vec4(data)
        v_col[loop.index].color = color

        uv_layer.data[loop.index].uv = (
            (loop.vertex_index + 0.5) / image_size, 128/255 
        )

// shader side, again I am not sure on 255 here
float unpackFloatFromVec4(const vec4 value) {
  vec4 bitSh =  vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 /  16581375.0);
  return dot(value, bitSh);
}

I use glTF file format for the model.

# Blender side. Not sure if 255.0 is correct shift, in some sources i found 256.0

def frac(x):
    return x - np.floor(x)

def pack_float32_to_vec4(v):
    enc = np.float32((1.0, 256.0, 65536.0, 16777216.0)) * v
    enc = frac(enc)
    enc -= (enc[1], enc[2], enc[3], enc[3]) * np.float32((1.0/256.0, 1.0/256.0, 1.0/256.0, 0.0))
    return enc


def write_vertex_ind_to_vertex_color(me, image_size):
    if (len(me.vertex_colors) <= 0 ) :
        me.vertex_colors.new()

    v_col = me.vertex_colors.active.data
    uv_layer = me.uv_layers[0]
    
    for loop in me.loops:
        data = (loop.vertex_index + 0.5) / image_size
        color = pack_float32_to_vec4(data)
        v_col[loop.index].color = color

        uv_layer.data[loop.index].uv = (
            (loop.vertex_index + 0.5) / image_size, 128/255 
        )

// shader side, again I am not sure on 255 here
float unpackFloatFromVec4(const vec4 value) {
  vec4 bitSh =  vec4(1.0, 1/256.0, 1/(256.0**2), 1/(256.0**3));
  return dot(value, bitSh);
}

I use glTF file format for the model.

UPD:

Thanks to @Ron Jensen i made a separate python script to test. I use 256.0 as shift base everywhere now, so OP also updated. Here is a plot for difference initial - unpacked:

enter image description here

using 255

enter image description here

Also i've tested other values for bit shifts, but they look kind of wrong :

1.0, 255.0, 65535.0, 16777215.0 - subtract one from powers of 2

enter image description here

Source Link

Packing float32 to vertex color

I am learning vertex animation textures. Already used UV.x as normalised vertex index to animate a flag and it works great.

enter image description here

I don't have access to uv2 ("game" engine still under in dev stage) but I want to apply textures. I found a technique for packing float32 in [0,1) range into a texture (in my case vertex color) so I can free my uv space. Here is my code :

# Blender side. Not sure if 255.0 is correct shift, in some sources i found 256.0

def frac(x):
    return x - np.floor(x)

def pack_float32_to_vec4(v):
    enc = np.float32((1.0, 255.0, 65025.0, 16581375.0)) * v
    enc = frac(enc)
    enc -= (enc[1], enc[2], enc[3], enc[3]) * np.float32((1.0/255.0, 1.0/255.0, 1.0/255.0, 0.0))
    return enc


def write_vertex_ind_to_vertex_color(me, image_size):
    if (len(me.vertex_colors) <= 0 ) :
        me.vertex_colors.new()

    v_col = me.vertex_colors.active.data
    uv_layer = me.uv_layers[0]
    
    for loop in me.loops:
        data = (loop.vertex_index + 0.5) / image_size
        color = pack_float32_to_vec4(data)
        v_col[loop.index].color = color

        uv_layer.data[loop.index].uv = (
            (loop.vertex_index + 0.5) / image_size, 128/255 
        )

And in shader I try to unpack :

// shader side, again I am not sure on 255 here
float unpackFloatFromVec4(const vec4 value) {
  vec4 bitSh =  vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 /  16581375.0);
  return dot(value, bitSh);
}

Here is the difference for UV.x and my unpacked value :

float u = unpackFloatFromVec4(vCol);
Color = abs((uv.x - u)).xxx1;

uv.x vs unpacked

Vertex color seems to have sRGB correction, which I've tried to fix in python and shader by pow(1.0 / 2.2), but with no luck. It feels like I loose some precision.

vCol = vec4(pow(vCol.xyz, vec3(1.0 / 2.2)) , vCol.w);
float u = unpackFloatFromVec4(vCol);
Color = abs( (uv.x - u) * 100.0).xxx1; # mul 100.0 to see difference

uv.x vs unpacked \w gamma correction

So the even this difference is enough to sample the wrong texel:

just garbage

I use glTF file format for the model.