Unity Products:Amplify Shader Editor/API and Unity Products:Amplify Shader Editor/Vertex To Fragment: Difference between pages

From Amplify Creations Wiki
(Difference between pages)
Jump to navigation Jump to search
ampwiki>Amplify RnD Rick
 
ampwiki>Amplify RnD Rick
No edit summary
 
Line 1: Line 1:
<span style="font-size: 120%;">[http://amplify.pt/unity/amplify-shader-editor Product Page] - [http://amplify.pt/unity/amplify-shader-editor/samples/ Included Shaders] - [[Unity_Products:Amplify_Shader_Editor/Manual| Manual]] - [[Unity_Products:Amplify_Shader_Editor/Manual#Shader_Functions| Shader Functions]] - [[Unity_Products:Amplify_Shader_Editor/Tutorials| Tutorials]] - [[Unity_Products:Amplify_Shader_Editor/API| API]] - [[Unity_Products:Amplify_Shader_Editor/Templates| Shader Templates]] - [[Unity_Products:Amplify_Shader_Editor/Scriptable_Rendering_Pipeline| Scriptable Rendering Pipeline ]] - [[Unity_Products:Amplify_Shader_Editor/Nodes| Nodes]] - [[Unity_Products:Amplify_Shader_Editor/Community_Nodes| Community Nodes]]</span>
[[Unity_Products:Amplify_Shader_Editor/Nodes | Back to Node List]]
== API Description ==
(Although the core behavior remains the same, some of this information is outdated)
==== Introduction ====
Is this section the most important aspects of ASE API are going to be described via a demonstration on how to create a simple node.


Nodes are made of three major components
== Vertex To Fragment Node ==
* Node Body
* Input Ports ( Left-Side Ports )
* Output Ports ( Right-Side Ports )


A node output result will always be a string containing shader instructions required to achieve its goal.  
The Vertex To Fragment node allows data to be calculated on the vertex function and transferred to the surface/fragment via interpolators.<br/>
'''NOTE 1:''' [[#paramNoInterpolation|No Interpolation]] cannot be used over the '''Standard Surface''' type as we must be able to directly control interpolators registry, which does't happen over this shader type.<br/>
'''NOTE 2:''' [[#paramNoInterpolation|No Interpolation]] will not work across all API's and can even throw compilation errors on some of them ( p.e. Metal and GLES 2.0 ).<br/>


In this example a node will be created with two input ports and an output port. It will calculate a simplified fmod and output the resulting shader operation.
<img class="responsive-img" src="http://wiki.amplify.pt/images/NodeDetail/VertexToFragmentNode.gif">
<font size="1">Nodes used:
[[Unity_Products:Amplify_Shader_Editor/World_Normal|Word Normal]],
[[Unity_Products:Amplify_Shader_Editor/World_Space_Light_Dir|World Space Light Dir]],
[[Unity_Products:Amplify_Shader_Editor/Dot|Dot]],
[[Unity_Products:Amplify_Shader_Editor/Float|Float]],
[[Unity_Products:Amplify_Shader_Editor/Power|Power]],
[[Unity_Products:Amplify_Shader_Editor/Vertex_To_Fragment|Vertex To Fragment]],
[[Unity_Products:Amplify_Shader_Editor/Light_Attenuation|Light Attenuation]],
[[Unity_Products:Amplify_Shader_Editor/Light_Color|Light Color]],
[[Unity_Products:Amplify_Shader_Editor/Multiply|Multiply]]
</font>


Each Shader Graph always have at least one node, the <b>Master Node</b>, which is responsible for generating the shader. When hitting the Update button, each one of its Input ports will be analyzed and if connected a value will be requested via its connection.
{| class="wikitable" style="width: 100%;"
 
|-  
Nodes are always analyzed from Input port of a node to the output of its connected node and so on.
! style="width: 10%;" | Node Parameter !! Description !! style="width: 10%;" | Default Value
 
So, let's begin...
 
==== Inherit from Parent Node ====
The first step on creating your custom node is creating a new c# script, which we are going to call MyTestNode, and make its class inherit from ParentNode.<br/>
There are three really important aspects to take into account:
* Since ASE is an Editor extension, the node file must be created inside an Editor folder to be considered an editor script and be able to make use of its API
* The class MUST be declared inside the AmplifyShaderEditor namespace so ASE can correctly capture it and make it available at the node palette
* Both the class and its properties must be serializable in order to Unity hot code reload to work correctly and no data in your node is lost.
 
<syntaxhighlight lang="csharp">
public NodeAttributes( string name, string category, string description , ... )
</syntaxhighlight>
 
# Create class and inherit from Parent Node
# Create NodeAttributes Class Attribute and associate it with your class
## Fill up at least first three arguments
### <b>Name</b>: Node Name
### <b>Category</b>: Node category
### <b>Description</b>: Basic description to be shown as a tooltip
 
Here's a snippet on how it looks on your class
<syntaxhighlight lang="csharp">
using UnityEngine;
using System;
using AmplifyShaderEditor;
 
namespace AmplifyShaderEditor
{
[Serializable]
[NodeAttributes( "My Test Node", "My Test Category", "My Test Description")]
public class MyTestNode : ParentNode
{
}
}
</syntaxhighlight>
 
It is essential that your class contains Node Attributes since only these are registered into our Node Palette and made available to use.
 
==== Methods to Override ====
 
Your next step is to override at least these methods:
* <b>void CommonInit( int uniqueId )</b>:
** This is where all the Input and Output ports are created and the nodes initial setup is done
*** Always call base.CommonInit( ... )
*<b> GenerateShaderForOutput( int outputId, WirePortDataType inputPortType, ref MasterNodeDataCollector dataCollector, bool ignoreLocalvar )</b>
** This is where the node will generate its shader instructions
* These next methods need only to be overridden if your node needs to read/write internal data into the shader
** <b>void ReadFromString( ref string[] nodeParams )</b>
*** In here our special node data is read from the shader
**** Always call base.ReadFromString(...)
** <b>void WriteToString( ref string nodeInfo, ref string connectionsInfo )</b>
*** In here our special node data is written into the shader
**** Always call base.WriteToString(...)
 
==== Ports ====
Like stated earlier, there are two types of ports, Inputs and Outputs.
* <b>Input ports</b> Located at the nodes left side, they are responsible for retrieving data from other nodes necessary to its internal operations.
* <b>Output ports</b> Located at the nodes right side, they are responsible for transmitting nodes results to other nodes
Both Input and Output ports must always have a specified <b>Data Type</b>, though you have the ability to change it  whenever you want or make them adaptable to their connections.
To see all available data types please refer to [[#Available Data Types|here]].
 
Also important to refer that it's always an Input Port which requests new data from an Output port and not other way around.
 
===== Output Ports =====
A node needs at least to have one Output port so some sort of information can be generated and make its way through into the master node.<br>
To add Output ports you can use:
* <b>AddOutputPort( WirePortDataType type, string name ):</b> Add a single output port of the specified type and name
* <b>AddOutputVectorPorts( WirePortDataType type, string name ):</b> Add both an output port from the selected vector type(FLOAT2...4), but also add outport ports for each of its components (X...W)
* <b>AddOutputColorPorts( string name ):</b> Similar to the method above but specific for the COLOR type
 
===== Input Ports =====
A node may not need input ports as it can generate data via its internal properties.
Each input port also contains its own internal data so you can have the node generating results even if not all, or none, of the input ports are connected.
However this might not be the behavior every node creator wants so, in order for you to have internal input port data being used you need to set the <b>m_useInternalPortData</b> flag to true on your CommonInit(...) method since its default value is false.
 
Adding input ports can only be done via:<br>
<b> AddInputPort( WirePortDataType type, bool typeLocked, string name, int orderId = -1, MasterNodePortCategory category = MasterNodePortCategory.Fragment ) </b>
* <b>WirePortDataType type:</b> Data Type to be used on Port
* <b>bool typeLocked:</b> If set to true, only Output ports from that specific type will be able to establish connection
* These next parameters for now are only used by the Master Node so you can leave them on their default values
** <b>int orderId:</b> Ports, by default[-1], are analyzed by their creation order, unless a different order Id is specified
** <b>MasterNodePortCategory category:</b> Specifies to which area this input will generate the shader code. Most ports are set to Fragment except Local Vertex Offset ( Vertex ), Tessellation and Debug ( Debug ).
 
Getting back to our node, we want to add two input ports and a single output port.<br>Here's how it looks like:
 
<syntaxhighlight lang="csharp">
using UnityEngine;
using System;
using AmplifyShaderEditor;
 
namespace AmplifyShaderEditor
{
[NodeAttributes( "My Test Node", "My Test Category", "My Test Description")]
[Serializable]
public class MyTestNode : ParentNode
{
protected override void CommonInit( int uniqueId )
{
base.CommonInit( uniqueId );
AddInputPort( WirePortDataType.FLOAT, false, "X");
AddInputPort( WirePortDataType.FLOAT, false, "Y");
AddOutputPort( WirePortDataType.FLOAT, "Out");
}
}
}
</syntaxhighlight>
 
===== Automatically adapting ports to connections =====
 
In certain nodes you may want for your ports to adapt to the outputs connected to them.
 
For example, on a Multiply node each input port must adapt to what is connected to it. Also, its output is dependent on the input types.
 
For this to correctly happen, two situations need to be taken into account by overriding two specific methods.
*<b>void OnInputPortConnected( int portId, int otherNodeId, int otherPortId, bool activateNode = true ):</b> New connection is established with input port
*<b>void OnConnectedOutputNodeChanges( int inputPortId, int otherNodeId, int otherPortId, string name, WirePortDataType type ):</b> Node connected to ours inputs changes its output type on an already established connection
 
On a typical situation you will need to override both these methods and call <b>MatchPortConnection()</b> method on the modified port to automatically adapt it to the newly connected type.
 
You can also use thise next method for both Input and Output ports to force a specific type to your port.
*<b>ChangeType( WirePortDataType newType, bool invalidateConnections )</b>
 
Now let's make our node automatically adapt to its connections.
 
<syntaxhighlight lang="csharp">
public override void OnConnectedOutputNodeChanges( int inputPortId, int otherNodeId, int otherPortId, string name, WirePortDataType type )
{
UpdateConnection( inputPortId );
}
 
public override void OnInputPortConnected( int inputPortId, int otherNodeId, int otherPortId, bool activateNode = true )
{
base.OnInputPortConnected( inputPortId, otherNodeId, otherPortId, activateNode );
UpdateConnection( inputPortId );
}
 
void UpdateConnection( int portId )
{
m_inputPorts[ portId ].MatchPortToConnection();
WirePortDataType outputType = ( UIUtils.GetPriority( m_inputPorts[ 0 ].DataType ) > UIUtils.GetPriority( m_inputPorts[ 1 ].DataType ) ) ? m_inputPorts[ 0 ].DataType : m_inputPorts[ 1 ].DataType;
m_outputPorts[ 0 ].ChangeType( outputType, false );
}
</syntaxhighlight>
 
On this snippet, the method <b>UpdateConnection( int portId )</b> is called on both the situations stated earlier. The first step is to match the input port to its new type, after that we calculate and set a new type for our output port by checking which of the input port types have the most priority.
Our full priority list is shown [[#Type Priorities|here]].
Please note that you need to call <b>OnInputPortConnected</b> base method since it's also responsible for setting connection status with the Master Node.
 
Besides watching for created connections, you can also check when a connection is removed. This can be done by overriding the <b>void OnInputPortDisconnected( int portId )</b> method.
 
==== Code generation and Master Node Data Collector ====
 
Like described earlier, it’s on the <b>GenerateShaderForOutput(...)</b> overridden method where your node shader operations happens.
There’s no need to call base.GenerateShaderForOutput() since it is an empty method.
 
In here you will fetch data from your inputs, cast and apply an operation on top of them.
There are three separate ways for you to retrieve data from your inputs.
They vary on if you want to automatically cast them to the input type, force cast to a certain type or do no cast at all.
*<b>GenerateShaderForOutput(ref MasterNodeDataCollector dataCollector, bool ignoreLocalVar ):</b>  Do no cast at all
*<b>GenerateShaderForOutput(ref MasterNodeDataCollector dataCollector, WirePortDataType myCastType, bool ignoreLocalVar, bool autoCast = false ):</b> Cast to a specific type
*<b>GeneratePortInstructions(ref MasterNodeDataCollector dataCollector ):</b> Automatically cast to the input port type
 
On our example we want to cast to our most priority type which is set on the output port
<syntaxhighlight lang="csharp">
public override string GenerateShaderForOutput( int outputId, ref MasterNodeDataCollector dataCollector, bool ignoreLocalvar )
{
WirePortDataType mainType = m_outputPorts[ 0 ].DataType;
//Get value from input 0
string valueInput0 = m_inputPorts[ 0 ].GenerateShaderForOutput( ref dataCollector, mainType, ignoreLocalvar, true );
//Get value from input 1
string valueInput1 = m_inputPorts[ 1 ].GenerateShaderForOutput( ref dataCollector, mainType, ignoreLocalvar, true );
 
// This is a simplified fmod operation only for demonstration purposes
string finalCalculation = string.Format( "frac({0}/{1})*{1}", valueInput0, valueInput1 );
 
// Return result contained on finalCalculation
return finalCalculation;
}
</syntaxhighlight>
 
===== Adding Local Variables =====
 
We already have some automatic mechanisms on forcing local variables to be generated in order to optimize instruction count and re-use previously calculated values.
You can however explicitly request a local variable to be created and re-use them on all later node calls.
Here’s a snippet illustrating a simple change which forces the generation/usage of what have been said.
 
<syntaxhighlight lang="csharp">
public override string GenerateShaderForOutput( int outputId, ref MasterNodeDataCollector dataCollector, bool ignoreLocalvar )
{
// If local variable is already created then you need only to re-use i
if ( m_outputPorts[ 0 ].IsLocalValue )
return m_outputPorts[ 0 ].LocalValue;
 
WirePortDataType mainType = m_outputPorts[ 0 ].DataType;
//Get value from input 0
string valueInput0 = m_inputPorts[ 0 ].GenerateShaderForOutput( ref dataCollector, mainType, ignoreLocalvar, true );
//Get value from input 1
string valueInput1 = m_inputPorts[ 1 ].GenerateShaderForOutput( ref dataCollector, mainType, ignoreLocalvar, true );
 
// This is a simplified fmod operation only for demonstration purposes
string finalCalculation = string.Format( "frac({0}/{1})*{1}", valueInput0, valueInput1 );
 
//Register the final operation on a local variable associated with our output port
RegisterLocalVariable( 0, finalCalculation, ref dataCollector, "myLocalVar" + OutputId);
 
// Use the newly created local variable
return m_outputPorts[ 0 ].LocalValue;
}
</syntaxhighlight>
 
In this example we use <b>void RegisterLocalVariable( int outputId, string value, ref MasterNodeDataCollector dataCollector , string customName  = null)</b> to register a local variable and associate it with our output port. This way if this output is used in more than one nodes then the already created local value will be returned instead of re-calculating everything.
The ''OutputId'' getter returns a string which combines both the current node own unique id with the graph id it is inserted on,  and it's used to create an unique name for the variable.
 
==== Draw Node and Draw Properties ====
 
You can change both how your node looks on the canvas and how its properties are displayed on the Node Properties Window.
For our example, we’ll create a boolean property called m_adjustPorts which will (un)lock the node automatic port adjustment.
This boolean will be toggleable from both the node itself and its Node Property window.
 
===== Draw Node =====
 
For you do change how the node looks on the main canvas  you’ll need to override:
*<b>void Draw( DrawInfo drawInfo )</b>
 
If you want to maintain the node default look and just add additional info then start by calling its base method <b>( highly recommended )</b>.
You can easily add extra size inside the node you’ll need to specify it through <b>m_extraSize</b> variable.<br>
By adding a value into m_extraSize.x or m_extraSize.y you’ll be increasing the nodes overall width and height.
 
A really important aspect to take into account is that you need to manually position and scale your additional UI Components to take canvas zoom into account.
Your available area to work with is given by the m_remainingBox rectangle and the current zoom can be easily be accessed through the InvertedZoom component passed via the drawInfo parameters.
 
<syntaxhighlight lang="csharp">
protected override void CommonInit( int uniqueId )
{
base.CommonInit( uniqueId );
AddInputPort( WirePortDataType.FLOAT, false, "X" );
AddInputPort( WirePortDataType.FLOAT, false, "Y" );
AddOutputPort( WirePortDataType.FLOAT, "Out" );
m_insideSize.Set( 50, 25 );
}
 
public override void Draw( DrawInfo drawInfo )
{
base.Draw( drawInfo );
Rect newPos = m_remainingBox;
newPos.x += newPos.width * 0.3f * drawInfo.InvertedZoom;
newPos.y += newPos.height * 0.3f * drawInfo.InvertedZoom;
m_adjustPorts = EditorGUI.Toggle( newPos, m_adjustPorts );
}
</syntaxhighlight>
 
In the provided snippet you can notice we’re increasing the node default size by 50 pixels on width and 25 on height. We then manually position our Toggle Box inside the node available on the Draw() method.
 
===== Draw Properties =====
 
When each node is selected, all its properties can be drawn on the left side menu, Node Properties.
When creating a new node, it’s the creator responsibility to choose what is being drawn.
For that, the DrawProperties() method must be overridden and base.DrawProperties() must be called.
This method is being called from within a <b>GUILayout.BeginArea(... ) - GUILayout.EndArea()</b>
 
Here’s a snippet of DrawProperties().
<syntaxhighlight lang="csharp">
public override void DrawProperties()
{
base.DrawProperties();
m_adjustPorts = EditorGUILayout.Toggle( "Auto-Adjust", m_adjustPorts );
}
</syntaxhighlight>
 
This is much simpler, in here you can use Unity EditorGUILayout and GUILayout tools and the generated UI will be automatically placed inside the menu available area.
 
==== Read/Write Meta Data ====
 
All node information is also saved on the shader body as a comment block at the end of the file. Each node has the responsibility to read/write data from/to this block.
Please notice that order matters and data must read in the same order it is being written.
 
===== Write =====
 
For you to write your node data you’ll need to override this method:
*<b>WriteToString( ref string nodeInfo, ref string connectionsInfo )</b>.
The first instruction inside the method must be a base.WriteToString(...).
 
After that you can add your data by simply calling:
*<b>IOUtils.AddFieldValueToString( ref nodeInfo, myData )</b>
 
One aspect to take into account is that this utility function internally calls  the objects ToString()
to retrieve the data to write into the shader.
You can call <b>AddFieldValueToString(...)</b> using your data directly if it is C# built-in data. In any other type of data you must build a string from it which must then must be also parsed when reading from the shader.
 
Our utility class IOUtils already have static functions to both read and write from string to Unity Vector data types:
* <b>Vector2:</b> IOUtils.Vector2ToString( Vector2 value )
* <b>Vector3:</b> IOUtils.Vector3ToString( Vector3 value )
* <b>Vector4:</b> IOUtils.Vector4ToString( Vector4 value )
* <b>Color:</b> IOUtils.ColorToString( Color value )
 
===== Read =====
 
For you to Read data from the shader you’ll need to override this method on your node.
*<b>void ReadFromString( ref string[] nodeParams )</b>
 
Again the first instruction needs to be base.ReadFromString(...).
For you to get each element of data you have previously written you’ll just need to use GetCurrentParam( ref nodeParams ).
Please note this utility function returns a string so you’ll need to parse it to your data type.
Here are some examples on how to parse for the most common data types:
* <b>Bool:</b> System.Convert.ToBoolean( string value )
* <b>Int:</b> System.Convert.ToInt32( string value )
* <b>Float:</b> System.Convert.ToSingle( string value )
* <b>Vector2:</b> IOUtils.StringToVector2( string value )
* <b>Vector3:</b> IOUtils.StringToVector3( string value )
* <b>Vector4:</b> IOUtils.StringToVector4( string value )
* <b>Color:</b> IOUtils.StringToColor( string value )
* <b>Enum:</b> Enum.Parse( string value )
 
Here’s a snippet on how everything is built into our sample class
<syntaxhighlight lang="csharp">
public override void WriteToString( ref string nodeInfo, ref string connectionsInfo )
{
base.WriteToString( ref nodeInfo, ref connectionsInfo );
IOUtils.AddFieldValueToString( ref nodeInfo, m_adjustPorts );
}
 
public override void ReadFromString( ref string[] nodeParams )
{
base.ReadFromString( ref nodeParams );
m_adjustPorts = Convert.ToBoolean( GetCurrentParam( ref nodeParams ) );
}
</syntaxhighlight>
 
==== Finished Node ====
 
And here is our final node:
<syntaxhighlight lang="csharp">
using UnityEngine;
using UnityEditor;
using System;
 
namespace AmplifyShaderEditor
{
[Serializable]
[NodeAttributes( "My Test Node", "My Test Category", "My Test Description" )]
public class MyTestNode : ParentNode
{
[SerializeField]
private bool m_adjustPorts = false;
 
protected override void CommonInit( int uniqueId )
{
base.CommonInit( uniqueId );
AddInputPort( WirePortDataType.FLOAT, false, "X" );
AddInputPort( WirePortDataType.FLOAT, false, "Y" );
AddOutputPort( WirePortDataType.FLOAT, "Out" );
m_insideSize.Set( 50, 25 );
}
 
public override void OnConnectedOutputNodeChanges( int inputPortId, int otherNodeId, int otherPortId, string name, WirePortDataType type )
{
UpdateConnection( inputPortId );
}
 
public override void OnInputPortConnected( int inputPortId, int otherNodeId, int otherPortId, bool activateNode = true )
{
base.OnInputPortConnected( inputPortId, otherNodeId, otherPortId, activateNode );
UpdateConnection( inputPortId );
}
void UpdateConnection( int portId )
{
if ( m_adjustPorts )
{
m_inputPorts[ portId ].MatchPortToConnection();
WirePortDataType outputType = ( UIUtils.GetPriority( m_inputPorts[ 0 ].DataType ) > UIUtils.GetPriority( m_inputPorts[ 1 ].DataType ) ) ? m_inputPorts[ 0 ].DataType : m_inputPorts[ 1 ].DataType;
m_outputPorts[ 0 ].ChangeType( outputType, false );
}
}
 
public override void Draw( DrawInfo drawInfo )
{
base.Draw( drawInfo );
Rect newPos = m_remainingBox;
newPos.x += newPos.width * 0.3f * drawInfo.InvertedZoom;
newPos.y += newPos.height * 0.3f * drawInfo.InvertedZoom;
m_adjustPorts = EditorGUI.Toggle( newPos, m_adjustPorts );
}
 
public override void DrawProperties()
{
base.DrawProperties();
m_adjustPorts = EditorGUILayout.Toggle( "Auto-Adjust", m_adjustPorts );
}
 
public override string GenerateShaderForOutput( int outputId, ref MasterNodeDataCollector dataCollector, bool ignoreLocalvar )
{
// If local variable is already created then you need only to re-use it
if ( m_outputPorts[ 0 ].IsLocalValue )
return m_outputPorts[ 0 ].LocalValue;
 
WirePortDataType mainType = m_outputPorts[ 0 ].DataType;
//Get value from input 0
string valueInput0 = m_inputPorts[ 0 ].GenerateShaderForOutput( ref dataCollector, mainType, ignoreLocalvar, true );
 
//Get value from input 1
string valueInput1 = m_inputPorts[ 1 ].GenerateShaderForOutput( ref dataCollector, mainType, ignoreLocalvar, true );
 
// This is a simplified fmod operation only for demonstration purposes
string finalCalculation = string.Format( "frac({0}/{1})*{1}", valueInput0, valueInput1 );
 
//Register the final operation on a local variable associated with our output port
RegisterLocalVariable( 0, finalCalculation, ref dataCollector, "myLocalVar" + OutputId);
 
// Use the newly created local variable
return m_outputPorts[ 0 ].LocalValue;
}
 
public override void WriteToString( ref string nodeInfo, ref string connectionsInfo )
{
base.WriteToString( ref nodeInfo, ref connectionsInfo );
IOUtils.AddFieldValueToString( ref nodeInfo, m_adjustPorts );
}
 
public override void ReadFromString( ref string[] nodeParams )
{
base.ReadFromString( ref nodeParams );
m_adjustPorts = Convert.ToBoolean( GetCurrentParam( ref nodeParams ) );
}
}
}
</syntaxhighlight>
 
==== Advanced ====
 
Here we'll place additional info regarding the in-depth behavior of ASE.
 
===== Available Master Node Port Categories =====
 
* <b>Vertex:</b> Write to vertex function
* <b>Fragment:</b> Write to surface main function
* <b>Tessellation:</b> Write to tessellation function
* <b>Debug:</b> Special value that should only be set on the Master Node Debug port ( also writes into the surface main function
 
===== Available Data Types =====
 
* <b>OBJECT:</b> Generic data type ( don’t use it )
* <b>FLOAT:</b> Single Floating point value 
* <b>FLOAT2:</b> Two component vector (x,y)
* <b>FLOAT3:</b> Three component vector (x,y,z)
* <b>FLOAT4:</b> Four component vector (x,y,z,w)
* <b>FLOAT3x3:</b> 3x3 Square Matrix
* <b>FLOAT4x4:</b> 4x4 Square Matrix
* <b>COLOR:</b> Similar to FLOAT4 but represents a Color ( UI wize ) and also swizzles with .rgba
* <b>INT:</b> Integer
* <b>SAMPLER1D:</b> 1D Texture Object
* <b>SAMPLER2D:</b> 2D Texture Object
* <b>SAMPLER3D:</b> 3D Texture Object
* <b>SAMPLERCUBE:</b> CubeTexture Object
 
Sampler Types are very specific and shouldn’t be used. These are specific of the Texture Object nodes.
 
===== Type Priorities =====
 
{| class="wikitable"
|-
! align="center" | Type
! align="center" | Priority
|-
| align="center" | COLOR
| align="center" | 7
|-
| align="center" | FLOAT4
| align="center" | 7
|-
| align="center" | FLOAT3
| align="center" | 6
|-
|-
| align="center" | FLOAT2
| id="paramNoInterpolation" | No Interpolation
| align="center" | 5
| When active, makes sure it uses an interpolator with the nointerpolation flag used, which prevents the values assigned to it to be interpolated on the rasterization process.
|-
| False
| align="center" | FLOAT
| align="center" | 4
|-
| align="center" | INT
| align="center" | 3
|-
| align="center" | FLOAT4x4
| align="center" | 2
|-
| align="center" | FLOAT3x3
| align="center" | 1
|-
| align="center" | OBJECT
| align="center" | 0
|}
|}


The higher the number the greater the priority.
{| class="wikitable" style="width: 100%;"
 
|-
===== Adding Includes =====
! style="width: 10%;" | Input Port !! Description !! style="width: 10%;" | Type
 
|-  
You might need to add specific libraries into your shader which are needed by your node. This can be easily done by calling <b>AddToIncludes( int nodeId, string value )</b> where node id is your node unique id and value is the library you want to include:
| id="paramVSIn" | (VS) In
<syntaxhighlight lang="csharp">
| Value to be transferred to surface/fragment function.  
public override string GenerateShaderForOutput( int outputId, WirePortDataType inputPortType, ref MasterNodeDataCollector dataCollector, bool ignoreLocalvar )
| Float <sup id="ref1">[[#anchor|[1]]]</sup>
{
|}
// do awesome stuff
----
dataCollector.AddToIncludes( UniqueId, "UnityShaderVariables.cginc" );
# <span id="anchor1">'''[[#ref1|^]]'''</span> Port automatically adapts to all connection types except Matrices and [[Unity Products:Amplify Shader Editor/Texture Object|Sampler]] types.
// do awesome stuff
}
</syntaxhighlight>
 
===== Adding Shader Code Commentary =====
 
You can add commentaries directly into the shader source code via the:
AddCodeComments( bool forceForwardSlash, params string[] comments )
The forceForwardSlash forces // into multi-line comments instead of using /* */
<syntaxhighlight lang="csharp">
public override string GenerateShaderForOutput( int outputId, WirePortDataType inputPortType, ref MasterNodeDataCollector dataCollector, bool ignoreLocalvar )
{
dataCollector.AddCodeComments( false, "This is my", "awesome comment" );
// do awesome stuff
}
</syntaxhighlight>
 
 
===== Controlling Precision =====
By inheriting from ParentNode you also get access to <b>m_currentPrecisionType</b>, on which you can make accessible for the user to change by calling  <b>DrawPrecisionProperty()</b> on your DrawProperties() override method.
 
The shader overall precision is set by the master node. For you to get your current precision ( minimum between node and master node precision ) you can use our utils function:
*public static PrecisionType GetFinalPrecision( PrecisionType precision ).
 
And if you want to get the final cg variable type already taking precision into account you call use our utils function:
*public static string FinalPrecisionWirePortToCgType( PrecisionType precisionType, WirePortDataType type )
 
===== Adding previews =====
 
The first step is to create a shader that will render the preview, and register its GUID over the node's '''m_previewShaderGUID''' variable.<br/>
To know which GUID was assigned to the shader open the .meta file which Unity automatically generates for it on notepad, and copy the value after the '''guid:''' field to the m_previewShaderGUID variable.
<br/><br/>
Each input port of your node will be automatically mapped into a specific texture property over the shader as long as a naming rule is followed.
The mapping rule is done via the port unique id and not necessarily the order it is shown on the node.<br/>
So the input with id 0 is mapped on the shader to '''_A''' texture property, the one with id 1 is mapped to '''_B''' texture property, and so on.<br/>  
Taking a look at the simple example of the Smoothstep node, it registers the guid '''954cdd40a7a528344a0a4d3ff1db5176''', which points to the '''Preview_SmoothstepOpNode.shader''', over the CommonInit method.<br/>
<br/>
This node contains three input ports:
* Interpolator Value ( which on the node appears unnamed )
* Min
* Max
<br/>
Since in this case the declaration of the input ports matches its visual order of the node, '''Interpolator Value''' will be injected to '''_A''', '''Min''' will be injected to '''_B''' and '''Max''' will be injected into '''_C'''.<br/>
By opening the shader file we can see three declared properties ready to receive the input ports data.
<syntaxhighlight lang="csharp">
_A ("_Alpha", 2D) = "white" {}
_B ("_Min", 2D) = "white" {}
_C ("_Max", 2D) = "white" {}
</syntaxhighlight>
<br/>
Please mind that only the property name is relevant for ASE, the inspector names (_Alpha, _Min and _Max) can be whatever the developer wants, however its a good practice to name them to the corresponding input port names.
<br/>
If the node has multiple options that can affect how the input ports are internally used thus affecting the preview then these options can either be sent to the preview shader as additional properties or have multiple passes on it.
The best way to send additional properties is to override the '''SetPreviewInputs()''' method. In there any of the allowed property data types can be sent via the '''SetPreviewInputs''' material getter.
* SetPreviewInputs.SetFloat(...)
* SetPreviewInputs.SetVector(...)
* etc
<br/>
If a different pass is to be set, then the '''m_previewMaterialPassId''' variable must be changed to target the correct pass id. First pass has id 0 and so on.
 
A good example that can be used as reference is the '''Fresnel''' node as it uses multiple passes and send additional properties.
 
Also please notice the declaration of its input ports ( last parameter of the '''AddInputPort(...)''' call ), as their ids do not correspond to their visual order.
 
==== Unfinished Topics ====
This section is for topics still being written. We will move them to their proper categories as soon as they are finished.
 
===== Adding Properties/Global Variables =====
TODO: You can add shader properties and global variables via, once more, the data collector.
*void AddToProperties( int nodeId, string value, int orderIndex ):
 
===== Adding additional instructions =====
TODO
 
===== Adding functions =====
TODO
 
===== Master Node Port Category  =====
TODO: Talk about how important port signal propagation works and may need to be changed inside certain nodes so instructions are written on the correct place ( vertex function vs surface function vs tessellation function )


===== Usage of Constants class  =====
TODO: Talk about using static constants on this class to prevent future errors on nodes is API is changed


===== Master Node Port Execution Order  =====
[[Unity_Products:Amplify_Shader_Editor/Nodes | Back to Node List]]
TODO: Talk about Normal port being executed first and why
[[Category:Nodes]][[Category:Miscellaneous]]

Revision as of 15:49, 19 November 2020

Back to Node List

Vertex To Fragment Node

The Vertex To Fragment node allows data to be calculated on the vertex function and transferred to the surface/fragment via interpolators.
NOTE 1: No Interpolation cannot be used over the Standard Surface type as we must be able to directly control interpolators registry, which does't happen over this shader type.
NOTE 2: No Interpolation will not work across all API's and can even throw compilation errors on some of them ( p.e. Metal and GLES 2.0 ).

Nodes used: Word Normal, World Space Light Dir, Dot, Float, Power, Vertex To Fragment, Light Attenuation, Light Color, Multiply

Node Parameter Description Default Value
No Interpolation When active, makes sure it uses an interpolator with the nointerpolation flag used, which prevents the values assigned to it to be interpolated on the rasterization process. False
Input Port Description Type
(VS) In Value to be transferred to surface/fragment function. Float [1]

  1. ^ Port automatically adapts to all connection types except Matrices and Sampler types.


Back to Node List