The canonical answer
There is no canonical solution for a robust all-encompassing baking in Blender.
Blender shaders cannot be distilled to a set of PBR maps without a lot of assumptions. The baking itself didn't get enough attention from the devs and suffers from fundamental issues and requires workarounds.
It is an extensive topic and would take hundreds of hours and thousands lines of code.
Your code
If it is not to cover some niche application and will not grow much further is flawed. It needs a refactor. It will break for many more reasons. It should not be a singleton. You should not code in the Text Editor. You should develop an API wrapper for the native nodes API. You should create a test suit for testing against many different blend files right a way.
The question
Subsequent darkening
Diffuse color is not the same as base color, so you can't bake it as diffuse and expect to get the base color.
https://projects.blender.org/blender/blender/issues/69826#issuecomment-397246
To get the exact Base Color
input color you need to bake it using the Emit
bake type.
A modified function to bake the Base Color
using an Emission
shader which you have to replace the bake
function from your blend file with. This prevents the color from darkening each bake.
def bake(self, obj):
self.bake_images = []
self.add_image_texture_for_baking(obj)
self.prev_uvmap_names = [uv_layer.name for uv_layer in obj.data.uv_layers]
self.create_uv_map_and_unwrap(obj)
nodes_to_delete = []
links_to_reconnect = []
for material_slot in obj.material_slots:
material = material_slot.material
if not material or not material.node_tree:
continue
for target in ('ALL', 'CYCLES', 'EEVEE'):
active_output = material.node_tree.get_output_node(target)
if active_output:
break
else:
continue
active_output_link = active_output.inputs[0].links[0]
from_node = active_output_link.from_node
# assuming it is ShaderNodeBsdfPrincipled and the first input is Base Color, also works with other shaders
if from_node.inputs[0].links:
shader_node_base_color_link = from_node.inputs[0].links[0]
base_color_output_socket = shader_node_base_color_link.from_socket
else:
_input_socket = from_node.inputs[0]
nodes = material.node_tree.nodes
if _input_socket.type in ('VALUE', 'INT'):
_node = nodes.new('ShaderNodeValue')
_node.outputs[0].default_value = _input_socket.default_value
nodes_to_delete.append(_node)
base_color_output_socket = _node.outputs[0]
elif _input_socket.type == 'RGBA':
_node = nodes.new('ShaderNodeRGB')
_node.outputs[0].default_value = _input_socket.default_value
nodes_to_delete.append(_node)
base_color_output_socket = _node.outputs[0]
elif _input_socket.type == 'VECTOR':
_node = nodes.new('ShaderNodeCombineXYZ')
_node.inputs['X'].default_value, _node.inputs['Y'].default_value, _node.inputs['Z'].default_value = tuple(_input_socket.__data__.default_value)
nodes_to_delete.append(_node)
base_color_output_socket = _node.outputs[0]
emission_node = material.node_tree.nodes.new(type='ShaderNodeEmission')
nodes_to_delete.append(emission_node)
links_to_reconnect.append((active_output_link.from_socket, active_output_link.to_socket))
material.node_tree.links.new(emission_node.outputs[0], active_output.inputs[0])
material.node_tree.links.new(base_color_output_socket, emission_node.inputs[0])
bpy.ops.object.bake(type='EMIT')
for node in nodes_to_delete:
nodes = node.id_data.nodes
nodes.remove(node)
for link in links_to_reconnect:
links = link[0].id_data.links
links.new(link[0], link[1])
for uvname in self.prev_uvmap_names:
obj.data.uv_layers.remove(obj.data.uv_layers[uvname])
self.setup_new_material_with_baked_texture(obj)
def add_image_texture_for_baking(self, obj):
D = self.data
self.bake_image = D.images.new(f"Image.{obj.name}", 200, 200)
print(f"Created temporary bake image: {self.bake_image}")
for material_slot in obj.material_slots:
material = material_slot.material
if not material or not material.node_tree:
continue
mat = material
nodes = mat.node_tree.nodes
for n in nodes:
n.select = False
tex_node = nodes.new(type='ShaderNodeTexImage')
tex_node.image = self.bake_image
tex_node.name = mat.name
tex_node.select = True
nodes.active = tex_node
Initial darkening
The first significant darkening happens due to how Cycles and EEVEE handle texture filtering. Your object's UVs are very small compared to the size of the pixels of the image used. Each UV island samples a very small part of a pixel so the difference in the texture filtering between Cycles and EEVEE becomes very apparent. You bake with Cycles so it the Cycles' texture filtering that is getting baked into the texture. If you switch the viewport renderer to Cycles you will see that the colors are the same.