Skip to main content
remove label
Source Link
taiyo
  • 3.5k
  • 1
  • 4
  • 19

Actually you can, see edit below.

As @Harry McKenzie suggested, you can't. Let me make this plausible by some observations. I changed your script a little to produce all grey tones from 0-1 with a step of 0.001:.

(Edit: choosing 1024 would have made the explanantion easier :))

Edit:

I dug deeper and there actually is a way to get all hex values on screen, but not with the approach above so that observation still holds. But the observation only shows that the values get capped, but why?

Simplifying the above script to this:

import bpy
import gpu
from gpu_extras.batch import batch_for_shader

shader = gpu.shader.from_builtin('UNIFORM_COLOR')

batches = []
indices = ((0, 1, 2), (2, 1, 3))
for i in range(256):
    vertices = ((10 + i, 100), (10 + i+1, 100), (10 + i, 200), (10 + i+1, 200))
    batches.append(batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices))

def draw():
    for i in range(256):
        c = i / 255.0  # come on, all hex values plz
        #shader.uniform_bool("srgbTarget", False)
        shader.uniform_float("color", (c,c,c, 1.0))
        batches[i].draw(shader)

bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_PIXEL')

and running it gives a linear looking gradient ((127,127,127) in the middle) but with capped values, so there may be a problem within the shader. The shader's implementation is interesting (source):

#pragma BLENDER_REQUIRE(gpu_shader_colorspace_lib.glsl)

void main()
{
  fragColor = blender_srgb_to_framebuffer_space(color); // huh?
}

Looking further one finds the implementation of this function (source):

#ifndef USE_GPU_SHADER_CREATE_INFO
uniform bool srgbTarget = false;
#endif

vec4 blender_srgb_to_framebuffer_space(vec4 in_color)
{
  if (srgbTarget) {
    # this is the very precise version of the common gamma correction
    vec3 c = max(in_color.rgb, vec3(0.0));
    vec3 c1 = c * (1.0 / 12.92);
    vec3 c2 = pow((c + 0.055) * (1.0 / 1.055), vec3(2.4));
    in_color.rgb = mix(c1, c2, step(vec3(0.04045), c));
  }
  return in_color;
}

Note the uniform srgbTarget which is defaulted to false. Setting this uniform explicitly to false with shader.uniform_bool("srgbTarget", False) should not change anything. But surprise, now the gradient is nonlinear ((187,187,187) in the middle)!

enter image description here

This can't be. Turns out srgbTarget for the UNIFORM_COLOR shader is set to true (source), but this means the gamma correction in blender_srgb_to_framebuffer_space takes place before the color value is fed into FragColor. And this means the values must be somehow corrected back after that! There is a newer feature in OpenGL where the driver can gamma correct all framebuffers after fragment shaders have been run and there are traces of that in Blender (source), thus i assume its enabled for the default framebuffer. Thus gamma correcting and reverting this correction seems to be the root cause for the numerical issues.

However, one can do offscreen rendering (source) and hope that the driver's gamma correction is not enabled (i have no idea if this can be set). Starting from there and with the other examples, i created this script:

import bpy
import gpu
import math
import random
from mathutils import Matrix
from gpu_extras.batch import batch_for_shader

# create the 'UNIFORM COLOR' shader ourself

shader_info = gpu.types.GPUShaderCreateInfo()
shader_info.push_constant('MAT4', "viewProjectionMatrix")
shader_info.push_constant('FLOAT', "color")
shader_info.vertex_in(0, 'VEC3', "position")
shader_info.fragment_out(0, 'VEC4', "FragColor")

shader_info.vertex_source(
    "void main()"
    "{"
    "  gl_Position = viewProjectionMatrix * vec4(position, 1.0f);"
    "}"
)

shader_info.fragment_source(
    "void main()"
    "{"
    "  float c = color / 255.0;"
    "  FragColor = vec4(c, c, c, 1.0);"
    "}"
)

shader = gpu.shader.create_from_info(shader_info)
del shader_info

# create the batches to render each column in the image

batches = []
step = 2 / 255.0
for i in range(256):
    k = i * step - 1.0 - step * 0.5
    coords = [
        (k, -1, 0), (k+step, -1, 0), (k+step, 1, 0),
        (k+step, 1, 0), (k, 1, 0), (k, -1, 0)]
    
    batches.append(batch_for_shader(shader, 'TRIS', {"position": coords}))

# start offscreen rendering

IMAGE_NAME = "all_hex_values"
WIDTH = 256
HEIGHT = 256

offscreen = gpu.types.GPUOffScreen(WIDTH, HEIGHT)

with offscreen.bind():
    fb = gpu.state.active_framebuffer_get()
    fb.clear(color=(0.0, 0.0, 0.0, 0.0))
    with gpu.matrix.push_pop():
        # reset matrices -> use normalized device coordinates [-1, 1]
        shader.uniform_float("viewProjectionMatrix", Matrix.Identity(4))
        
        for i in range(256):    
            shader.uniform_float("color", i)
            batches[i].draw(shader)

    buffer = fb.read_color(0, 0, WIDTH, HEIGHT, 4, 0, 'UBYTE')

offscreen.free()

# copy the render result into an blender image

if IMAGE_NAME not in bpy.data.images:
    bpy.data.images.new(IMAGE_NAME, WIDTH, HEIGHT)

image = bpy.data.images[IMAGE_NAME]
image.scale(WIDTH, HEIGHT)

buffer.dimensions = WIDTH * HEIGHT * 4
image.pixels = [v / 255 for v in buffer]

Save the image and you got all these precious hex values in its full glory:

enter image description here

As @Harry McKenzie suggested, you can't. Let me make this plausible by some observations. I changed your script a little to produce all grey tones from 0-1 with a step of 0.001:

Actually you can, see edit below.

As @Harry McKenzie suggested, you can't. Let me make this plausible by some observations. I changed your script a little to produce all grey tones from 0-1 with a step of 0.001.

(Edit: choosing 1024 would have made the explanantion easier :))

Edit:

I dug deeper and there actually is a way to get all hex values on screen, but not with the approach above so that observation still holds. But the observation only shows that the values get capped, but why?

Simplifying the above script to this:

import bpy
import gpu
from gpu_extras.batch import batch_for_shader

shader = gpu.shader.from_builtin('UNIFORM_COLOR')

batches = []
indices = ((0, 1, 2), (2, 1, 3))
for i in range(256):
    vertices = ((10 + i, 100), (10 + i+1, 100), (10 + i, 200), (10 + i+1, 200))
    batches.append(batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices))

def draw():
    for i in range(256):
        c = i / 255.0  # come on, all hex values plz
        #shader.uniform_bool("srgbTarget", False)
        shader.uniform_float("color", (c,c,c, 1.0))
        batches[i].draw(shader)

bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_PIXEL')

and running it gives a linear looking gradient ((127,127,127) in the middle) but with capped values, so there may be a problem within the shader. The shader's implementation is interesting (source):

#pragma BLENDER_REQUIRE(gpu_shader_colorspace_lib.glsl)

void main()
{
  fragColor = blender_srgb_to_framebuffer_space(color); // huh?
}

Looking further one finds the implementation of this function (source):

#ifndef USE_GPU_SHADER_CREATE_INFO
uniform bool srgbTarget = false;
#endif

vec4 blender_srgb_to_framebuffer_space(vec4 in_color)
{
  if (srgbTarget) {
    # this is the very precise version of the common gamma correction
    vec3 c = max(in_color.rgb, vec3(0.0));
    vec3 c1 = c * (1.0 / 12.92);
    vec3 c2 = pow((c + 0.055) * (1.0 / 1.055), vec3(2.4));
    in_color.rgb = mix(c1, c2, step(vec3(0.04045), c));
  }
  return in_color;
}

Note the uniform srgbTarget which is defaulted to false. Setting this uniform explicitly to false with shader.uniform_bool("srgbTarget", False) should not change anything. But surprise, now the gradient is nonlinear ((187,187,187) in the middle)!

enter image description here

This can't be. Turns out srgbTarget for the UNIFORM_COLOR shader is set to true (source), but this means the gamma correction in blender_srgb_to_framebuffer_space takes place before the color value is fed into FragColor. And this means the values must be somehow corrected back after that! There is a newer feature in OpenGL where the driver can gamma correct all framebuffers after fragment shaders have been run and there are traces of that in Blender (source), thus i assume its enabled for the default framebuffer. Thus gamma correcting and reverting this correction seems to be the root cause for the numerical issues.

However, one can do offscreen rendering (source) and hope that the driver's gamma correction is not enabled (i have no idea if this can be set). Starting from there and with the other examples, i created this script:

import bpy
import gpu
import math
import random
from mathutils import Matrix
from gpu_extras.batch import batch_for_shader

# create the 'UNIFORM COLOR' shader ourself

shader_info = gpu.types.GPUShaderCreateInfo()
shader_info.push_constant('MAT4', "viewProjectionMatrix")
shader_info.push_constant('FLOAT', "color")
shader_info.vertex_in(0, 'VEC3', "position")
shader_info.fragment_out(0, 'VEC4', "FragColor")

shader_info.vertex_source(
    "void main()"
    "{"
    "  gl_Position = viewProjectionMatrix * vec4(position, 1.0f);"
    "}"
)

shader_info.fragment_source(
    "void main()"
    "{"
    "  float c = color / 255.0;"
    "  FragColor = vec4(c, c, c, 1.0);"
    "}"
)

shader = gpu.shader.create_from_info(shader_info)
del shader_info

# create the batches to render each column in the image

batches = []
step = 2 / 255.0
for i in range(256):
    k = i * step - 1.0 - step * 0.5
    coords = [
        (k, -1, 0), (k+step, -1, 0), (k+step, 1, 0),
        (k+step, 1, 0), (k, 1, 0), (k, -1, 0)]
    
    batches.append(batch_for_shader(shader, 'TRIS', {"position": coords}))

# start offscreen rendering

IMAGE_NAME = "all_hex_values"
WIDTH = 256
HEIGHT = 256

offscreen = gpu.types.GPUOffScreen(WIDTH, HEIGHT)

with offscreen.bind():
    fb = gpu.state.active_framebuffer_get()
    fb.clear(color=(0.0, 0.0, 0.0, 0.0))
    with gpu.matrix.push_pop():
        # reset matrices -> use normalized device coordinates [-1, 1]
        shader.uniform_float("viewProjectionMatrix", Matrix.Identity(4))
        
        for i in range(256):    
            shader.uniform_float("color", i)
            batches[i].draw(shader)

    buffer = fb.read_color(0, 0, WIDTH, HEIGHT, 4, 0, 'UBYTE')

offscreen.free()

# copy the render result into an blender image

if IMAGE_NAME not in bpy.data.images:
    bpy.data.images.new(IMAGE_NAME, WIDTH, HEIGHT)

image = bpy.data.images[IMAGE_NAME]
image.scale(WIDTH, HEIGHT)

buffer.dimensions = WIDTH * HEIGHT * 4
image.pixels = [v / 255 for v in buffer]

Save the image and you got all these precious hex values in its full glory:

enter image description here

better detail description for how to fit 1000 into 256
Source Link
taiyo
  • 3.5k
  • 1
  • 4
  • 19

This is because you have to squeeze 1000 values into the R-channel which goes from #00 to #FF and offers place for only 256 values. Thus roughly 4 adjacent float values will fall into one hexadecimal value, because 1000 / 256 = 3.90625. (The two values at the beginning for #00 and the end value for #FF belong symmetrically together, p 1000 would also be 1.0). SometimesThats why sometimes you can see a block of just 3 (missing a 4th) or a 5/3 split instead of 4/4 (e.g. at p 993/994), but this. This is because of rounding errors (for the exact conversion formula one would need to know all data types participating when the color values go back and forth, but i did not look into the engine).

This is because you have to squeeze 1000 values into the R-channel which goes from #00 to #FF and offers place for only 256 values. Thus 4 adjacent float values will fall into one hexadecimal value. (The two values at the beginning for #00 and the end value for #FF belong symmetrically together, p 1000 would also be 1.0). Sometimes you can see a 5/3 split instead of 4/4 (e.g. at p 993/994), but this is because of rounding errors (for the exact conversion formula one would need to know all data types participating when the color values go back and forth, but i did not look into the engine).

This is because you have to squeeze 1000 values into the R-channel which goes from #00 to #FF and offers place for only 256 values. Thus roughly 4 adjacent float values will fall into one hexadecimal value, because 1000 / 256 = 3.90625. (The two values at the beginning for #00 and the end value for #FF belong symmetrically together, p 1000 would also be 1.0). Thats why sometimes you can see a block of just 3 (missing a 4th) or a 5/3 split instead of 4/4 (e.g. at p 993/994). This is because of rounding errors (for the exact conversion formula one would need to know all data types participating when the color values go back and forth, but i did not look into the engine).

align output with script version, typos
Source Link
taiyo
  • 3.5k
  • 1
  • 4
  • 19
p 0 : Buffer(FLOAT, [[[0.0, 0.0, 0.0, 1.0]]])
p 1 : Buffer(FLOAT, [[[0.0, 0.0, 0.0, 1.0]]])
p 2 : Buffer(FLOAT, [[[0.003921568859368563, 0.003921568859368563, 0.003921568859368563, 1.0]]])
p 3 : Buffer(FLOAT, [[[0.003921568859368563, 0.003921568859368563, 0.003921568859368563, 1.0]]])
p 4 : Buffer(FLOAT, [[[0.003921568859368563, 0.003921568859368563, 0.003921568859368563, 1.0]]])
p 5 : Buffer(FLOAT, [[[0.003921568859368563, 0.003921568859368563, 0.003921568859368563, 1.0]]])
p 6 : Buffer(FLOAT, [[[0.007843137718737125, 0.007843137718737125, 0.007843137718737125, 1.0]]])
p 7 : Buffer(FLOAT, [[[0.007843137718737125, 0.007843137718737125, 0.007843137718737125, 1.0]]])
p 8 : Buffer(FLOAT, [[[0.007843137718737125, 0.007843137718737125, 0.007843137718737125, 1.0]]])
p 9 : Buffer(FLOAT, [[[0.007843137718737125, 0.007843137718737125, 0.007843137718737125, 1.0]]])
p 10 : Buffer(FLOAT, [[[0.011764707043766975, 0.011764707043766975, 0.011764707043766975, 1.0]]])
p 11 : Buffer(FLOAT, [[[0.011764707043766975, 0.011764707043766975, 0.011764707043766975, 1.0]]])
p 12 : Buffer(FLOAT, [[[0.011764707043766975, 0.011764707043766975, 0.011764707043766975, 1.0]]])
p 13 : Buffer(FLOAT, [[[0.011764707043766975, 0.011764707043766975, 0.011764707043766975, 1.0]]])
...
p 987 : Buffer(FLOAT, [[[0.988235354423523, 0.988235354423523, 0.988235354423523, 1.0]]])
p 988 : Buffer(FLOAT, [[[0.988235354423523, 0.988235354423523, 0.988235354423523, 1.0]]])
p 989 : Buffer(FLOAT, [[[0.988235354423523, 0.988235354423523, 0.988235354423523, 1.0]]])
p 990 : Buffer(FLOAT, [[[0.988235354423523, 0.988235354423523, 0.988235354423523, 1.0]]])
p 991 : Buffer(FLOAT, [[[0.9921569228172302, 0.9921569228172302, 0.9921569228172302, 1.0]]])
p 992 : Buffer(FLOAT, [[[0.9921569228172302, 0.9921569228172302, 0.9921569228172302, 1.0]]])
p 993 : Buffer(FLOAT, [[[0.9921569228172302, 0.9921569228172302, 0.9921569228172302, 1.0]]])
p 994 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 995 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 996 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 997 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 998 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 999 : Buffer(FLOAT, [[[1.0, 1.0, 1.0, 1.0]]])
p 1000 : Buffer(FLOAT, [[[1.0, 1.0, 1.0, 1.0]]])

This is because you have to squezesqueeze 1000 values into the R-channel which goes from #00 to #FF and offers place for only 256 values. Thus 4 adjacent float values will fall into one hexadecimal value. (The two values at the beginning for #00 and the two end valuesvalue for #FF arebelong symmetrically combinedtogether, p 1000 would also be 1.0). Sometimes you can see a 5/3 split instead of 4/4 (e.g. at p 993/994), but this is because of rounding errors  ( forfor the exact conversion formula one would need to know all data types participating when the color values go back and forth, but i did not look into the engine).

p 0 : Buffer(FLOAT, [[[0.0, 0.0, 0.0, 1.0]]])
p 1 : Buffer(FLOAT, [[[0.0, 0.0, 0.0, 1.0]]])
p 2 : Buffer(FLOAT, [[[0.003921568859368563, 0.003921568859368563, 0.003921568859368563, 1.0]]])
p 3 : Buffer(FLOAT, [[[0.003921568859368563, 0.003921568859368563, 0.003921568859368563, 1.0]]])
p 4 : Buffer(FLOAT, [[[0.003921568859368563, 0.003921568859368563, 0.003921568859368563, 1.0]]])
p 5 : Buffer(FLOAT, [[[0.003921568859368563, 0.003921568859368563, 0.003921568859368563, 1.0]]])
p 6 : Buffer(FLOAT, [[[0.007843137718737125, 0.007843137718737125, 0.007843137718737125, 1.0]]])
p 7 : Buffer(FLOAT, [[[0.007843137718737125, 0.007843137718737125, 0.007843137718737125, 1.0]]])
p 8 : Buffer(FLOAT, [[[0.007843137718737125, 0.007843137718737125, 0.007843137718737125, 1.0]]])
p 9 : Buffer(FLOAT, [[[0.007843137718737125, 0.007843137718737125, 0.007843137718737125, 1.0]]])
p 10 : Buffer(FLOAT, [[[0.011764707043766975, 0.011764707043766975, 0.011764707043766975, 1.0]]])
p 11 : Buffer(FLOAT, [[[0.011764707043766975, 0.011764707043766975, 0.011764707043766975, 1.0]]])
p 12 : Buffer(FLOAT, [[[0.011764707043766975, 0.011764707043766975, 0.011764707043766975, 1.0]]])
p 13 : Buffer(FLOAT, [[[0.011764707043766975, 0.011764707043766975, 0.011764707043766975, 1.0]]])
...
p 987 : Buffer(FLOAT, [[[0.988235354423523, 0.988235354423523, 0.988235354423523, 1.0]]])
p 988 : Buffer(FLOAT, [[[0.988235354423523, 0.988235354423523, 0.988235354423523, 1.0]]])
p 989 : Buffer(FLOAT, [[[0.988235354423523, 0.988235354423523, 0.988235354423523, 1.0]]])
p 990 : Buffer(FLOAT, [[[0.988235354423523, 0.988235354423523, 0.988235354423523, 1.0]]])
p 991 : Buffer(FLOAT, [[[0.9921569228172302, 0.9921569228172302, 0.9921569228172302, 1.0]]])
p 992 : Buffer(FLOAT, [[[0.9921569228172302, 0.9921569228172302, 0.9921569228172302, 1.0]]])
p 993 : Buffer(FLOAT, [[[0.9921569228172302, 0.9921569228172302, 0.9921569228172302, 1.0]]])
p 994 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 995 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 996 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 997 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 998 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 999 : Buffer(FLOAT, [[[1.0, 1.0, 1.0, 1.0]]])
p 1000 : Buffer(FLOAT, [[[1.0, 1.0, 1.0, 1.0]]])

This is because you have to squeze 1000 values into the R-channel which goes from #00 to #FF and offers place for only 256 values. Thus 4 adjacent float values will fall into one hexadecimal value. (The two values at the beginning for #00 and the two end values for #FF are symmetrically combined). Sometimes you can see a 5/3 split instead of 4/4, but this is because of rounding errors( for the exact conversion formula one would need to know all data types participating when the color values go back and forth, but i did not look into the engine).

p 0 : Buffer(FLOAT, [[[0.0, 0.0, 0.0, 1.0]]])
p 1 : Buffer(FLOAT, [[[0.0, 0.0, 0.0, 1.0]]])
p 2 : Buffer(FLOAT, [[[0.003921568859368563, 0.003921568859368563, 0.003921568859368563, 1.0]]])
p 3 : Buffer(FLOAT, [[[0.003921568859368563, 0.003921568859368563, 0.003921568859368563, 1.0]]])
p 4 : Buffer(FLOAT, [[[0.003921568859368563, 0.003921568859368563, 0.003921568859368563, 1.0]]])
p 5 : Buffer(FLOAT, [[[0.003921568859368563, 0.003921568859368563, 0.003921568859368563, 1.0]]])
p 6 : Buffer(FLOAT, [[[0.007843137718737125, 0.007843137718737125, 0.007843137718737125, 1.0]]])
p 7 : Buffer(FLOAT, [[[0.007843137718737125, 0.007843137718737125, 0.007843137718737125, 1.0]]])
p 8 : Buffer(FLOAT, [[[0.007843137718737125, 0.007843137718737125, 0.007843137718737125, 1.0]]])
p 9 : Buffer(FLOAT, [[[0.007843137718737125, 0.007843137718737125, 0.007843137718737125, 1.0]]])
p 10 : Buffer(FLOAT, [[[0.011764707043766975, 0.011764707043766975, 0.011764707043766975, 1.0]]])
p 11 : Buffer(FLOAT, [[[0.011764707043766975, 0.011764707043766975, 0.011764707043766975, 1.0]]])
p 12 : Buffer(FLOAT, [[[0.011764707043766975, 0.011764707043766975, 0.011764707043766975, 1.0]]])
p 13 : Buffer(FLOAT, [[[0.011764707043766975, 0.011764707043766975, 0.011764707043766975, 1.0]]])
...
p 987 : Buffer(FLOAT, [[[0.988235354423523, 0.988235354423523, 0.988235354423523, 1.0]]])
p 988 : Buffer(FLOAT, [[[0.988235354423523, 0.988235354423523, 0.988235354423523, 1.0]]])
p 989 : Buffer(FLOAT, [[[0.988235354423523, 0.988235354423523, 0.988235354423523, 1.0]]])
p 990 : Buffer(FLOAT, [[[0.988235354423523, 0.988235354423523, 0.988235354423523, 1.0]]])
p 991 : Buffer(FLOAT, [[[0.9921569228172302, 0.9921569228172302, 0.9921569228172302, 1.0]]])
p 992 : Buffer(FLOAT, [[[0.9921569228172302, 0.9921569228172302, 0.9921569228172302, 1.0]]])
p 993 : Buffer(FLOAT, [[[0.9921569228172302, 0.9921569228172302, 0.9921569228172302, 1.0]]])
p 994 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 995 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 996 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 997 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 998 : Buffer(FLOAT, [[[0.9960784912109375, 0.9960784912109375, 0.9960784912109375, 1.0]]])
p 999 : Buffer(FLOAT, [[[1.0, 1.0, 1.0, 1.0]]])

This is because you have to squeeze 1000 values into the R-channel which goes from #00 to #FF and offers place for only 256 values. Thus 4 adjacent float values will fall into one hexadecimal value. (The two values at the beginning for #00 and the end value for #FF belong symmetrically together, p 1000 would also be 1.0). Sometimes you can see a 5/3 split instead of 4/4 (e.g. at p 993/994), but this is because of rounding errors  (for the exact conversion formula one would need to know all data types participating when the color values go back and forth, but i did not look into the engine).

Source Link
taiyo
  • 3.5k
  • 1
  • 4
  • 19
Loading