Skip to content

Instantly share code, notes, and snippets.

@supereggbert
Last active November 15, 2023 05:28
Show Gist options
  • Save supereggbert/acccdc0a380c7e6f4c0b5ff4302774fd to your computer and use it in GitHub Desktop.
Save supereggbert/acccdc0a380c7e6f4c0b5ff4302774fd to your computer and use it in GitHub Desktop.
Yet Another Parallax Mapping Addon
bl_info = {
"name": "Yet Anonther POM Addon",
"description": "Adds a Parallax Occlution Mapping Node to the shader editor",
"version": (1,0,0),
"blender": (3, 0, 0),
"category": "Material",
}
import bpy
from bpy.types import ShaderNodeCustomGroup, Image
import nodeitems_builtins
import nodeitems_utils
from nodeitems_utils import NodeItem
class POMUVNode(ShaderNodeCustomGroup):
'''Parallax Occlusion Map'''
bl_idname = 'pom_node'
bl_label = "Parallax Occlusion Map"
bl_width_default = 250
def updateprop(self,context):
if not self.built:
node_tree = bpy.context.space_data.edit_tree
outlinks={}
for key in self.outputs.keys():
for link in self.outputs[key].links:
if not key in outlinks:
outlinks[key]=[]
outlinks[key].append(link.to_socket)
inlinks={}
for key in self.inputs.keys():
if len(self.inputs[key].links)>0:
inlinks[key]=self.inputs[key].links[0].from_socket
self.buildTree(False)
for key in outlinks:
for value in outlinks[key]:
node_tree.links.new(self.outputs[key],value)
for key in inlinks:
node_tree.links.new(inlinks[key],self.inputs[key])
if self.image:
self.image.colorspace_settings.name = 'Non-Color'
images = [x for x in self.node_tree.nodes if x.type=='TEX_IMAGE']
for img in images:
img.image = self.image
uvtangnets = [x for x in self.node_tree.nodes if x.type=='UVMAP' or x.type=="TANGENT" or x.type=="NORMAL_MAP"]
for uv in uvtangnets:
uv.uv_map = self.uv
mixnodes = [x for x in self.node_tree.nodes if x.type=='MIX_RGB']
outputs = [x for x in self.node_tree.nodes if x.type=='GROUP_OUTPUT']
self.node_tree.links.new(mixnodes[self.samples-1].outputs['Color'],outputs[0].inputs["Vector"])
checkfactors = [x for x in self.node_tree.nodes if x.label=='checkfactor']
scalefactors = [x for x in self.node_tree.nodes if x.label=='scalefactor']
count = self.samples
stepsize_disp = 1 / count
stepsize_check = 1 / (count + 1)
mulnoise1 = [x for x in self.node_tree.nodes if x.label=='mulnoise1']
mulnoise2 = [x for x in self.node_tree.nodes if x.label=='mulnoise2']
mulnoise1[0].inputs[1].default_value=stepsize_disp
mulnoise2[0].inputs[0].default_value=stepsize_check
for i in range(1,count+1):
scalefactor = (count-i)*stepsize_disp
checkfactor = i*stepsize_check
checkfactors[i-1].inputs[0].default_value = checkfactor
scalefactors[i-1].inputs["Scale"].default_value = scalefactor
return
def init(self, context):
self.buildTree(True)
def buildTree(self,setValues):
if not setValues:
height_default_value=self.inputs["Height"].default_value
scale_default_value=self.inputs["Scale"].default_value
mrmax_default_value=self.inputs["Map Range Max"].default_value
mrmin_default_value=self.inputs["Map Range Min"].default_value
clipmax_default_value=self.inputs["Clip Max"].default_value
clipmin_default_value=self.inputs["Clip Min"].default_value
location_default_value=self.inputs["Location"].default_value
rotation_default_value=self.inputs["Rotation"].default_value
thetree = bpy.data.node_groups.new('.pomtree','ShaderNodeTree')
self.node_tree=thetree
treeoutput = thetree.nodes.new('NodeGroupOutput')
thetree.outputs.new('NodeSocketVector','Vector')
thetree.outputs.new('NodeSocketFloat','Alpha')
thetree.inputs.new('NodeSocketFloat','Height')
thetree.inputs.new('NodeSocketFloat','Map Range Min')
thetree.inputs.new('NodeSocketFloat','Map Range Max')
thetree.inputs.new('NodeSocketVector','Clip Min')
thetree.inputs.new('NodeSocketVector','Clip Max')
thetree.inputs.new('NodeSocketVector','Location')
thetree.inputs.new('NodeSocketVector','Scale')
thetree.inputs.new('NodeSocketFloat','Rotation')
if setValues:
self.inputs["Height"].default_value=0.1
self.inputs["Scale"].default_value=(1,1,1)
self.inputs["Map Range Max"].default_value=1
self.inputs["Map Range Min"].default_value=0
self.inputs["Clip Max"].default_value=(1,1,1)
else:
self.inputs["Height"].default_value=height_default_value
self.inputs["Scale"].default_value=scale_default_value
self.inputs["Map Range Max"].default_value=mrmax_default_value
self.inputs["Map Range Min"].default_value=mrmin_default_value
self.inputs["Clip Max"].default_value=clipmax_default_value
self.inputs["Clip Min"].default_value=clipmin_default_value
self.inputs["Location"].default_value=location_default_value
self.inputs["Rotation"].default_value=rotation_default_value
treeinput = thetree.nodes.new('NodeGroupInput')
height = thetree.nodes.new( type = 'ShaderNodeMath' )
height.operation = "MULTIPLY"
height.inputs[1].default_value=-1
thetree.links.new(treeinput.outputs["Height"],height.inputs[0])
rotation = thetree.nodes.new( type = 'ShaderNodeCombineXYZ' )
thetree.links.new(treeinput.outputs["Rotation"],rotation.inputs['Z'])
uvmap = thetree.nodes.new( type = 'ShaderNodeUVMap' )
x = thetree.nodes.new( type = 'ShaderNodeVectorMath' )
x.operation = 'DOT_PRODUCT'
y = thetree.nodes.new( type = 'ShaderNodeVectorMath' )
y.operation = 'DOT_PRODUCT'
binormal = thetree.nodes.new( type = 'ShaderNodeNormalMap' )
binormal.inputs['Color'].default_value[0]=0.5
binormal.inputs['Color'].default_value[1]=1
binormal.inputs['Color'].default_value[2]=0.5
tangent = thetree.nodes.new( type = 'ShaderNodeNormalMap' )
tangent.inputs['Color'].default_value[0]=1
tangent.inputs['Color'].default_value[1]=0.5
tangent.inputs['Color'].default_value[2]=0.5
z = thetree.nodes.new( type = 'ShaderNodeVectorMath' )
z.operation = 'DOT_PRODUCT'
combine = thetree.nodes.new( type = 'ShaderNodeCombineXYZ' )
divide1 = thetree.nodes.new( type = 'ShaderNodeMath' )
divide1.operation = "DIVIDE"
divide2 = thetree.nodes.new( type = 'ShaderNodeMath' )
divide2.operation = "DIVIDE"
geometry = thetree.nodes.new( type = 'ShaderNodeNewGeometry' )
thetree.links.new(tangent.outputs[0],x.inputs[0])
thetree.links.new(geometry.outputs["Incoming"],x.inputs[1])
thetree.links.new(geometry.outputs["Normal"],z.inputs[0])
thetree.links.new(geometry.outputs["Incoming"],z.inputs[1])
thetree.links.new(binormal.outputs[0],y.inputs[0])
thetree.links.new(geometry.outputs["Incoming"],y.inputs[1])
thetree.links.new(x.outputs["Value"],divide1.inputs[0])
thetree.links.new(z.outputs["Value"],divide1.inputs[1])
thetree.links.new(y.outputs["Value"],divide2.inputs[0])
thetree.links.new(z.outputs["Value"],divide2.inputs[1])
thetree.links.new(divide1.outputs[0],combine.inputs[0])
thetree.links.new(divide2.outputs[0],combine.inputs[1])
#invert for backfacing
backfacing = thetree.nodes.new( type = 'ShaderNodeMath' )
backfacing.operation = "MULTIPLY_ADD"
backfacing.inputs[1].default_value=-2
backfacing.inputs[2].default_value=1
thetree.links.new(geometry.outputs["Backfacing"],backfacing.inputs['Value'])
backscale = thetree.nodes.new( type = 'ShaderNodeVectorMath' )
backscale.operation = 'SCALE'
thetree.links.new(combine.outputs["Vector"],backscale.inputs['Vector'])
thetree.links.new(backfacing.outputs["Value"],backscale.inputs['Scale'])
combine = backscale
#end invert for backface
#scale vector
texscaleRotate = thetree.nodes.new( type = 'ShaderNodeMapping' )
thetree.links.new(combine.outputs[0],texscaleRotate.inputs['Vector'])
thetree.links.new(treeinput.outputs["Scale"],texscaleRotate.inputs['Scale'])
thetree.links.new(rotation.outputs["Vector"],texscaleRotate.inputs['Rotation'])
combine = texscaleRotate
#scale vector
initscale = thetree.nodes.new( type = 'ShaderNodeVectorMath' )
initscale.operation = 'SCALE'
thetree.links.new(combine.outputs[0],initscale.inputs[0])
thetree.links.new(height.outputs["Value"],initscale.inputs["Scale"])
vectoroutput = initscale.outputs[0]
#add offset
noise = thetree.nodes.new( type = 'ShaderNodeTexWhiteNoise' )
noise.noise_dimensions = '2D'
mulnoise = thetree.nodes.new( type = 'ShaderNodeMath' )
mulnoise.operation = "MULTIPLY"
thetree.links.new(noise.outputs['Value'],mulnoise.inputs[0])
mulnoise.label="mulnoise1"
mulnoise.inputs[1].default_value=1/32 # todo make this dynamic
thetree.links.new(uvmap.outputs['UV'],noise.inputs['Vector'])
stepoffset = thetree.nodes.new( type = 'ShaderNodeVectorMath' )
stepoffset.operation = 'SCALE'
thetree.links.new(mulnoise.outputs['Value'],stepoffset.inputs['Scale'])
thetree.links.new(vectoroutput,stepoffset.inputs['Vector'])
uvmapping = thetree.nodes.new( type = 'ShaderNodeMapping' )
thetree.links.new(uvmap.outputs['UV'],uvmapping.inputs['Vector'])
thetree.links.new(treeinput.outputs["Location"],uvmapping.inputs['Location'])
thetree.links.new(treeinput.outputs["Scale"],uvmapping.inputs['Scale'])
thetree.links.new(rotation.outputs["Vector"],uvmapping.inputs['Rotation'])
uvadd = thetree.nodes.new( type = 'ShaderNodeVectorMath' )
uvadd.operation = 'ADD'
#thetree.links.new(uvmap.outputs['UV'],uvadd.inputs[0])
thetree.links.new(uvmapping.outputs['Vector'],uvadd.inputs[0])
thetree.links.new(stepoffset.outputs['Vector'],uvadd.inputs[1])
#end add offset
#add height noise
mulnoise2 = thetree.nodes.new( type = 'ShaderNodeMath' )
mulnoise2.operation = "MULTIPLY"
thetree.links.new(noise.outputs['Value'],mulnoise2.inputs[1])
mulnoise2.inputs[0].default_value=1/33 # todo make this dynamic
mulnoise2.label="mulnoise2"
#end add height noise
#uv = uvmap.outputs[0]
uv = uvadd.outputs['Vector']
addbase = thetree.nodes.new( type = 'ShaderNodeVectorMath' )
addbase.operation = 'ADD'
thetree.links.new(initscale.outputs[0],addbase.inputs[0])
thetree.links.new(uv,addbase.inputs[1])
vectoroutput = addbase.outputs[0]
count = 32
stepsize_disp = 1 / count
stepsize_check = 1 / (count + 1)
for i in range(count,0,-1):
scalefactor = (i-1)*stepsize_disp
checkfactor = (count-i+1)*stepsize_check
image = thetree.nodes.new( type = 'ShaderNodeTexImage' )
#add map range
maprange = thetree.nodes.new( type = 'ShaderNodeMapRange' )
thetree.links.new(image.outputs["Color"],maprange.inputs['Value'])
thetree.links.new(treeinput.outputs["Map Range Min"],maprange.inputs['From Min'])
thetree.links.new(treeinput.outputs["Map Range Max"],maprange.inputs['From Max'])
greater = thetree.nodes.new( type = 'ShaderNodeMath' )
greater.operation = "GREATER_THAN"
addnoise = thetree.nodes.new( type = 'ShaderNodeMath' )
addnoise.operation = "SUBTRACT"
addnoise.inputs[0].default_value = checkfactor
addnoise.label="checkfactor"
thetree.links.new(mulnoise2.outputs["Value"],addnoise.inputs[1])
thetree.links.new(addnoise.outputs["Value"],greater.inputs[1])
mix = thetree.nodes.new( type = 'ShaderNodeMixRGB' )
scale = thetree.nodes.new( type = 'ShaderNodeVectorMath' )
scale.label="scalefactor"
scale.operation = "SCALE"
scale.inputs["Scale"].default_value = scalefactor
thetree.links.new(initscale.outputs[0],scale.inputs[0])
add = thetree.nodes.new( type = 'ShaderNodeVectorMath' )
add.operation = "ADD"
thetree.links.new(uv,add.inputs[0])
thetree.links.new(scale.outputs["Vector"],add.inputs[1])
thetree.links.new(add.outputs["Vector"],image.inputs["Vector"])
thetree.links.new(maprange.outputs["Result"],greater.inputs[0])
split = thetree.nodes.new( type = 'ShaderNodeSeparateXYZ' )
thetree.links.new(add.outputs["Vector"],split.inputs[0])
combine = thetree.nodes.new( type = 'ShaderNodeCombineXYZ' )
thetree.links.new(split.outputs[0],combine.inputs[0])
thetree.links.new(split.outputs[1],combine.inputs[1])
thetree.links.new(image.outputs["Color"],combine.inputs[2])
thetree.links.new(combine.outputs["Vector"],mix.inputs["Color2"])
thetree.links.new(greater.outputs["Value"],mix.inputs["Fac"])
thetree.links.new(vectoroutput,mix.inputs["Color1"])
vectoroutput=mix.outputs["Color"]
thetree.links.new(vectoroutput,treeoutput.inputs["Vector"])
#calc alpha mask
minx = thetree.nodes.new( type = 'ShaderNodeMath' )
minx.operation = "GREATER_THAN"
miny = thetree.nodes.new( type = 'ShaderNodeMath' )
miny.operation = "GREATER_THAN"
minz = thetree.nodes.new( type = 'ShaderNodeMath' )
minz.operation = "GREATER_THAN"
maxx = thetree.nodes.new( type = 'ShaderNodeMath' )
maxx.operation = "LESS_THAN"
maxy = thetree.nodes.new( type = 'ShaderNodeMath' )
maxy.operation = "LESS_THAN"
maxz = thetree.nodes.new( type = 'ShaderNodeMath' )
maxz.operation = "LESS_THAN"
splitmin = thetree.nodes.new( type = 'ShaderNodeSeparateXYZ' )
thetree.links.new(treeinput.outputs["Clip Min"],splitmin.inputs['Vector'])
splitmax = thetree.nodes.new( type = 'ShaderNodeSeparateXYZ' )
thetree.links.new(treeinput.outputs["Clip Max"],splitmax.inputs['Vector'])
splitv = thetree.nodes.new( type = 'ShaderNodeSeparateXYZ' )
thetree.links.new(vectoroutput,splitv.inputs['Vector'])
thetree.links.new(splitv.outputs[0],minx.inputs[0])
thetree.links.new(splitmin.outputs[0],minx.inputs[1])
thetree.links.new(splitv.outputs[1],miny.inputs[0])
thetree.links.new(splitmin.outputs[1],miny.inputs[1])
thetree.links.new(splitv.outputs[2],minz.inputs[0])
thetree.links.new(splitmin.outputs[2],minz.inputs[1])
mulmin1 = thetree.nodes.new( type = 'ShaderNodeMath' )
mulmin1.operation = "MULTIPLY"
mulmin2 = thetree.nodes.new( type = 'ShaderNodeMath' )
mulmin2.operation = "MULTIPLY"
thetree.links.new(minx.outputs['Value'],mulmin1.inputs[0])
thetree.links.new(miny.outputs['Value'],mulmin1.inputs[1])
thetree.links.new(mulmin1.outputs['Value'],mulmin2.inputs[0])
thetree.links.new(minz.outputs['Value'],mulmin2.inputs[1])
thetree.links.new(splitv.outputs[0],maxx.inputs[0])
thetree.links.new(splitmax.outputs[0],maxx.inputs[1])
thetree.links.new(splitv.outputs[1],maxy.inputs[0])
thetree.links.new(splitmax.outputs[1],maxy.inputs[1])
thetree.links.new(splitv.outputs[2],maxz.inputs[0])
thetree.links.new(splitmax.outputs[2],maxz.inputs[1])
mulmax1 = thetree.nodes.new( type = 'ShaderNodeMath' )
mulmax1.operation = "MULTIPLY"
mulmax2 = thetree.nodes.new( type = 'ShaderNodeMath' )
mulmax2.operation = "MULTIPLY"
thetree.links.new(maxx.outputs['Value'],mulmax1.inputs[0])
thetree.links.new(maxy.outputs['Value'],mulmax1.inputs[1])
thetree.links.new(mulmax1.outputs['Value'],mulmax2.inputs[0])
thetree.links.new(maxz.outputs['Value'],mulmax2.inputs[1])
mulmask = thetree.nodes.new( type = 'ShaderNodeMath' )
mulmask.operation = "MULTIPLY"
thetree.links.new(mulmin2.outputs['Value'],mulmask.inputs[0])
thetree.links.new(mulmax2.outputs['Value'],mulmask.inputs[1])
#output is mulmin2
thetree.links.new(mulmask.outputs['Value'],treeoutput.inputs['Alpha'])
self.built=True
self.updateprop(None)
image: bpy.props.PointerProperty(name="Image", type=Image, update=updateprop)
uv: bpy.props.StringProperty(update=updateprop)
built: bpy.props.BoolProperty()
samples: bpy.props.IntProperty(default =16, min=4, max=32, update=updateprop)
# Copy function to initialize a copied node from an existing one.
def copy(self, node):
self.built=False
# Free function to clean up on removal.
def free(self):
print("Removing node ", self, ", Goodbye!")
# Additional buttons displayed on the node.
def draw_buttons(self, context, layout):
me = context.object.data
layout.template_ID(self, "image", open="image.open")
layout.prop_search(self,"uv",me,"uv_layers",text="UVMap",icon="GROUP_UVS")
layout.prop(self, "samples",text="Samples")
parallax_menu = nodeitems_builtins.ShaderNodeCategory("SH_NEW_EXP", "Parallax", items=[
NodeItem("pom_node")
])
def register():
from bpy.utils import register_class
register_class(POMUVNode)
nodeitems_builtins.shader_node_categories.insert( 7, parallax_menu)
nodeitems_utils.unregister_node_categories('SHADER')
nodeitems_utils.register_node_categories('SHADER', nodeitems_builtins.shader_node_categories)
def unregister():
nodeitems_utils.unregister_node_categories('SHADER')
nodeitems_builtins.shader_node_categories.remove( parallax_menu )
nodeitems_utils.register_node_categories('SHADER', nodeitems_builtins.shader_node_categories)
from bpy.utils import unregister_class
unregister_class(POMUVNode)
if __name__ == "__main__":
register()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment