docs.unity3d.com
    Show / Hide Table of Contents

    Creating Custom Nodes for Visual Compositor

    Create the node class.

    Custom node classes need to inherit from the Unity.VisualCompositor.CompositorNode base class. This class is an abstract base class, which requires a single method to be implemented:

    using Unity.VisualCompositor;
    
    public class MyCustomNode : CompositorNode {
        public override void Render() { }
    }
    

    Visual Compositor will call this method when executing its Compositor Graph asset.

    UI Controls

    To construct controls for the UI of the custom node, declare fields with [ExposeField] attributes.

    For example:

    [ExposeField] public float param;
    

    This will create a FloatField in the node as parameters.

    We can use almost any serializable type, where each will create its corresponding Unity UI.

    Node Inputs/Outputs

    Node Inputs

    In order to get input into the custom node from the outputs of other nodes, we decorate its fields with [InputPort] special attribute. When the Render() method is called on the custom node, these fields will have values in them ready to be used.

    For example:

    [InputPort(name: "input")] private RenderTexture m_input;
    

    This declares a field named m_input, which is labeled as input and able to receive a RenderTexture from another node.

    There can be as many input nodes defined with any types. The Selection Group node for example, outputs a value of type HashSet<GameObject>.

    Node Outputs

    Visual Compositor will automatically copy the values of all output fields to any connected nodes. These output fields are fields decorated with [OutputPort] attributes and we can set their values inside the Render() method of the custom node.

    For example:

    [OutputPort(name: "output")] private RenderTexture m_output;
    

    This declares an output port named m_output, which is labeled as output and able to send a RenderTexture to any connected node.

    Node UI Extension

    A custom node may be decorated with a [CompositorNode] attribute with the following parameters for further customization.

    No Name Description Required
    1 title The name used in the create node menu. ✔️
    2 tooltip The documentation string (tooltip) for the node. ✔️
    3 w The width of the node widget in the editor.
    4 userCreatable If true, allows users to create the node through the 'Create Node' menu.
    5 icon The path to a custom icon for the node.

    We can also extend the UI of a custom node by writing a class inheriting from CustomCompositorNodeEditor with [CompositorNodeEditor] attribute. For example:

    using Unity.VisualCompositor.Editor;
    
    [CompositorNodeEditor(typeof(MyCustomNode))]
    public class MyCustomNodeEditor : CustomCompositorNodeEditor {
    
        public override NodeUI CreateUI(CompositorNode compositorNode) {
            // This method is called first. If you need use the node
            // in the below method, capture it here.
            // Eg: this.node = compositorNode;
            return new NodeUI();
        }
    
        public override void ConfigureUI(NodeUI ui) {
            // This is called last, you can modify the default UI here.
        }
    }
    

    Complete Example

    Runtime Script

    using UnityEngine;
    using Unity.VisualCompositor;
    
    // Any class that inherits Compositor Node will be available to use in the Compositor.
    [CompositorNode("My Custom Node", "A node.", icon: "SpeechBubble.png")]
    public class MyCustomNode : CompositorNode {
    
        // This method implements the functionality of the node. You can read from input port fields and write to output port fields,
        // the Compositor takes care of moving those values around the graph.
        public override void Render() {
            if (null == m_input) {
                if (null != m_output) {
                    ClearRenderTexture(m_output);
                }
                return;
            }
    
            if (null == m_output) {
                m_output           = new RenderTexture(m_input);
                m_output.hideFlags = HideFlags.DontSaveInEditor;
            }
    
            if(m_material == null) {
                Shader shader = Shader.Find("MyCustomNodeShader");
                m_material           = new Material(shader);
                m_material.hideFlags = HideFlags.DontSaveInEditor;
            } 
    
            Graphics.Blit(m_input, m_output, m_material);
        }
    
        private static void ClearRenderTexture(RenderTexture rt) {
            RenderTexture prevRT = RenderTexture.active;
            RenderTexture.active = rt;
            GL.Clear(true, true, Color.clear);
            RenderTexture.active = prevRT;
        }
    
    //--------------------------------------------------------------------------------------------------------------------------------------------------------------
    
        // Fields that have an InputPort attribute will become input ports in the Compositor Editor. 
        [InputPort(name: "input")] private RenderTexture m_input;
    
        // Fields that have an OutputPort attribute will become output ports in the Compositor Editor.
        [OutputPort(name: "output")] private RenderTexture m_output;
    
        // Fields that have an ExposeField attribute will have the appropriate ui controls created in
        // the node editor. Any serializable field types can be used. 
        [ExposeField] public float param;
    
        Material m_material;
    }
    

    Shader

    Shader "MyCustomNodeShader" {
        Properties {
            _MainTex ("Texture", 2D) = "white" {}
        }
        SubShader {
            // No culling or depth
            Cull Off ZWrite Off ZTest Always
    
            Pass {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    
                struct appdata{
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                struct v2f{
                    float2 uv : TEXCOORD0;
                    float4 vertex : SV_POSITION;
                };
    
                v2f vert(appdata v) {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = v.uv;
                    return o;
                }
    
                sampler2D _MainTex;
    
                float4 frag(v2f i) : SV_Target {
                    float4 c = tex2D(_MainTex, i.uv);
                    const half t = c.r;
                    c.r = c.g;
                    c.g = c.b;
                    c.b = t;
                    return c;
                }
                ENDCG
            }
    
    
        }
    }
    

    Editor Script

    using Unity.VisualCompositor;
    using Unity.VisualCompositor.Editor;
    using UnityEditor.UIElements;
    using UnityEngine;
    using UnityEngine.UIElements;
    
    [CompositorNodeEditor(typeof(MyCustomNode))]
    public class MyCustomNodeEditor : CustomCompositorNodeEditor {
    
        public override NodeUI CreateUI(CompositorNode compositorNode) {
    
            NodeUI nodeUI = new NodeUI();
            m_node = compositorNode as MyCustomNode;
            return nodeUI;
        }
    
        public override void ConfigureUI(NodeUI ui) {
            ui.tooltip = "Hey, this is a customised tool tip!";
    
            Label label = new Label("Custom Editor");
            ui.extensionContainer.Add(label);
    
            TextField textField = new TextField() {
                label = "Foo",
                value = "Bar",
            };
            ui.extensionContainer.Add(textField);
    
            ui.extensionContainer.Add(new ObjectField("HogeObj") { objectType = typeof(GameObject) });
    
            Button button = new Button() { text = "Increase Param", };
            button.clicked += () => { ++m_node.param; };
            ui.extensionContainer.Add(button);
        }
    
        private MyCustomNode m_node = null;
    
    }
    
    Back to top
    Copyright © 2023 Unity Technologies — Terms of use
    • Legal
    • Privacy Policy
    • Cookies
    • Do Not Sell or Share My Personal Information
    • Your Privacy Choices (Cookie Settings)
    "Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
    Generated by DocFX on 18 October 2023