654 lines
24 KiB
Python
654 lines
24 KiB
Python
"""
|
|
Copyright (C) 2023-2025 Lucas Roedel
|
|
https://lucasroedel.com
|
|
contato@lucasroedel.com
|
|
|
|
Created by Lucas Roedel Ribeiro
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
bl_info = {
|
|
"name": "Pixel Art Rendering (Eevee)",
|
|
"author": "Lucas Roedel",
|
|
"version": (3, 0),
|
|
"blender": (4, 3, 2),
|
|
"location": "3D Viewport > Side panel",
|
|
"description": "Pixel Art rendering addon using eevee. Based on the work of Mezaka",
|
|
"warning": "",
|
|
"doc_url": "",
|
|
"category": "Render",
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import bpy
|
|
|
|
|
|
|
|
########## Set Render Settings ##########
|
|
|
|
def render_settings(context):
|
|
|
|
bpy.data.scenes['Scene'].render.engine = 'BLENDER_EEVEE_NEXT'
|
|
bpy.data.scenes["Scene"].eevee.taa_render_samples = 1
|
|
bpy.data.scenes["Scene"].eevee.taa_samples = 1
|
|
bpy.data.scenes["Scene"].eevee.use_taa_reprojection = False
|
|
bpy.data.scenes["Scene"].eevee.use_gtao = True
|
|
bpy.data.scenes["Scene"].render.filter_size = 0.00
|
|
bpy.data.scenes["Scene"].render.use_freestyle = True
|
|
bpy.data.scenes["Scene"].render.line_thickness = 0.3
|
|
|
|
bpy.data.scenes["Scene"].render.resolution_x = 200
|
|
bpy.data.scenes["Scene"].render.resolution_y = 150
|
|
|
|
bpy.ops.scene.freestyle_color_modifier_add(type='MATERIAL')
|
|
bpy.data.linestyles["LineStyle"].thickness_position = 'INSIDE'
|
|
|
|
|
|
|
|
|
|
class PIXEL_ART_OT_render_settings(bpy.types.Operator):
|
|
"""Sets up blender with the correct settings for pixel art rendering"""
|
|
bl_idname = "render.render_settings"
|
|
bl_label = "Render Settings"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
render_settings(context)
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
########## Creates Simple Default Material ##########
|
|
|
|
def single_material(context):
|
|
|
|
# Generate Bayer Matrix 2x2
|
|
bayerMatrix = bpy.data.images.get("Bayer Matrix")
|
|
|
|
if bayerMatrix == None:
|
|
|
|
bayerMatrix = bpy.data.images.new("Bayer Matrix", 2, 2)
|
|
bayerMatrix.use_fake_user = True
|
|
bayerMatrix.pixels[0] = (0.75294)
|
|
bayerMatrix.pixels[1] = (0.75294)
|
|
bayerMatrix.pixels[2] = (0.75294)
|
|
bayerMatrix.pixels[4] = (0.25098)
|
|
bayerMatrix.pixels[5] = (0.25098)
|
|
bayerMatrix.pixels[6] = (0.25098)
|
|
bayerMatrix.pixels[12] = (0.50196)
|
|
bayerMatrix.pixels[13] = (0.50196)
|
|
bayerMatrix.pixels[14] = (0.50196)
|
|
|
|
bayerMatrix.filepath_raw = "/tmp/bayerMatrix.png"
|
|
bayerMatrix.file_format = 'PNG'
|
|
bayerMatrix.save()
|
|
|
|
# Creates a material with the name if it doesn' exist already
|
|
for material in bpy.data.materials:
|
|
if material.name == "PixelArt_Simple":
|
|
bpy.data.materials.remove(material)
|
|
material = bpy.data.materials.new(name = "PixelArt_Simple")
|
|
material.use_nodes = True
|
|
material.use_fake_user = True
|
|
|
|
materialOutput = None
|
|
for node in material.node_tree.nodes:
|
|
if node.type == 'OUTPUT_MATERIAL':
|
|
materialOutput = node
|
|
break
|
|
|
|
for node in material.node_tree.nodes:
|
|
if node.type == 'BSDF_PRINCIPLED':
|
|
material.node_tree.nodes.remove(node)
|
|
break
|
|
|
|
# Creates the emission shader node
|
|
emissionNode = material.node_tree.nodes.new(type = "ShaderNodeEmission")
|
|
emissionNode.location = (100,300)
|
|
material.node_tree.links.new(emissionNode.outputs[0], materialOutput.inputs[0])
|
|
|
|
# Creates the color ramp node
|
|
colorRampNode = material.node_tree.nodes.new(type = "ShaderNodeValToRGB")
|
|
colorRampNode.location = (-250,300)
|
|
material.node_tree.links.new(colorRampNode.outputs[0], emissionNode.inputs[0])
|
|
colorRampNode.color_ramp.interpolation = 'CONSTANT'
|
|
colorRampNode.color_ramp.elements.remove(colorRampNode.color_ramp.elements[1])
|
|
colorRampNode.color_ramp.elements.new(0.075)
|
|
colorRampNode.color_ramp.elements.new(0.225)
|
|
colorRampNode.color_ramp.elements.new(0.450)
|
|
colorRampNode.color_ramp.elements.new(0.800)
|
|
colorRampNode.color_ramp.elements[0].color = [0.191202, 0.033105, 0.063010, 1.000000]
|
|
colorRampNode.color_ramp.elements[1].color = [0.337164, 0.063010, 0.045186, 1.000000]
|
|
colorRampNode.color_ramp.elements[2].color = [0.603828, 0.138432, 0.049707, 1.000000]
|
|
colorRampNode.color_ramp.elements[3].color = [0.783538, 0.274677, 0.078187, 1.000000]
|
|
colorRampNode.color_ramp.elements[4].color = [0.955974, 0.473532, 0.090842, 1.000000]
|
|
|
|
# Creates the mixRGB soft light node
|
|
mixSoftLightNode = material.node_tree.nodes.new(type = "ShaderNodeMixRGB")
|
|
mixSoftLightNode.location = (-500, 138)
|
|
mixSoftLightNode.blend_type = 'SOFT_LIGHT'
|
|
mixSoftLightNode.inputs[0].default_value = 0.2
|
|
material.node_tree.links.new(mixSoftLightNode.outputs[0], colorRampNode.inputs[0])
|
|
|
|
# Creates the shader to RGB node
|
|
shaderToRgbNode = material.node_tree.nodes.new(type = "ShaderNodeShaderToRGB")
|
|
shaderToRgbNode.location = (-750, 250)
|
|
material.node_tree.links.new(shaderToRgbNode.outputs[0], mixSoftLightNode.inputs[1])
|
|
|
|
# Creates the principled BSDF node
|
|
bsdfNode = material.node_tree.nodes.new(type = "ShaderNodeBsdfPrincipled")
|
|
bsdfNode.location = (-1100, 500)
|
|
material.node_tree.links.new(bsdfNode.outputs[0], shaderToRgbNode.inputs[0])
|
|
|
|
# Creates the bayer texture node
|
|
bayerTexNode = material.node_tree.nodes.new(type = "ShaderNodeTexImage")
|
|
bayerTexNode.location = (-850, -250)
|
|
material.node_tree.links.new(bayerTexNode.outputs[0], mixSoftLightNode.inputs[2])
|
|
bayerTexNode.image = bayerMatrix
|
|
bayerTexNode.interpolation = 'Closest'
|
|
|
|
# Creates the multiply node
|
|
multiplyVector = material.node_tree.nodes.new(type = "ShaderNodeVectorMath")
|
|
multiplyVector.location = (-1050, -450)
|
|
multiplyVector.operation = 'MULTIPLY'
|
|
material.node_tree.links.new(multiplyVector.outputs[0], bayerTexNode.inputs[0])
|
|
|
|
# Creates the Texture Coordinate node
|
|
texCoordNode = material.node_tree.nodes.new(type = "ShaderNodeTexCoord")
|
|
texCoordNode.location = (-1250, -300)
|
|
material.node_tree.links.new(texCoordNode.outputs[5], multiplyVector.inputs[0])
|
|
|
|
# Creates the combineXYZ node
|
|
combineXyzNode = material.node_tree.nodes.new(type = "ShaderNodeCombineXYZ")
|
|
combineXyzNode.location = (-1250, -600)
|
|
material.node_tree.links.new(combineXyzNode.outputs[0], multiplyVector.inputs[1])
|
|
|
|
# Creates the Resolution nodes
|
|
resolutionXnode = material.node_tree.nodes.new(type = "ShaderNodeValue")
|
|
resolutionXnode.location = (-1450, -600)
|
|
resolutionXnode.label = "ResolutionX / 2"
|
|
material.node_tree.links.new(resolutionXnode.outputs[0], combineXyzNode.inputs[0])
|
|
|
|
resolutionXdriver = resolutionXnode.outputs['Value'].driver_add("default_value")
|
|
var1 = resolutionXdriver.driver.variables.new()
|
|
var1.name = "resolutionX"
|
|
var1.targets[0].id_type = 'SCENE'
|
|
var1.targets[0].id = bpy.data.scenes["Scene"]
|
|
var1.targets[0].data_path = "render.resolution_x"
|
|
resolutionXdriver.driver.expression = "resolutionX / 2"
|
|
|
|
resolutionYnode = material.node_tree.nodes.new(type = "ShaderNodeValue")
|
|
resolutionYnode.location = (-1450, -700)
|
|
resolutionYnode.label = "ResolutionY / 2"
|
|
material.node_tree.links.new(resolutionYnode.outputs[0], combineXyzNode.inputs[1])
|
|
|
|
resolutionYdriver = resolutionYnode.outputs['Value'].driver_add("default_value")
|
|
var2 = resolutionYdriver.driver.variables.new()
|
|
var2.name = "resolutionY"
|
|
var2.targets[0].id_type = 'SCENE'
|
|
var2.targets[0].id = bpy.data.scenes["Scene"]
|
|
var2.targets[0].data_path = "render.resolution_y"
|
|
resolutionYdriver.driver.expression = "resolutionY / 2"
|
|
|
|
|
|
|
|
|
|
class PIXEL_ART_OT_single_material(bpy.types.Operator):
|
|
"""Creates default pixel art material. If material with name 'PixelArt_Simple' already exists, resets it to default"""
|
|
bl_idname = "render.single_material"
|
|
bl_label = "Create/Reset Material"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
single_material(context)
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
########## Creates Multiple Lights Default Material ##########
|
|
|
|
def multiple_material(context):
|
|
|
|
# Generate Bayer Matrix 2x2
|
|
bayerMatrix = bpy.data.images.get("Bayer Matrix")
|
|
|
|
if bayerMatrix == None:
|
|
|
|
bayerMatrix = bpy.data.images.new("Bayer Matrix", 2, 2)
|
|
bayerMatrix.use_fake_user = True
|
|
bayerMatrix.pixels[0] = (0.75294)
|
|
bayerMatrix.pixels[1] = (0.75294)
|
|
bayerMatrix.pixels[2] = (0.75294)
|
|
bayerMatrix.pixels[4] = (0.25098)
|
|
bayerMatrix.pixels[5] = (0.25098)
|
|
bayerMatrix.pixels[6] = (0.25098)
|
|
bayerMatrix.pixels[12] = (0.50196)
|
|
bayerMatrix.pixels[13] = (0.50196)
|
|
bayerMatrix.pixels[14] = (0.50196)
|
|
|
|
bayerMatrix.filepath_raw = "/tmp/bayerMatrix.png"
|
|
bayerMatrix.file_format = 'PNG'
|
|
bayerMatrix.save()
|
|
|
|
# Creates a material with the name if it doesn' exist already
|
|
for material in bpy.data.materials:
|
|
if material.name == "PixelArt_MultipleLights":
|
|
bpy.data.materials.remove(material)
|
|
|
|
material = bpy.data.materials.new(name = "PixelArt_MultipleLights")
|
|
|
|
material.use_nodes = True
|
|
material.use_fake_user = True
|
|
|
|
materialOutput = None
|
|
for node in material.node_tree.nodes:
|
|
if node.type == 'OUTPUT_MATERIAL':
|
|
materialOutput = node
|
|
break
|
|
|
|
for node in material.node_tree.nodes:
|
|
if node.type == 'BSDF_PRINCIPLED':
|
|
material.node_tree.nodes.remove(node)
|
|
break
|
|
|
|
# Creates a group for the dithering part
|
|
for group in bpy.data.node_groups:
|
|
if group.name == 'Dithering':
|
|
bpy.data.node_groups.remove(group)
|
|
|
|
ditherGroup = bpy.data.node_groups.new('Dithering', 'ShaderNodeTree')
|
|
ditherGroup.interface.new_socket("Color", in_out='OUTPUT', socket_type="NodeSocketColor")
|
|
|
|
outputNode = ditherGroup.nodes.new("NodeGroupOutput")
|
|
outputNode.location = (0, 0)
|
|
|
|
# Creates the bayer texture node
|
|
bayerTexNode = ditherGroup.nodes.new(type = "ShaderNodeTexImage")
|
|
bayerTexNode.location = (-300, 0)
|
|
ditherGroup.links.new(bayerTexNode.outputs[0], outputNode.inputs[0])
|
|
bayerTexNode.image = bayerMatrix
|
|
bayerTexNode.interpolation = 'Closest'
|
|
|
|
# Creates the multiply node
|
|
multiplyVector = ditherGroup.nodes.new(type = "ShaderNodeVectorMath")
|
|
multiplyVector.location = (-500, -210)
|
|
multiplyVector.operation = 'MULTIPLY'
|
|
ditherGroup.links.new(multiplyVector.outputs[0], bayerTexNode.inputs[0])
|
|
|
|
# Creates the Texture Coordinate node
|
|
texCoordNode = ditherGroup.nodes.new(type = "ShaderNodeTexCoord")
|
|
texCoordNode.location = (-700, -100)
|
|
ditherGroup.links.new(texCoordNode.outputs[5], multiplyVector.inputs[0])
|
|
|
|
# Creates the combineXYZ node
|
|
combineXyzNode = ditherGroup.nodes.new(type = "ShaderNodeCombineXYZ")
|
|
combineXyzNode.location = (-700, -400)
|
|
ditherGroup.links.new(combineXyzNode.outputs[0], multiplyVector.inputs[1])
|
|
|
|
# Creates the Resolution nodes
|
|
resolutionXnode = ditherGroup.nodes.new(type = "ShaderNodeValue")
|
|
resolutionXnode.location = (-900, -400)
|
|
resolutionXnode.label = "ResolutionX / 2"
|
|
ditherGroup.links.new(resolutionXnode.outputs[0], combineXyzNode.inputs[0])
|
|
|
|
resolutionXdriver = resolutionXnode.outputs['Value'].driver_add("default_value")
|
|
var1 = resolutionXdriver.driver.variables.new()
|
|
var1.name = "resolutionX"
|
|
var1.targets[0].id_type = 'SCENE'
|
|
var1.targets[0].id = bpy.data.scenes["Scene"]
|
|
var1.targets[0].data_path = "render.resolution_x"
|
|
resolutionXdriver.driver.expression = "resolutionX / 2"
|
|
|
|
resolutionYnode = ditherGroup.nodes.new(type = "ShaderNodeValue")
|
|
resolutionYnode.location = (-900, -500)
|
|
resolutionYnode.label = "ResolutionY / 2"
|
|
ditherGroup.links.new(resolutionYnode.outputs[0], combineXyzNode.inputs[1])
|
|
|
|
resolutionYdriver = resolutionYnode.outputs['Value'].driver_add("default_value")
|
|
var2 = resolutionYdriver.driver.variables.new()
|
|
var2.name = "resolutionY"
|
|
var2.targets[0].id_type = 'SCENE'
|
|
var2.targets[0].id = bpy.data.scenes["Scene"]
|
|
var2.targets[0].data_path = "render.resolution_y"
|
|
resolutionYdriver.driver.expression = "resolutionY / 2"
|
|
|
|
|
|
# Creates the two mix RGB
|
|
|
|
emissionOutputNode = material.node_tree.nodes.new(type = "ShaderNodeEmission")
|
|
emissionOutputNode.location = (100, 300)
|
|
material.node_tree.links.new(emissionOutputNode.outputs[0], materialOutput.inputs[0])
|
|
|
|
mixShaderNode1 = material.node_tree.nodes.new(type = "ShaderNodeMixRGB")
|
|
mixShaderNode1.location = (-150, 300)
|
|
mixShaderNode1.blend_type = 'LIGHTEN'
|
|
material.node_tree.links.new(mixShaderNode1.outputs[0], emissionOutputNode.inputs[0])
|
|
|
|
mixShaderNode2 = material.node_tree.nodes.new(type = "ShaderNodeMixRGB")
|
|
mixShaderNode2.location = (-400, 100)
|
|
mixShaderNode2.blend_type = 'LIGHTEN'
|
|
material.node_tree.links.new(mixShaderNode2.outputs[0], mixShaderNode1.inputs[2])
|
|
|
|
|
|
### RED CHANNEL ###
|
|
# Creates the color ramp node
|
|
colorRampNode = material.node_tree.nodes.new(type = "ShaderNodeValToRGB")
|
|
colorRampNode.location = (-850,600)
|
|
material.node_tree.links.new(colorRampNode.outputs[0], mixShaderNode1.inputs[1])
|
|
colorRampNode.color_ramp.interpolation = 'CONSTANT'
|
|
colorRampNode.color_ramp.elements.remove(colorRampNode.color_ramp.elements[1])
|
|
colorRampNode.color_ramp.elements.new(0.01)
|
|
colorRampNode.color_ramp.elements.new(0.075)
|
|
colorRampNode.color_ramp.elements.new(0.225)
|
|
colorRampNode.color_ramp.elements.new(0.450)
|
|
colorRampNode.color_ramp.elements.new(0.800)
|
|
|
|
colorRampNode.color_ramp.elements[0].color = [0, 0, 0, 1.000000]
|
|
colorRampNode.color_ramp.elements[1].color = [0.191202, 0.033105, 0.063010, 1.000000]
|
|
colorRampNode.color_ramp.elements[2].color = [0.337164, 0.063010, 0.045186, 1.000000]
|
|
colorRampNode.color_ramp.elements[3].color = [0.603828, 0.138432, 0.049707, 1.000000]
|
|
colorRampNode.color_ramp.elements[4].color = [0.783538, 0.274677, 0.078187, 1.000000]
|
|
colorRampNode.color_ramp.elements[5].color = [0.955974, 0.473532, 0.090842, 1.000000]
|
|
|
|
# Creates the mixRGB soft light node
|
|
mixSoftLightNode = material.node_tree.nodes.new(type = "ShaderNodeMixRGB")
|
|
mixSoftLightNode.location = (-1100, 600)
|
|
mixSoftLightNode.blend_type = 'SOFT_LIGHT'
|
|
mixSoftLightNode.inputs[0].default_value = 0.2
|
|
material.node_tree.links.new(mixSoftLightNode.outputs[0], colorRampNode.inputs[0])
|
|
|
|
# Adds the dithering node group to the tree
|
|
ditherGroupNode = material.node_tree.nodes.new("ShaderNodeGroup")
|
|
ditherGroupNode.node_tree = ditherGroup
|
|
ditherGroupNode.location = (-1300, 600)
|
|
material.node_tree.links.new(ditherGroupNode.outputs[0], mixSoftLightNode.inputs[2])
|
|
|
|
### GREEN CHANNEL ###
|
|
# Creates the color ramp node
|
|
colorRampNodeG = material.node_tree.nodes.new(type = "ShaderNodeValToRGB")
|
|
colorRampNodeG.location = (-850,200)
|
|
material.node_tree.links.new(colorRampNodeG.outputs[0], mixShaderNode2.inputs[1])
|
|
colorRampNodeG.color_ramp.interpolation = 'CONSTANT'
|
|
colorRampNodeG.color_ramp.elements.remove(colorRampNodeG.color_ramp.elements[1])
|
|
colorRampNodeG.color_ramp.elements.new(0.01)
|
|
colorRampNodeG.color_ramp.elements.new(0.075)
|
|
colorRampNodeG.color_ramp.elements.new(0.225)
|
|
colorRampNodeG.color_ramp.elements.new(0.450)
|
|
colorRampNodeG.color_ramp.elements.new(0.800)
|
|
|
|
colorRampNodeG.color_ramp.elements[0].color = [0, 0, 0, 1.000000]
|
|
colorRampNodeG.color_ramp.elements[1].color = [0.011612, 0.102242, 0.074214, 1.000000]
|
|
colorRampNodeG.color_ramp.elements[2].color = [0.011612, 0.102242, 0.074214, 1.000000]
|
|
colorRampNodeG.color_ramp.elements[3].color = [0.016807, 0.496933, 0.168269, 1.000000]
|
|
colorRampNodeG.color_ramp.elements[4].color = [0.278894, 0.701102, 0.141263, 1.000000]
|
|
colorRampNodeG.color_ramp.elements[5].color = [0.603828, 0.730461, 0.149960, 1.000000]
|
|
|
|
# Creates the mixRGB soft light node
|
|
mixSoftLightNodeG = material.node_tree.nodes.new(type = "ShaderNodeMixRGB")
|
|
mixSoftLightNodeG.location = (-1100, 200)
|
|
mixSoftLightNodeG.blend_type = 'SOFT_LIGHT'
|
|
mixSoftLightNodeG.inputs[0].default_value = 0.2
|
|
material.node_tree.links.new(mixSoftLightNodeG.outputs[0], colorRampNodeG.inputs[0])
|
|
|
|
# Adds the dithering node group to the tree
|
|
ditherGroupNodeG = material.node_tree.nodes.new("ShaderNodeGroup")
|
|
ditherGroupNodeG.node_tree = ditherGroup
|
|
ditherGroupNodeG.location = (-1300, 200)
|
|
material.node_tree.links.new(ditherGroupNodeG.outputs[0], mixSoftLightNodeG.inputs[2])
|
|
|
|
### BLUE CHANNEL ###
|
|
# Creates the color ramp node
|
|
colorRampNodeB = material.node_tree.nodes.new(type = "ShaderNodeValToRGB")
|
|
colorRampNodeB.location = (-850,-100)
|
|
material.node_tree.links.new(colorRampNodeB.outputs[0], mixShaderNode2.inputs[2])
|
|
colorRampNodeB.color_ramp.interpolation = 'CONSTANT'
|
|
colorRampNodeB.color_ramp.elements.remove(colorRampNodeB.color_ramp.elements[1])
|
|
colorRampNodeB.color_ramp.elements.new(0.01)
|
|
colorRampNodeB.color_ramp.elements.new(0.075)
|
|
colorRampNodeB.color_ramp.elements.new(0.225)
|
|
colorRampNodeB.color_ramp.elements.new(0.450)
|
|
colorRampNodeB.color_ramp.elements.new(0.800)
|
|
|
|
colorRampNodeB.color_ramp.elements[0].color = [0, 0, 0, 1.000000]
|
|
colorRampNodeB.color_ramp.elements[1].color = [0.035601, 0.036889, 0.088656, 1.000000]
|
|
colorRampNodeB.color_ramp.elements[2].color = [0.068478, 0.070360, 0.181164, 1.000000]
|
|
colorRampNodeB.color_ramp.elements[3].color = [0.076185, 0.130137, 0.450786, 1.000000]
|
|
colorRampNodeB.color_ramp.elements[4].color = [0.076185, 0.323143, 0.783538, 1.000000]
|
|
colorRampNodeB.color_ramp.elements[5].color = [0.270498, 0.644480, 1.000000, 1.000000]
|
|
|
|
# Creates the mixRGB soft light node
|
|
mixSoftLightNodeB = material.node_tree.nodes.new(type = "ShaderNodeMixRGB")
|
|
mixSoftLightNodeB.location = (-1100, -100)
|
|
mixSoftLightNodeB.blend_type = 'SOFT_LIGHT'
|
|
mixSoftLightNodeB.inputs[0].default_value = 0.2
|
|
material.node_tree.links.new(mixSoftLightNodeB.outputs[0], colorRampNodeB.inputs[0])
|
|
|
|
# Adds the dithering node group to the tree
|
|
ditherGroupNodeB = material.node_tree.nodes.new("ShaderNodeGroup")
|
|
ditherGroupNodeB.node_tree = ditherGroup
|
|
ditherGroupNodeB.location = (-1300, -100)
|
|
material.node_tree.links.new(ditherGroupNodeB.outputs[0], mixSoftLightNodeB.inputs[2])
|
|
|
|
# Creates the shader to RGB node
|
|
shaderToRgbNode = material.node_tree.nodes.new(type = "ShaderNodeShaderToRGB")
|
|
shaderToRgbNode.location = (-2200, 0)
|
|
|
|
# Creates the separate color node
|
|
separateColorNode = material.node_tree.nodes.new(type = "ShaderNodeSeparateColor")
|
|
separateColorNode.location = (-2000, 0)
|
|
material.node_tree.links.new(shaderToRgbNode.outputs[0], separateColorNode.inputs[0])
|
|
material.node_tree.links.new(separateColorNode.outputs[0], mixSoftLightNode.inputs[1])
|
|
material.node_tree.links.new(separateColorNode.outputs[1], mixSoftLightNodeG.inputs[1])
|
|
material.node_tree.links.new(separateColorNode.outputs[2], mixSoftLightNodeB.inputs[1])
|
|
|
|
# Creates the principled BSDF node
|
|
bsdfNode = material.node_tree.nodes.new(type = "ShaderNodeBsdfPrincipled")
|
|
bsdfNode.location = (-2500, 0)
|
|
material.node_tree.links.new(bsdfNode.outputs[0], shaderToRgbNode.inputs[0])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PIXEL_ART_OT_multiple_material(bpy.types.Operator):
|
|
"""Creates pixel art material with multiple lights setup. If material with name 'PixelArt_MultipleLights' already exists, resets it to default"""
|
|
bl_idname = "render.multiple_material"
|
|
bl_label = "Create/Reset Material"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
multiple_material(context)
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
########## Creates Tri Lights Setup ##########
|
|
|
|
def lights_setup(context):
|
|
|
|
# Deletes previous light sources with the same name
|
|
for obj in bpy.data.objects:
|
|
if obj.name.startswith("PixelArt_Light_"):
|
|
bpy.data.objects.remove(obj, do_unlink = True)
|
|
|
|
for light in bpy.data.lights:
|
|
if light.name.startswith("PixelArt_Light_"):
|
|
bpy.data.lights.remove(light, do_unlink = True)
|
|
|
|
|
|
# Creates the red light
|
|
lightR = bpy.data.lights.new(name = "PixelArt_Light_R", type = 'POINT')
|
|
lightR.color = (1,0,0)
|
|
lightR.energy = 250
|
|
lightR_object = bpy.data.objects.new(name = "PixelArt_Light_R", object_data = lightR)
|
|
bpy.context.collection.objects.link(lightR_object)
|
|
lightR_object.location = (3.46, -0.41, 1.04)
|
|
|
|
# Creates the green light
|
|
lightG = bpy.data.lights.new(name = "PixelArt_Light_G", type = 'POINT')
|
|
lightG.color = (0,1,0)
|
|
lightG.energy = 250
|
|
lightG_object = bpy.data.objects.new(name = "PixelArt_Light_G", object_data = lightG)
|
|
bpy.context.collection.objects.link(lightG_object)
|
|
lightG_object.location = (-2.1, 2, 1.37)
|
|
|
|
# Creates the blue light
|
|
lightB = bpy.data.lights.new(name = "PixelArt_Light_B", type = 'POINT')
|
|
lightB.color = (0,0,1)
|
|
lightB.energy = 150
|
|
lightB_object = bpy.data.objects.new(name = "PixelArt_Light_B", object_data = lightB)
|
|
bpy.context.collection.objects.link(lightB_object)
|
|
lightB_object.location = (-0.06, -1.46, 2.18)
|
|
|
|
# Removes world light
|
|
bpy.data.worlds["World"].node_tree.nodes["Background"].inputs[1].default_value = 0
|
|
|
|
|
|
|
|
|
|
|
|
class PIXEL_ART_OT_lights_setup(bpy.types.Operator):
|
|
"""Creates a setup of three point lights to work with the multiple lights pixel art material"""
|
|
bl_idname = "render.lights_setup"
|
|
bl_label = "Tri Light Setup"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(self, context):
|
|
lights_setup(context)
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
########## Creating the UI panel ##########
|
|
|
|
class PIXEL_RENDER_PT_pixel_render_panel(bpy.types.Panel):
|
|
"""Creates a Panel for Pixel Rendering in the UI Panels"""
|
|
bl_label = "Pixel Render"
|
|
bl_idname = "PIXEL_RENDER_PT_pixel_render_panel"
|
|
bl_space_type = 'VIEW_3D'
|
|
bl_region_type = 'UI'
|
|
bl_category = "Pixel Render"
|
|
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
scene = context.scene
|
|
|
|
box = layout.box()
|
|
row = box.row()
|
|
row.alignment = 'CENTER'
|
|
row.label(text="Render Settings for Pixel Art", icon = "RESTRICT_RENDER_OFF")
|
|
row = box.row()
|
|
row = box.row()
|
|
row.scale_y = 1.5
|
|
row.operator("render.render_settings")
|
|
row = box.row()
|
|
row = layout.row()
|
|
|
|
box = layout.box()
|
|
row = box.row()
|
|
row.alignment = 'CENTER'
|
|
row.label(text="Default Pixel Art Material", icon = "MATERIAL")
|
|
row = box.row()
|
|
row = box.row()
|
|
row.scale_y = 1.5
|
|
row.operator("render.single_material")
|
|
row = box.row()
|
|
row = layout.row()
|
|
|
|
|
|
box = layout.box()
|
|
row = box.row()
|
|
row.alignment = 'CENTER'
|
|
row.label(text="Multiple Lights Material", icon = "NODE_MATERIAL")
|
|
row = box.row()
|
|
row = box.row()
|
|
row.scale_y = 1.5
|
|
row.operator("render.multiple_material")
|
|
row = box.row()
|
|
row.scale_y = 1
|
|
row.operator("render.lights_setup")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def register():
|
|
bpy.utils.register_class(PIXEL_ART_OT_render_settings)
|
|
bpy.utils.register_class(PIXEL_ART_OT_single_material)
|
|
bpy.utils.register_class(PIXEL_ART_OT_multiple_material)
|
|
bpy.utils.register_class(PIXEL_ART_OT_lights_setup)
|
|
bpy.utils.register_class(PIXEL_RENDER_PT_pixel_render_panel)
|
|
|
|
|
|
def unregister():
|
|
bpy.utils.register_class(PIXEL_ART_OT_render_settings)
|
|
bpy.utils.register_class(PIXEL_ART_OT_single_material)
|
|
bpy.utils.register_class(PIXEL_ART_OT_multiple_material)
|
|
bpy.utils.register_class(PIXEL_ART_OT_lights_setup)
|
|
bpy.utils.register_class(PIXEL_RENDER_PT_pixel_render_panel)
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
register() |