2
$\begingroup$

First time poster, long time lurker here.

I'm using a script in a vector drawing program that separates color fields into greyscale bitmaps, that can then be interpreted as areas with different shaders in a Blender material. These shaders can be metal, glitter, flip flop paint, so just using a texture doesn't work. I'm dealing with 1000s of designs, each needing different shaders from a limited but huge set. Automation to the rescue!

I've neatly lined up all the nodes, so the data can flow without obstruction, but I've run into a problem:

Every design only utilizes a few of the total amount of defined shaders, so at the moment I need to generate fully black 2x2 pixel textures for the unused shaders, otherwise the missing texture gets interpreted as being mid-grey all over.

Using all the available shaders. This never happens in practice

Providing only the needed bitmaps gives me this.

I'm using empty bitmaps to get the desired result. Unsatisfactory.

Is there a more elegant solution to handle this problem procedurally, from within the nodes system? That way I can render Blender's output from the command line without having to edit every single node tree by hand, or generate about 50 bitmaps for each design (instead of only the ones actually used).

Thanks in advance, Ron

The image below the result of CreeDorofl's offered solution. This trick just moves the problem one node further upstream, but when I use "linear light" instead of "add" mode it almost works. Almost, because it shrinks the shader area - it seems to influence the way the mask is interpreted, and when using a blurred texture, it becomes clear that it chops of the darker tones, turning them all black. Zooming in shows the way it shrinks the green shader area, and it means I can't use gradients in the mask. [Implementing CreeDorofl's solution]

$\endgroup$
  • $\begingroup$ if I understand the question right...when a texture is missing, you want a specific replacement texture (2x2 black pixel tiled) rather than the blender medium gray substitute? Maybe you could mix together two materials using a mixRGB mode. One of them will be your desired black color/image. The other will be something else (let's say a bright green image texture), using a blending mode that completely obscures the dark color. Let's say "add". So if the green image is present, you see all green on your object. But if it's missing, the underlying black texture shows instead. $\endgroup$ – CreeDorofl Jul 28 '18 at 16:48
  • 1
    $\begingroup$ Thanks very much for your suggestion. So close... See image added in the post above. $\endgroup$ – Ron Netgrazer Jul 28 '18 at 21:28
  • $\begingroup$ shoot, shoulda known it wouldn't be that easy. Only other thing I can think of is to add a coloramp node between maybe the linear light node and first image texture. Then use the color ramp to reset the boundary of when linear light cuts off color and turns it to black. I usually have to just experiment to figure out how to make coloramp do what I want. You're either moving the black slider all the way in towards the white, or vice versa. Not sure if it'll affect it the way I think. $\endgroup$ – CreeDorofl Jul 28 '18 at 21:53
  • $\begingroup$ You should automate it. Not just the import and render, also create the materials with python, so each time all the required nodes are created. $\endgroup$ – sambler Jul 29 '18 at 4:37
  • $\begingroup$ CreeDorofl, I've tried the colorramping, didn't work, and even if it did it would be too imprecise for my needs. Too bad. Thanks again! $\endgroup$ – Ron Netgrazer Jul 29 '18 at 8:26
0
$\begingroup$

The solution would be to automate creating the material nodes, that way you add the number of colours you want and then add the black filler at the end.

import bpy

obj = bpy.context.object
if obj.active_material is None:
    obj.active_material = bpy.data.materials.new('Material')

mat = obj.active_material
mat.use_nodes = True

mat_nodes = mat.node_tree.nodes
node_links = mat.node_tree.links
# remove everything except the output node
for n in mat_nodes:
    if n.type != 'OUTPUT_MATERIAL':
        mat_nodes.remove(n)

#  RGBA           red       green      blue
colour_list = [(1,0,0,1), (0,1,0,1), (0,0,1,1)]
data_imgs = bpy.data.images
# length should not be less than colour_list
img_list = [data_imgs['img.001'], data_imgs['img.002'],data_imgs['img.003']]

out_node = mat_nodes[0]
col_space = 200
padding = 40
prev_mix = None

for x in range(len(colour_list)):
    mix_node = mat_nodes.new('ShaderNodeMixShader')
    mix_node.location = (out_node.location.x - (mix_node.width+padding) - (col_space*x),
                        out_node.location.y)
    if x == 0:
        node_links.new(mix_node.outputs['Shader'], out_node.inputs['Surface'])
    else:
        node_links.new(mix_node.outputs['Shader'], prev_mix.inputs[1])
    prev_mix = mix_node

    img_node = mat_nodes.new('ShaderNodeTexImage')
    img_node.image = img_list[x]
    img_node.location = (mix_node.location.x - (img_node.width+padding),
                        mix_node.location.y + (img_node.height+padding+150))
    node_links.new(img_node.outputs['Color'], mix_node.inputs['Fac'])

    dif_node = mat_nodes.new('ShaderNodeBsdfDiffuse')
    dif_node.inputs['Color'].default_value = colour_list[x]
    dif_node.location = (mix_node.location.x - (dif_node.width+padding),
                        mix_node.location.y - (dif_node.height+padding))
    node_links.new(dif_node.outputs['BSDF'], mix_node.inputs[2])

fillin = mat_nodes.new('ShaderNodeBsdfDiffuse')
fillin.inputs['Color'].default_value = (0,0,0,1) # black
fillin.location = (prev_mix.location.x - (fillin.width+padding),
                    prev_mix.location.y)
node_links.new(fillin.outputs['BSDF'], prev_mix.inputs[1])
$\endgroup$
  • $\begingroup$ Amazing. Marking this "answered" eventhough I'm not quite sure how it even works. Need time to figure all this out, feeling more noobish than usual. Very greatful for your efforts, thanks again! $\endgroup$ – Ron Netgrazer Aug 2 '18 at 12:15
  • $\begingroup$ The for loop creates, positions and connects three nodes for each colour placed in the colour list, then after the loop the extra black diffuse node is created. If you look at one line at a time, you should be able to get an idea of what it is for. You just need to fill in the colour_list and img_list for each material, then the loop will create enough nodes for your colours. $\endgroup$ – sambler Aug 2 '18 at 14:22
  • $\begingroup$ That seems very useful! I should be able to cobble together some code myself that loads all suitable textures found in the same directory as the blend file so that there is something in the img_list to use. $\endgroup$ – Ron Netgrazer Aug 5 '18 at 11:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.