5

I am painting the default cube through vertex coloring. I paint the bottom four vertices as (0.25, 0.25, 0.25) and the top four vertices as (0.75, 0.75, 0.75).

When I inspect the result in VERTEX PAINT mode (using hotkey S to get the color), the bottom of the cube is indeed (0.25, 0.25, 0.25) and the top is (0.75, 0.75, 0.75).

However, the middle point of the cube is not (0.5, 0.5, 0.5), it is ~(0.56, 0.56, 0.56). Somehow the gradient is not linear. I don't think its an issue with sRGB vs Linear, because otherwise the bottom and top values would also be wrong, but it's just the values in between. So what is the issue here? I really need a linear gradient.

EDIT. related issue: https://developer.blender.org/T71835 This says it has something to do with how the vertex colors are stored (8-bit sRGB apparently).

Code to reproduce this issue:

"""Script that checks vertex color interpolation error on the default cube.

run command: blender --python gradient_error.py (opens blender) """

import bpy

Set up scene

sce = bpy.context.scene

Select cube

scene = bpy.context.scene for ob in scene.objects: ob.select_set(False) if ob.name == 'Cube': ob.select_set(True) obj = bpy.context.view_layer.objects.active

Get cube data

mesh = obj.data vert_list = mesh.vertices

Get the color map

if mesh.vertex_colors: color_map = mesh.vertex_colors.active else: color_map = mesh.vertex_colors.new()

apply colors

i = 0 for poly in mesh.polygons: for idx in poly.loop_indices: #vertices loop = mesh.loops[idx] v = vert_list[loop.vertex_index]

    z_coord = v.co.z

    # Paint bottom 4 vertices 0.25, top 4 vertices 0.75.
    if z_coord < 0:
        color_map.data[i].color = (0.25, 0.25, 0.25, 0)
    else:
        color_map.data[i].color = (0.75, 0.75, 0.75, 0)
    i += 1

Give it this new material

mat = bpy.data.materials.new('vcolor_material')

Deactivate shadows

mat.shadow_method = 'NONE'

Apply material

mesh.materials.append(mat)

Set Flat lighting and show Vertex Coloring

my_areas = bpy.context.workspace.screens[0].areas my_shading = 'WIREFRAME' for area in my_areas: for space in area.spaces: if space.type == 'VIEW_3D': space.shading.light = "FLAT" space.shading.color_type = "VERTEX"

bpy.ops.object.mode_set(mode='VERTEX_PAINT')

print("\nRan succesfully.\n")

Math_Max
  • 183
  • 10
  • Where "0.56" comes from here? I mean, by which way do you have it? – lemon Aug 23 '20 at 16:01
  • @lemon In Blender: I go into 'Vertex Paint' mode. I hover my cursor on the middle of the box (i.e. where Z=0). I use the hotkey S to sample the color at this point. Then I check the RGB values of this color. – Math_Max Aug 23 '20 at 16:13
  • If you loop cut the cube, you'll obtain 0.501960813999176 for the points on the cut. This is due to 8 bits storage as mentioned in your question. As 0.5 * 255 gives 128 (stored in 8 bits) which is back to 0.501960... between 0 and 1. – lemon Aug 23 '20 at 16:21
  • 1
    FYI: https://devtalk.blender.org/t/full-precision-float-vertex-colors/4706/6 – lemon Aug 23 '20 at 16:39
  • @lemon Loopcutting or subdividing does not actually give the points on the middle 0.5 for me (blender 2.83). – Math_Max Aug 23 '20 at 17:12
  • @lemon could it also have something to do with https://stackoverflow.com/questions/22445925/opengl-vertex-color-interpolations – batFINGER Aug 23 '20 at 17:20
  • @Math_Max, what do you mean by "does not actually give the points on the middle"? – lemon Aug 25 '20 at 07:09

2 Answers2

6

The gradient is linear but not in the colourspace you are expecting - as evidenced by the following :

linear gradient from vertex colour blend

Here the Attribute node is making the Vertex Color (Col) available to the shader. This is split into separate RGB components and compared with the Generated 'X' coordinate (which runs from 0.0 at the left to 1.0 at the right of the cube).

The gradient is linear but the end points are not as you expect (ie, the blue vertical grid-line is at X=0.5 and the gradient line runs from about 0.05 at the bottom through to around 0.525 at the top). This is due to sRGB colourspace conversion.

Your values of 0.25 and 0.75 relate to those 'absolute' values of approx 0.05 and 0.525 when converted from sRGB into the 'true' brightness - the sRGB colourspace is intended to make the most of an 0-bit integer representation of the brightness to make it as visually diverse as possible - so values at the start/end of the range and compressed/expanded to suit what would be noticable when displayed on a screen to the human eye.

See https://en.wikipedia.org/wiki/SRGB and note the red line on the following graph :

sRGB curve

Using the axis values at the bottom and right of the graph you can see that 0.75 on the x-axis converts to around 0.5 in 'intensity' - your Python code is applying the sRGB value of 0.75 and producing an 'intensity' of approximately 0.525 for the upper limit. Similarly, the lower 0.25 is being converted at the bottom end of the graph to a much smaller 'intensity' value. That 'intensity' is what is being linearly interpolated to produce your resultant vertex colour gradient - not your original sRGB values.

Rich Sedman
  • 44,721
  • 2
  • 105
  • 222
  • Thanks. Just to verify. The vertex colors are stored in the optimized colourspace: sRGB. But to calculate the gradient, Blender performs an expansion to go from sRGB to linear. So 0.75 becomes ~0.525; 0.25 becomes ~0.05. The linear color-gradient line then goes from 0.05 to 0.525. The mid-point of this line is then (0.525-0.05) / 2 = 0.26. This value is Linear, so it needs to go back to sRGB for me to perceive. So the mid-point goes from 0.26 (Linear) to ~0.56 (sRGB) =/= 0.5. So now my question is: how can I stop Blender from expanding/compressing to/from Linear when calculating the gradient. – Math_Max Aug 25 '20 at 11:09
  • 2
    Yes - I believe that's exactly it. As far as how to prevent it, with Vertex Colours I don't think you can - since they are implicitly storing sRGB colour data. I'm sure I saw a suggestion somewhere to store the details in a UV map instead. A UV map is effectively the same except that it's only two channels (UV instead of RGB) and is held as 32-bit floats instead of 8-bit sRGB - and UVs don't have any color conversion, of course. – Rich Sedman Aug 25 '20 at 11:16
  • Ok, I will look into that. Although I will need to use RGB values for my project. Also, I am not sure UV mapping has automatic interpolation as well? If the UV map does not work out, I think there are two other options. First, changing the Blender source code to store vertex colors linearly. Or, second, do some post-processing. – Math_Max Aug 25 '20 at 12:55
  • 1
    UV should be fine with interpolation as it's effectively the same as used for texturing (it's just that you'd have encoded colors rather than UV coordinates). I don't think changing the source code will be practical - although I did read somewhere they are looking to convert from 8-bit to 32-bit float for vertex colours anyway (could check on progress of that if you can find reference to it). Also found these related questions that might be of use : https://blender.stackexchange.com/questions/87576 – Rich Sedman Aug 25 '20 at 13:05
  • 1
    https://blender.stackexchange.com/a/87978, https://blender.stackexchange.com/a/133925, https://blender.stackexchange.com/q/79311 – Rich Sedman Aug 25 '20 at 13:06
1

If you only need grayscale data to be used in shader nodes, here's an ugly workaround - use alpha channel instead. In your script I set the alpha values to 0.25 and 0.75 respectively and using a greater than 0.5 node visually confirmed that the alpha channel is interpolated lineary.

Perhaps other attributes could be used as well, like UV.

Description

I'm afraid a cononical explanation is beyond me, but here's a thread that discusses the issue. The workaround is based on the linked answer

https://github.com/KhronosGroup/glTF-Blender-IO/issues/542#issuecomment-569361267

uvnoob
  • 436
  • 2
  • 8
  • Thanks. Unfortunately, I need to work with RGB values. I simplified the scenario to be grayscale for the purpose of this question. – Math_Max Aug 25 '20 at 11:11