O gradiente de cor do vértice não é linear

Aug 21 2020

Estou pintando o cubo padrão por meio da coloração de vértices. Eu pinto os quatro vértices inferiores como (0,25, 0,25, 0,25) e os quatro vértices superiores como (0,75, 0,75, 0,75).

Quando eu inspeciono o resultado no modo VERTEX PAINT (usando a tecla de atalho S para obter a cor), a parte inferior do cubo é de fato (0,25, 0,25, 0,25) e a parte superior é (0,75, 0,75, 0,75).

No entanto, o ponto médio do cubo não é (0,5, 0,5, 0,5), é ~ (0,56, 0,56, 0,56). De alguma forma, o gradiente não é linear. Não acho que seja um problema com sRGB vs Linear, porque, caso contrário, os valores inferior e superior também estariam errados, mas são apenas os valores intermediários. Então, qual é o problema aqui? Eu realmente preciso de um gradiente linear.

EDITAR. questão relacionada:https://developer.blender.org/T71835 Isso diz que tem algo a ver com como as cores dos vértices são armazenadas (sRGB de 8 bits aparentemente).

Código para reproduzir este problema:

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

Respostas

5 RichSedman Aug 25 2020 at 16:03

O gradiente é linear, mas não no espaço de cores que você espera - conforme evidenciado pelo seguinte:

Aqui, o nó de atributo está disponibilizando a cor do vértice (Col) para o sombreador. Isso é dividido em componentes RGB separados e comparado com a coordenada 'X' gerada (que vai de 0,0 à esquerda a 1,0 à direita do cubo).

O gradiente é linear, mas os pontos finais não são como você espera (ou seja, a linha de grade vertical azul está em X = 0,5 e a linha de gradiente vai de cerca de 0,05 na parte inferior até cerca de 0,525 na parte superior). Isso ocorre devido à conversão de espaço de cores sRGB.

Seus valores de 0,25 e 0,75 se relacionam com os valores "absolutos" de aproximadamente 0,05 e 0,525 quando convertidos de sRGB para o brilho "verdadeiro" - o espaço de cores sRGB se destina a aproveitar ao máximo uma representação inteira de 0 bits do brilho a ser feito é o mais visualmente diverso possível - portanto, valores no início / fim da faixa e compactados / expandidos para se adequar ao que seria perceptível quando exibido em uma tela ao olho humano.

Vejo https://en.wikipedia.org/wiki/SRGB e observe a linha vermelha no gráfico a seguir:

Usando os valores do eixo na parte inferior e direita do gráfico, você pode ver que 0,75 no eixo x converte para cerca de 0,5 em 'intensidade' - seu código Python está aplicando o valor sRGB de 0,75 e produzindo uma 'intensidade' de aproximadamente 0,525 para o limite superior. Da mesma forma, o menor 0,25 está sendo convertido na extremidade inferior do gráfico para um valor de 'intensidade' muito menor. Essa 'intensidade' é o que está sendo interpolado linearmente para produzir o gradiente de cor do vértice resultante - não seus valores originais de sRGB.

1 uvnoob Aug 25 2020 at 13:32

Se você só precisa de dados em tons de cinza para serem usados ​​em nós de sombreamento, aqui está uma solução alternativa - use o canal alfa. Em seu script, defini os valores alfa para 0,25 e 0,75 respectivamente e, usando um greater than 0.5nó, confirmei visualmente que o canal alfa é interpolado linearmente.

Talvez outros atributos possam ser usados ​​também, como UV.

Receio que uma explicação cônica esteja além de mim, mas aqui está um tópico que discute o problema. A solução alternativa é baseada na resposta vinculada

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