# Vertex color gradient is not linear

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')

# Apply material
mesh.materials.append(mat)

# Set Flat lighting and show Vertex Coloring
my_areas = bpy.context.workspace.screens[0].areas
for area in my_areas:
for space in area.spaces:
if space.type == 'VIEW_3D':

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

print("\nRan succesfully.\n")

• Where "0.56" comes from here? I mean, by which way do you have it? 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. 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. Aug 23 '20 at 16:21
• 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). Aug 23 '20 at 17:12

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

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 :

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.

• 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. Aug 25 '20 at 11:09
• 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. 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. Aug 25 '20 at 12:55
• 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 : blender.stackexchange.com/questions/87576 Aug 25 '20 at 13:05
• Aug 25 '20 at 13:06

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.

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

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