diff --git a/README.md b/README.md index aeed6a7..0556740 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Texture VFX Control: Blender Add-on -This Blender add-on provides with shader-based effects on image/sequence/movie textures. It enables users to perform video composition directly in the 3D space without using the compositor or the sequencer. +This Blender add-on provides with shader-based effects on image/sequence/movie textures. Users can perform video composition directly in the 3D space without using the compositor or the video sequence editor. The functionalities of this add-on include: - Texture Playback Control - - Setting the speed and loop mode of sequence/movie textures + - Controlling the playback and speed of sequence/movie textures with keyframes - VFX Shaders - Blur - Chroma Key @@ -18,14 +18,28 @@ Blender 3.3+ or Blender 4.0 ## Installation -WIP +1. Download the `.zip` archive from the [Releases](https://github.com/chsh2/texture_vfx_control/releases) page. +2. Install and enable the add-on from `[Preferences] > [Add-ons]` panel. + +![](docs/install.png) ## Usage -WIP +It is recommended to use this add-on's operators on images/sequences/movies imported through [Images as Planes](https://docs.blender.org/manual/en/latest/addons/import_export/images_as_planes.html). Select the imported object, and access the add-on through `[Object] > [Quick Effects] > [Texture VFX Control]`. + +![](docs/menu.png) + +The add-on can also work with any material containing a [Image Texture](https://docs.blender.org/manual/en/latest/render/shader_nodes/textures/image.html) shader node. If the active material contains more than one image texture, please select one of them in the Shader Editor. A similar menu is available in the Shader Editor's `[Add] > [Texture VFX Control]`. ### Playback Control Driver + +**[Limitations]** + ### VFX Shader Node Groups -## Credits \ No newline at end of file +**[Limitations]** + +## Credits + +The method of Chroma Key comes from [OBS Studio](https://obsproject.com/). Its implementation also refers to a [Godot shader](https://godotshaders.com/shader/green-screen-chromakey/) made by [BlueMoon_Coder](https://godotshaders.com/author/bluemoon_coder/). \ No newline at end of file diff --git a/__init__.py b/__init__.py index e0d9e7f..80edc40 100644 --- a/__init__.py +++ b/__init__.py @@ -1,7 +1,7 @@ bl_info = { "name" : "Texture VFX Control", - "author" : "https://github.com/chsh2/", + "author" : "https://github.com/chsh2/texture_vfx_control", "description" : "Shader-based video playback control and composition VFXs", "blender" : (3, 3, 0), "version" : (0, 1, 0), diff --git a/docs/install.png b/docs/install.png new file mode 100644 index 0000000..f292ea4 Binary files /dev/null and b/docs/install.png differ diff --git a/docs/menu.png b/docs/menu.png new file mode 100644 index 0000000..6295c6b Binary files /dev/null and b/docs/menu.png differ diff --git a/operators/op_shader_nodes.py b/operators/op_shader_nodes.py index e5f237d..9fcdb01 100644 --- a/operators/op_shader_nodes.py +++ b/operators/op_shader_nodes.py @@ -72,6 +72,8 @@ def duplicate_tex_and_fx_nodes(node_tree, tex_node, node_chain, inside_links): offset_nodes[tag].image = tex_node.image offset_nodes[tag].projection = tex_node.projection offset_nodes[tag].extension = tex_node.extension + if offset_nodes[tag].extension == 'CLIP': # Avoid unwanted borders for outline calculation + offset_nodes[tag].extension = 'EXTEND' offset_nodes[tag].image_user.frame_duration = tex_node.image_user.frame_duration offset_nodes[tag].image_user.frame_start = tex_node.image_user.frame_start offset_nodes[tag].image_user.frame_offset = tex_node.image_user.frame_offset @@ -269,24 +271,30 @@ class OutlineOperator(bpy.types.Operator): bl_category = 'View' bl_options = {'REGISTER', 'UNDO'} - outline_color: bpy.props.FloatVectorProperty( - name = "Outline Color", - subtype = "COLOR", - default = (1.0,1.0,.0,1.0), - min = 0.0, max = 1.0, size = 4, - ) outline_size: bpy.props.FloatProperty( name = "Size", default = 0.5, - min = 0, soft_max = 5, step = 1 + min=0, soft_max=5, step=1 ) - outline_outer: bpy.props.BoolProperty( - name = "Outer Contour", - default = True + outline_outer: bpy.props.FloatProperty( + name = "Outer Strength", + default=0, soft_max=1, min=0 ) - outline_inner: bpy.props.BoolProperty( - name = "Inner Contour", - default = False + outline_outer_color: bpy.props.FloatVectorProperty( + name = "Outer Color", + subtype = "COLOR", + default = (1.0,1.0,.0,1.0), + min=0.0, max=1.0, size=4, + ) + outline_inner: bpy.props.FloatProperty( + name = "Inner Strength", + default=1, soft_max=1, min=0 + ) + outline_inner_color: bpy.props.FloatVectorProperty( + name = "Outer Color", + subtype = "COLOR", + default = (1.0,1.0,.0,1.0), + min=0.0, max=1.0, size=4, ) blur_strength: bpy.props.FloatProperty( name='Blur', @@ -299,12 +307,12 @@ class OutlineOperator(bpy.types.Operator): direction: bpy.props.EnumProperty( name='Direction', items=[('FIXED', 'Fixed Angle', ''), - ('REF', 'Following a Light', '')], + ('REF', 'Reference Object', '')], default='FIXED' ) angle: bpy.props.FloatProperty( name='Angle', - default=0.25*math.pi, min=-2*math.pi, max=2*math.pi, + default=0.25*math.pi, min=-2*math.pi, max=2*math.pi, step=10, unit='ROTATION', ) light_name: bpy.props.StringProperty( @@ -312,14 +320,21 @@ class OutlineOperator(bpy.types.Operator): default='', search=lambda self, context, edit_text: [obj.name for obj in context.scene.objects] ) + invert_light: bpy.props.BoolProperty( + name='Invert', + default = False + ) def draw(self, context): layout = self.layout layout.prop(self, 'outline_color') layout.prop(self, 'outline_size') - row = layout.row() - row.prop(self, 'outline_outer') - row.prop(self, 'outline_inner') + split = self.layout.split(factor=0.75) + split.prop(self, 'outline_outer', slider=True) + split.prop(self, 'outline_outer_color', text='') + split = self.layout.split(factor=0.75) + split.prop(self, 'outline_inner', slider=True) + split.prop(self, 'outline_inner_color', text='') layout.prop(self, 'blur_strength') layout.prop(self, 'directional') if self.directional: @@ -327,9 +342,10 @@ def draw(self, context): if self.direction == 'FIXED': layout.prop(self, 'angle') else: - layout.label(text="Light Information:") + layout.label(text="Reference Object:") box = layout.box() - box.prop(self, 'light_name') + box.prop(self, 'light_name', icon='OBJECT_DATA') + box.prop(self, 'invert_light') def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) @@ -345,8 +361,6 @@ def execute(self, context): if not tex_node: self.report({"WARNING"}, "Cannot find any eligible texture node to perform the operation.") return {'FINISHED'} - if tex_node.extension == 'CLIP': - tex_node.extension = 'EXTEND' # Duplicate nodes for edge detection node_chain, inside_links, output_color_links, output_alpha_links, \ @@ -383,9 +397,10 @@ def execute(self, context): post_node = add_node_group(target_node_tree, 'tfx_OutlinePost', location=(node_chain[-1].location[0] + 200, node_chain[-1].location[1])) - post_node.inputs['Color'].default_value = self.outline_color - post_node.inputs['Inner'].default_value = float(self.outline_inner) - post_node.inputs['Outer'].default_value = float(self.outline_outer) + post_node.inputs['Inner Color'].default_value = self.outline_inner_color + post_node.inputs['Inner'].default_value = self.outline_inner + post_node.inputs['Outer Color'].default_value = self.outline_outer_color + post_node.inputs['Outer'].default_value = self.outline_outer # Set offset values considering different factors ratio = tex_node.image.size[0] / tex_node.image.size[1] @@ -414,21 +429,9 @@ def execute(self, context): add_driver_variable(dr.driver, light_obj, f'location[{i}]', 'var', custom_property=False) dr.driver.expression = 'var' # Set light strength - light_node.inputs['Scale'].default_value[0] = converted_outline_size[0] - light_node.inputs['Scale'].default_value[1] = converted_outline_size[1] - - # Set light color: disabled for now - if False and light_obj.type == 'LIGHT': - light_color_drivers = post_node.inputs['Color'].driver_add('default_value') - for i,dr in enumerate(light_color_drivers): - dr.driver.type = 'SCRIPTED' - if i < 3: - add_driver_variable(dr.driver, light_obj.data, f'color[{i}]', 'var', - id_type='LIGHT', custom_property=False) - dr.driver.expression = 'var' - else: - dr.driver.expression = '1.0' - + light_node.inputs['Scale'].default_value[0] = converted_outline_size[0] * (1-self.invert_light*2) + light_node.inputs['Scale'].default_value[1] = converted_outline_size[1] * (1-self.invert_light*2) + # Connect to the FX nodes target_node_tree.links.new(uv_socket, pre_node.inputs['UV']) for tag in ['U+', 'U-', 'V+', 'V-']: diff --git a/res/assets.blend b/res/assets.blend index cde68e2..f7b3d76 100644 Binary files a/res/assets.blend and b/res/assets.blend differ