Drive the viewport display color

edited April 22 in General

The following video shows how to 'drive' an object's viewport display color by its base color.
https://www.dropbox.com/scl/fi/ucdvh12ak1trpcipbmbcp/2024-04-22_06-56-25.mp4?rlkey=a3kseg8n8722rd7nmgzga99dy&dl=0

Is there a way, however, to 'drive' an object's viewport display color by the result color from a 'Combine Color' node, for example? Basically want to average all the colors of a texture, and apply that color to the viewport display color.

... but still have the texture viewable in 'Rendered' or 'Material Preview' mode.

Comments

  • cool!... will give it a spin.

    stefanm2
  • hmm... doesn't seem to work on my side (blender 4.1). It just sets the viewport display color to white.

    stefanm2
  • :-( sry

  • @theoryshaw said:
    hmm... doesn't seem to work on my side (blender 4.1). It just sets the viewport display color to white.

    any errors?

  • edited April 24

    Hey I'll expand a bit on my BSE answer and try and answer the concerns here.

    What the addon does if I understood correctly is aggregate the default values of the color input of all the principled bsdfs of each material, and then set the viewport color accordingly. It does not work if you plug in a noodle coming from a texture node in the color input of the principled bsdf, so it will never work if you're using a PBR based workflow. The reason is because plugging in the noodle tells the node to ditch the default value and wait for the previous node to evaluate its output. For technical and optimization reasons this information is not available to the API. You can see the noodles as a pipeline, and you can only tweak the leftmost "default" values and you can only access the rightmost output from the material, and even then only if you bake the result to a texture. The Blender version won't change this fact, sorry.

    The information is not statically available. You need to actively analyse the data from the texture map if you want to achieve your goal. I think the simplest way is as described in my answer, read the pixel colors and average them. This could be turned into an addon but I advise against running this script continuously, even if foreach_get is extremely fast, the information is not bound to change until the texture map itself changes so a button seems more reasonable.

    import bpy
    import numpy as np
    
    img = bpy.data.images["Image.png"]  # Case sensitive
    x, y = img.size
    pixels = np.empty(x * y << 2, dtype=np.float32)
    img.pixels.foreach_get(pixels)
    mean_color_rgb = np.mean(pixels.reshape((x, y, 4))[:, :, :3], axis=(0, 1))  # Don't average alpha value
    
    mat = bpy.data.materials["Material"]  # Case sensitive
    mat.diffuse_color = list(mean_color_rgb) + [1]  # Add alpha value to the mean
    

    Cheers

    theoryshawstefanm2
  • Thanks @Gorgious !
    Let's say you have a texture that is predominantly a red color, and you use a blue color in a 'color ramp' node.
    Is there a clever way to set the viewport display color as the resultant purple color? It would seem not, from your explanation above, but wanted to ask just in case.

  • edited April 25

    I guess i should say something like a 'mix node', not a 'color ramp'--same concept though.
    Something like the following example. Possible to get the average color at .5 factor, for example?


    ...

  • edited April 25

    Everything is possible in Blender :) The only problem is how to tackle the issue. For very specific scenarii you'll need to write specific scripts. I omitted try/except and if/else clauses so this will throw error on other materials.

    import bpy
    import numpy as np
    
    
    def get_mean_color(img):
        x, y = img.size
        pixels = np.empty(x * y << 2, dtype=np.float32)
        img.pixels.foreach_get(pixels)
        mean_color_rgb = np.mean(pixels.reshape((x, y, 4))[:, :, :3], axis=(0, 1))  # Don't average alpha value
    
        return mean_color_rgb
    
    
    obj = bpy.context.active_object
    mat = obj.data.materials[obj.active_material_index]
    
    principled_bsdf = next(n for n in mat.node_tree.nodes if n.type == "BSDF_PRINCIPLED")
    color_socket = principled_bsdf.inputs["Base Color"]
    mix_node = color_socket.links[0].from_node
    factor = mix_node.inputs["Factor"].default_value
    texture_node_a = mix_node.inputs["A"].links[0].from_node
    texture_node_b = mix_node.inputs["B"].links[0].from_node
    image_a = texture_node_a.image
    image_b = texture_node_b.image
    image_a_mean_color = get_mean_color(image_a)
    image_b_mean_color = get_mean_color(image_b)
    
    mat.diffuse_color = [
        image_a_mean_color[0] * (1-factor) + image_b_mean_color[0] * factor,
        image_a_mean_color[1] * (1-factor) + image_b_mean_color[1] * factor,
        image_a_mean_color[2] * (1-factor) + image_b_mean_color[2] * factor,
        1
    ]
    

    While I was writing this I noticed that the material icon in the material list updated when you changed the factor. There might be a way to tap into the material icon system to get the icon color. I unfortunately don't know much about it but that's a lead. I think the icons are dynamically re-rendered at very low resolution every time you change the material.

  • For very specific scenario you'll need to write specific scripts.

    Yeah, that would be less than ideal, as the shader node setup changes a lot, from one material definition to the next.

    There might be a way to tap into the material icon system to get the icon color.

    That would be awesome.

  • Whoops, i got reprimanded by the development coordinator. Guess it wasn't a 'developer-enough' question. :)

    CoenNigel
  • Hehe, people can be a bit blunt, don't get discouraged. :)

Sign In or Register to comment.