Vertex color assigned a wrong value

I am doing some vertex color task automation, and at the core of it is assignment of values to separate channels. So I’m assigning a value only to the red channel of the object, or copying AO only to blue one. The values have to be precise, as they they are used to define specific stuff in the shader later on.

The problem I ran into is that the value that is assigned in the code is not what the object ends up with in the end. Assign a 0.1, and it ends up 0.34, 0.5 end up 0.73. Is there some sort of gamma correction that happens automatically? Or there’s a conversion between linear space and srgb?

import bpy

# Create a new vcol channel
bpy.ops.geometry.color_attribute_add(name = 'Col', domain='CORNER', data_type='BYTE_COLOR')

# Assigning a value to a vcol channel
for ob in bpy.context.selected_objects:
    for poly in ob.data.polygons:
        # Loop through each vertex in the polygon
        for verts in poly.loop_indices:
            # Applying value to the first VC attribute, into the first (red) channel.
            ob.data.color_attributes[0].data[verts].color[0] = 0.1 # Change this value between 0-1 to test.

If anyone knows how to correct this, I’ll be really grateful, since I really need the values to be assigned without modification.

I would recommend using bmesh for this instead of the built in operator. My guess is that the operator is trying to help by interpolating the values from one call to the next and giving you imprecise results.

This advice goes for just about anything really- built in operator are made for specific things and using them in your own scripts can have unpredictable results. modify data directly whenever possible.

Ok, it looks like what I want can be done with bMesh module. Thanks for advice!
It seems to assign values correctly, and writing data by color channel can be achieved by splitting up the loop color into parts, then writing it back with a desired channel tweaked.
The code below assumes there’s an already existing color attribute. Then assigns a defined value in the green channel.

import bpy
import bmesh

bm = bmesh.new() # Create new bMesh entity
mesh = bpy.context.object.data # Get data of selected object
bm.from_mesh(mesh) # Fill the bMesh with data from selected object
clayer = bm.loops.layers.color[0] # Code assumes object has at least one color attribute

# Fill all verts with color
for face in bm.faces: 
    for loop in face.loops:
        loopcolor = loop[clayer]
        r = loopcolor[0]
        g = loopcolor[1]
        b = loopcolor[2]
        a = loopcolor[3]
        value = 0 # Your value here
        loop[clayer] = [r,value,b,a] # R,G,B,A , place value in required position
bm.to_mesh(mesh) # Get data back to original object
bm.free()

While the assigned values are correct (as tested outside of Blender on exported model), it seems like the color picker rounds up any value 0<…<0.01 to 0.01, and also adds 0.002 to any assigned value. I am assigning stuff like i/255, so 1/255 being ~0.003 means it shows up in color picker as 0.012 anyway. 0.5 ends up as 0.502. This is fine as I only care about the values being exported correctly to Unity.

Additionally, if you need to say bake AO only to blue, it can be achieved by baking it to a new temporary layer, then transferring the data to original layer with the code above, by assigning values from the temp layer to value variable.

clayer = bm.loops.layers.color[0] # Code assumes object has at least one color attribute
clayer_ao = bm.loops.layers.color[1] # Code assumes there's a second color attribute with baked AO
# Fill all verts with color
for face in bm.faces: 
    for loop in face.loops:
        loopcolor_ao = loop[clayer_ao]
        loopcolor = loop[clayer]
        r = loopcolor[0]
        g = loopcolor[1]
        b = loopcolor[2]
        a = loopcolor[3]
        value = loopcolor_ao[0]
        loop[clayer] = [r,g,value,a] # R,G,B,A
bm.to_mesh(mesh) # Get data back to original object
bm.free()