Merge pull request #1810 from MoritzBrueckner/shaderdata-node
Add ShaderData node for easier uniform and input access
This commit is contained in:
commit
db6c621ccc
6
blender/arm/material/arm_nodes/__init__.py
Normal file
6
blender/arm/material/arm_nodes/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
"""Import all nodes"""
|
||||
import glob
|
||||
from os.path import dirname, basename, isfile
|
||||
|
||||
modules = glob.glob(dirname(__file__) + "/*.py")
|
||||
__all__ = [basename(f)[:-3] for f in modules if isfile(f)]
|
15
blender/arm/material/arm_nodes/arm_nodes.py
Normal file
15
blender/arm/material/arm_nodes/arm_nodes.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from typing import Type
|
||||
|
||||
from bpy.types import Node
|
||||
import nodeitems_utils
|
||||
|
||||
nodes = []
|
||||
category_items = {}
|
||||
|
||||
|
||||
def add_node(node_class: Type[Node], category: str):
|
||||
global nodes
|
||||
nodes.append(node_class)
|
||||
if category_items.get(category) is None:
|
||||
category_items[category] = []
|
||||
category_items[category].append(nodeitems_utils.NodeItem(node_class.bl_idname))
|
84
blender/arm/material/arm_nodes/shader_data_node.py
Normal file
84
blender/arm/material/arm_nodes/shader_data_node.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
from bpy.props import *
|
||||
from bpy.types import Node
|
||||
|
||||
from arm.material.arm_nodes.arm_nodes import add_node
|
||||
from arm.material.shader import Shader
|
||||
|
||||
|
||||
class ShaderDataNode(Node):
|
||||
"""Allows access to shader data such as uniforms and inputs."""
|
||||
bl_idname = 'ArmShaderDataNode'
|
||||
bl_label = 'Shader Data'
|
||||
bl_icon = 'NONE'
|
||||
|
||||
input_type: EnumProperty(
|
||||
items = [('input', 'Input', 'Shader Input'),
|
||||
('uniform', 'Uniform', 'Uniform value')],
|
||||
name='Input Type',
|
||||
default='input',
|
||||
description="The kind of data that should be retrieved")
|
||||
|
||||
input_source: EnumProperty(
|
||||
items = [('frag', 'Fragment Shader', 'Take the input from the fragment shader'),
|
||||
('vert', 'Vertex Shader', 'Take the input from the vertex shader and pass it through to the fragment shader')],
|
||||
name='Input Source',
|
||||
default='vert',
|
||||
description="Where to take the input value from")
|
||||
|
||||
variable_type: EnumProperty(
|
||||
items = [('int', 'int', 'int'),
|
||||
('float', 'float', 'float'),
|
||||
('vec2', 'vec2', 'vec2'),
|
||||
('vec3', 'vec3', 'vec3'),
|
||||
('vec4', 'vec4', 'vec4')],
|
||||
name='Variable Type',
|
||||
default='vec3',
|
||||
description="The type of the variable")
|
||||
|
||||
variable_name: StringProperty(name="Variable Name", description="The name of the variable")
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
col = layout.column(align=True)
|
||||
col.label(text="Input Type:")
|
||||
# Use a row to expand horizontally
|
||||
col.row().prop(self, "input_type", expand=True)
|
||||
|
||||
split = layout.split(factor=0.5, align=True)
|
||||
col_left = split.column()
|
||||
col_right = split.column()
|
||||
|
||||
if self.input_type == "input":
|
||||
col_left.label(text="Input Source")
|
||||
col_right.prop(self, "input_source", text="")
|
||||
|
||||
col_left.label(text="Variable Type")
|
||||
col_right.prop(self, "variable_type", text="")
|
||||
col_left.label(text="Variable Name")
|
||||
col_right.prop(self, "variable_name", text="")
|
||||
|
||||
def init(self, context):
|
||||
self.outputs.new('NodeSocketColor', 'Color')
|
||||
self.outputs.new('NodeSocketVector', 'Vector')
|
||||
self.outputs.new('NodeSocketFloat', 'Float')
|
||||
self.outputs.new('NodeSocketInt', 'Int')
|
||||
|
||||
def parse(self, frag: Shader, vert: Shader) -> str:
|
||||
if self.input_type == "uniform":
|
||||
frag.add_uniform(f'{self.variable_type} {self.variable_name}', link=self.variable_name)
|
||||
return self.variable_name
|
||||
|
||||
else:
|
||||
if self.input_source == "frag":
|
||||
frag.add_in(f'{self.variable_type} {self.variable_name}')
|
||||
return self.variable_name
|
||||
|
||||
# Reroute input from vertex shader to fragment shader (input must exist!)
|
||||
else:
|
||||
vert.add_out(f'{self.variable_type} out_{self.variable_name}')
|
||||
frag.add_in(f'{self.variable_type} out_{self.variable_name}')
|
||||
|
||||
vert.write(f'out_{self.variable_name} = {self.variable_name};')
|
||||
return 'out_' + self.variable_name
|
||||
|
||||
|
||||
add_node(ShaderDataNode, category='Armory')
|
|
@ -396,12 +396,6 @@ def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str:
|
|||
return 'vcolor'
|
||||
|
||||
elif node.type == 'ATTRIBUTE':
|
||||
# Shader uniforms
|
||||
if node.attribute_name.startswith(('vec2 ', 'vec3 ', 'vec4 ')):
|
||||
utype, uname = node.attribute_name.split(' ')[:2]
|
||||
frag.add_uniform(f'{utype} {uname}', link=uname)
|
||||
return uname
|
||||
|
||||
if socket == node.outputs[0]: # Color
|
||||
# Vertex colors
|
||||
con.add_elem('col', 'short4norm')
|
||||
|
@ -1059,6 +1053,10 @@ def parse_vector(node: bpy.types.Node, socket: bpy.types.NodeSocket) -> str:
|
|||
nor = parse_vector_input(node.inputs[3])
|
||||
return '(vec3({0}) * {1})'.format(height, scale)
|
||||
|
||||
elif node.type == 'CUSTOM':
|
||||
if node.bl_idname == 'ArmShaderDataNode':
|
||||
return node.parse(frag, vert)
|
||||
|
||||
def parse_normal_map_color_input(inp, strength_input=None):
|
||||
global normal_parsed
|
||||
global frag
|
||||
|
@ -1129,12 +1127,6 @@ def parse_value(node, socket):
|
|||
curshader.add_uniform('float time', link='_time')
|
||||
return 'time'
|
||||
|
||||
# Shader uniforms
|
||||
elif node.attribute_name.startswith(('float ', 'int ')):
|
||||
utype, uname = node.attribute_name.split(' ')[:2]
|
||||
frag.add_uniform(f'{utype} {uname}', link=uname)
|
||||
return uname
|
||||
|
||||
# Return 0.0 till drivers are implemented
|
||||
else:
|
||||
return '0.0'
|
||||
|
@ -1520,6 +1512,10 @@ def parse_value(node, socket):
|
|||
else:
|
||||
return '0.0'
|
||||
|
||||
elif node.type == 'CUSTOM':
|
||||
if node.bl_idname == 'ArmShaderDataNode':
|
||||
return node.parse(frag, vert)
|
||||
|
||||
##
|
||||
|
||||
def vector_curve(name, fac, points):
|
||||
|
|
|
@ -196,13 +196,16 @@ class Shader:
|
|||
return s in self.includes
|
||||
|
||||
def add_include(self, s):
|
||||
self.includes.append(s)
|
||||
if not self.has_include(s):
|
||||
self.includes.append(s)
|
||||
|
||||
def add_in(self, s):
|
||||
self.ins.append(s)
|
||||
if s not in self.ins:
|
||||
self.ins.append(s)
|
||||
|
||||
def add_out(self, s):
|
||||
self.outs.append(s)
|
||||
if s not in self.outs:
|
||||
self.outs.append(s)
|
||||
|
||||
def add_uniform(self, s, link=None, included=False):
|
||||
ar = s.split(' ')
|
||||
|
|
54
blender/arm/nodes_material.py
Normal file
54
blender/arm/nodes_material.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
import bpy
|
||||
import nodeitems_utils
|
||||
from nodeitems_utils import NodeCategory
|
||||
|
||||
import arm.material.arm_nodes.arm_nodes as arm_nodes
|
||||
# Import all nodes so that they register. Do not remove this import
|
||||
# even if it looks unused
|
||||
from arm.material.arm_nodes import *
|
||||
|
||||
registered_nodes = []
|
||||
|
||||
|
||||
class MaterialNodeCategory(NodeCategory):
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.space_data.tree_type == 'ShaderNodeTree'
|
||||
|
||||
|
||||
def register_nodes():
|
||||
global registered_nodes
|
||||
|
||||
# Re-register all nodes for now..
|
||||
if len(registered_nodes) > 0:
|
||||
unregister_nodes()
|
||||
|
||||
for n in arm_nodes.nodes:
|
||||
registered_nodes.append(n)
|
||||
bpy.utils.register_class(n)
|
||||
|
||||
node_categories = []
|
||||
|
||||
for category in sorted(arm_nodes.category_items):
|
||||
sorted_items = sorted(arm_nodes.category_items[category], key=lambda item: item.nodetype)
|
||||
node_categories.append(
|
||||
MaterialNodeCategory('ArmMaterial' + category + 'Nodes', category, items=sorted_items)
|
||||
)
|
||||
|
||||
nodeitems_utils.register_node_categories('ArmMaterialNodes', node_categories)
|
||||
|
||||
|
||||
def unregister_nodes():
|
||||
global registered_nodes
|
||||
for n in registered_nodes:
|
||||
bpy.utils.unregister_class(n)
|
||||
registered_nodes = []
|
||||
nodeitems_utils.unregister_node_categories('ArmMaterialNodes')
|
||||
|
||||
|
||||
def register():
|
||||
register_nodes()
|
||||
|
||||
|
||||
def unregister():
|
||||
unregister_nodes()
|
|
@ -1,4 +1,5 @@
|
|||
import arm.nodes_logic
|
||||
import arm.nodes_material
|
||||
import arm.props_traits_props
|
||||
import arm.props_traits
|
||||
import arm.props_lod
|
||||
|
@ -31,6 +32,7 @@ def register(local_sdk=False):
|
|||
arm.props.register()
|
||||
arm.props_ui.register()
|
||||
arm.nodes_logic.register()
|
||||
arm.nodes_material.register()
|
||||
arm.keymap.register()
|
||||
arm.handlers.register()
|
||||
arm.props_collision_filter_mask.register()
|
||||
|
@ -40,6 +42,7 @@ def unregister():
|
|||
registered = False
|
||||
arm.keymap.unregister()
|
||||
arm.utils.unregister()
|
||||
arm.nodes_material.unregister()
|
||||
arm.nodes_logic.unregister()
|
||||
arm.handlers.unregister()
|
||||
arm.props_ui.unregister()
|
||||
|
|
Loading…
Reference in a new issue