Last active
November 15, 2023 05:28
-
-
Save supereggbert/acccdc0a380c7e6f4c0b5ff4302774fd to your computer and use it in GitHub Desktop.
Yet Another Parallax Mapping Addon
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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