Source code for blenderproc.python.material.Dust

"""Provides the functionality to add a dust effect to a material."""

import warnings
from typing import List
import random

import bpy

from blenderproc.python.types.MaterialUtility import Material


[docs] def add_dust(material: Material, strength: float, texture_nodes: List[bpy.types.Texture] = None, texture_scale: float = 0.1): """ Adds a dust film to the material, where the strength determines how much dust is used. This will be added right before the output of the material. :param material: Used material :param strength: This determines the strength of the dust, 0 means no dust 1.0 means full dust. Values above 1.0 are possible, but create a thick film out of dust, which hides the material completely. :param texture_nodes: If a specific dust texture should be used, this can be specified. If this is empty a random noise texture is generated. :param texture_scale: This scale is used to scale down the used noise texture (even for the case where a random noise texture is used). """ group_node = material.new_node("ShaderNodeGroup") group_node.width = 250 group = bpy.data.node_groups.new(name="Dust Material", type="ShaderNodeTree") group_node.node_tree = group nodes, links = group.nodes, group.links # define start locations and differences, to make the debugging easier x_pos, x_diff = -(250 * 4), 250 y_pos, y_diff = (x_diff * 1), x_diff # Extract the normal for the current material location geometry_node = nodes.new('ShaderNodeNewGeometry') geometry_node.location.x = x_pos + x_diff * 0 geometry_node.location.y = y_pos # this node clips the values, to avoid negative values in the usage below clip_mix_node = nodes.new("ShaderNodeMixRGB") clip_mix_node.inputs["Fac"].default_value = 1.0 clip_mix_node.use_clamp = True clip_mix_node.location.x = x_pos + x_diff * 1 clip_mix_node.location.y = y_pos links.new(geometry_node.outputs["Normal"], clip_mix_node.inputs["Color2"]) # use only the z component separate_z_normal = nodes.new("ShaderNodeSeparateRGB") separate_z_normal.location.x = x_pos + x_diff * 2 separate_z_normal.location.y = y_pos links.new(clip_mix_node.outputs["Color"], separate_z_normal.inputs["Image"]) # this layer weight adds a small fresnel around the object, which makes it more realistic layer_weight = nodes.new("ShaderNodeLayerWeight") layer_weight.location.x = x_pos + x_diff * 2 layer_weight.location.y = y_pos - y_diff * 1 layer_weight.inputs["Blend"].default_value = 0.5 # combine it with the z component mix_with_layer_weight = nodes.new("ShaderNodeMixRGB") mix_with_layer_weight.location.x = x_pos + x_diff * 3 mix_with_layer_weight.location.y = y_pos mix_with_layer_weight.inputs["Fac"].default_value = 0.2 links.new(separate_z_normal.outputs["B"], mix_with_layer_weight.inputs["Color1"]) links.new(layer_weight.outputs["Facing"], mix_with_layer_weight.inputs["Color2"]) # add a gamma node, to scale the colors correctly gamma_node = nodes.new("ShaderNodeGamma") gamma_node.location.x = x_pos + x_diff * 4 gamma_node.location.y = y_pos gamma_node.inputs["Gamma"].default_value = 2.2 links.new(mix_with_layer_weight.outputs["Color"], gamma_node.inputs["Color"]) # use an overlay node to combine it with the texture result overlay = nodes.new("ShaderNodeMixRGB") overlay.location.x = x_pos + x_diff * 5 overlay.location.y = y_pos overlay.blend_type = "OVERLAY" overlay.inputs["Fac"].default_value = 1.0 links.new(gamma_node.outputs["Color"], overlay.inputs["Color1"]) # add a multiply node to scale down or up the strength of the dust multiply_node = nodes.new("ShaderNodeMath") multiply_node.location.x = x_pos + x_diff * 6 multiply_node.location.y = y_pos multiply_node.inputs[1].default_value = strength multiply_node.operation = "MULTIPLY" links.new(overlay.outputs["Color"], multiply_node.inputs[0]) # add texture coords to make the scaling of the dust texture possible texture_coords = nodes.new("ShaderNodeTexCoord") texture_coords.location.x = x_pos + x_diff * 0 texture_coords.location.y = y_pos - y_diff * 2 mapping_node = nodes.new("ShaderNodeMapping") mapping_node.location.x = x_pos + x_diff * 1 mapping_node.location.y = y_pos - y_diff * 2 mapping_node.vector_type = "TEXTURE" scale_value = texture_scale mapping_node.inputs["Scale"].default_value = [scale_value] * 3 links.new(texture_coords.outputs["UV"], mapping_node.inputs["Vector"]) # check if a texture should be used if texture_nodes is not None and texture_nodes: texture_node = nodes.new("ShaderNodeTexImage") texture_node.location.x = x_pos + x_diff * 2 texture_node.location.y = y_pos - y_diff * 2 texture_node.image = random.choice(texture_nodes).image links.new(mapping_node.outputs["Vector"], texture_node.inputs["Vector"]) links.new(texture_node.outputs["Color"], overlay.inputs["Color2"]) else: if not texture_nodes: warnings.warn("No texture was found, check the config. Random generated texture is used.") # if no texture is used, we great a random noise pattern, which is used instead noise_node = nodes.new("ShaderNodeTexNoise") noise_node.location.x = x_pos + x_diff * 2 noise_node.location.y = y_pos - y_diff * 2 # this determines the pattern of the dust flakes, a high scale makes them small enough to look like dust noise_node.inputs["Scale"].default_value = 250.0 noise_node.inputs["Detail"].default_value = 0.0 noise_node.inputs["Roughness"].default_value = 0.0 noise_node.inputs["Distortion"].default_value = 1.9 links.new(mapping_node.outputs["Vector"], noise_node.inputs["Vector"]) # this noise_node produces RGB colors, we only need one value in this instance red separate_r_channel = nodes.new("ShaderNodeSeparateRGB") separate_r_channel.location.x = x_pos + x_diff * 3 separate_r_channel.location.y = y_pos - y_diff * 2 links.new(noise_node.outputs["Color"], separate_r_channel.inputs["Image"]) # as the produced noise image has a nice fading to it, we use a color ramp to create dust flakes color_ramp = nodes.new("ShaderNodeValToRGB") color_ramp.location.x = x_pos + x_diff * 4 color_ramp.location.y = y_pos - y_diff * 2 color_ramp.color_ramp.elements[0].position = 0.4 color_ramp.color_ramp.elements[0].color = [1, 1, 1, 1] color_ramp.color_ramp.elements[1].position = 0.46 color_ramp.color_ramp.elements[1].color = [0, 0, 0, 1] links.new(separate_r_channel.outputs["R"], color_ramp.inputs["Fac"]) links.new(color_ramp.outputs["Color"], overlay.inputs["Color2"]) # combine the previous color with the new dust mode mix_shader = nodes.new("ShaderNodeMixShader") mix_shader.location = (x_pos + x_diff * 8, y_pos) links.new(multiply_node.outputs["Value"], mix_shader.inputs["Fac"]) # add a bsdf node for the dust, this will be used to actually give the dust a color dust_color = nodes.new("ShaderNodeBsdfPrincipled") dust_color.location = (x_pos + x_diff * 6, y_pos - y_diff) # the used dust color is a grey with a tint in orange dust_color.inputs["Base Color"].default_value = [0.8, 0.773, 0.7, 1.0] dust_color.inputs["Roughness"].default_value = 1.0 dust_color.inputs["Specular"].default_value = 0.0 links.new(dust_color.outputs["BSDF"], mix_shader.inputs[2]) # create the input and output nodes inside of the group group_output = nodes.new("NodeGroupOutput") group_output.location = (x_pos + x_diff * 9, y_pos) group_input = nodes.new("NodeGroupInput") group_input.location = (x_pos + x_diff * 7, y_pos - y_diff * 0.5) # create sockets for the outside of the group match them to the mix shader group.outputs.new(mix_shader.outputs[0].bl_idname, mix_shader.outputs[0].name) group.inputs.new(mix_shader.inputs[1].bl_idname, mix_shader.inputs[1].name) group.inputs.new(multiply_node.inputs[1].bl_idname, "Dust strength") group.inputs.new(mapping_node.inputs["Scale"].bl_idname, "Texture scale") # link the input and output to the mix shader links.new(group_input.outputs[0], mix_shader.inputs[1]) links.new(mix_shader.outputs[0], group_output.inputs[0]) links.new(group_input.outputs["Dust strength"], multiply_node.inputs[1]) links.new(group_input.outputs["Texture scale"], mapping_node.inputs["Scale"]) # remove the connection between the output and the last node and put the mix shader in between node_connected_to_the_output, material_output = material.get_node_connected_to_the_output_and_unlink_it() # place the group node above the material output group_node.location = (material_output.location.x - x_diff, material_output.location.y + y_diff) # connect the dust group material.link(node_connected_to_the_output.outputs[0], group_node.inputs[0]) material.link(group_node.outputs[0], material_output.inputs["Surface"]) # set the default values group_node.inputs["Dust strength"].default_value = strength group_node.inputs["Texture scale"].default_value = [texture_scale] * 3