ShaderForge如何实现 TilingAndOffset节点
简介
最近准备用ShaderForge实现一个ShaderGraph的教程,但是做着做着发现ShaderForge没有TilingAndOffset节点,于是准备自己实现一个。
新增SFN_UVTileOffset节点
新增一个节点,我们需要做3件事情,它们分别是 创建一个 节点的类, 将该类添加到template里面,接着为其创建同名的 shader,用于preview的渲染。
SFN_UVTileOffset类
首先,我们可以照猫画虎,找到SFN_UVTile类,修改一下就能实现 SFN_UVTileOffset。
using UnityEngine;
using UnityEditor;
using System.Collections;
//using System;
namespace ShaderForge
{
public class SFN_UVTileOffset : SF_Node
{
private const string UVIN_STR = "UVIN";
private const string Tile_STR = "Tile";
private const string Offset_STR = "Offset";
...
}
}
首先,我们需要实现Initialize函数,这个函数会在ShaderGraphWindow.AddNode添加node到nodes集合的时候被调用。
public override void Initialize()
{
Debug.Log("ShaderForge SFN_UVTileOffset.Initialize");
base.Initialize("UV Tile and Offset");
base.EnablePreview = true;
base.alwaysDefineVariable = true;
base.shaderGenMode = ShaderGenerationMode.CustomFunction;
ComponentCount = 2;
ports = new SF_NodePort[]{
SF_NodePort.Create(this,"UVOUT","UV",PortTypeEnum.Output,ValueTypeEnum.v2,false),
SF_NodePort.Create(this,UVIN_STR,"UV",PortTypeEnum.Input,ValueTypeEnum.v2,false).SetRequired(false).SetGhostNodeLink(typeof(SFN_TexCoord),"UVOUT"),
SF_NodePort.Create(this,Tile_STR,Tile_STR,PortTypeEnum.Input,ValueTypeEnum.v2,false).SetRequired(true), //TODO: is WithUseCount necessary?
SF_NodePort.Create(this,Offset_STR,Offset_STR,PortTypeEnum.Input,ValueTypeEnum.v2,false).SetRequired(true).WithUseCount(2),
};
}
这里首先手动调用了 另一个底层的Initialize函数,接着开启了Preview,这样我们可以预览该节点输出的texture。
alwaysDefineVariable设置为true,表示 “总是 需要定义一个变量, 不能作为右值形式存在”。
shaderGenMode暂时没有太多作用。
ComponentCount和precision组合起来就是该node的 variable type,可以在GetVariableType函数中印证。
接着就是该节点和其他节点相互连接的端口。这里我们定义了1一个v2类型的输出节点,以及3个输入节点。
除了 Initialize函数,我们还需要定义 GetPreDefineVariableRows ,EvaluateVariable,他们会在DefineVarible函数被调用,主要作用是往ShaderGenerator里面添加当前节点对应的代码。
public override string[] GetPreDefineVariableRows()
{
return new string[] {
$"float2 {GetVariableName()}_xy = {Port(UVIN_STR).TryEvaluate()}.xy * {Port(Tile_STR).TryEvaluate()}.xy + {Port(Offset_STR).TryEvaluate()}.xy;" ,
$"float {GetVariableName()}_x = {GetVariableName()}_xy.x - floor({GetVariableName()}_xy.x);",
$"float {GetVariableName()}_y = {GetVariableName()}_xy.y - floor({GetVariableName()}_xy.y);"
};
}
public override string EvaluateVariable(OutChannelEnum channel = OutChannelEnum.All)
{
return $"float2({GetVariableName()}_x, {GetVariableName()}_y)";
}
将节点添加到template
这里,我们还需要在 InitializeNodeTemplates函数中,手动添加刚刚创建的类,找到"UV Operations"对应的位置,加入AddTemplate( typeof( SFN_UVTileOffset ), catUvOps + "UV Tile and Offset" ); 代码。
这样,我们就可以使用刚刚写好的SFN_UVTileOffset节点了。
shader
最后我们需要定义一个shader用于 NodePreview的渲染, 这里Properties的名字需要和上面创建的类的port的name相对应。并且传入参数都必须 2D类型,对应到Material.SetTexture函数。
这里我们也是 照着SFN_UVTile.shader改改就行。
Shader "Hidden/Shader Forge/SFN_UVTileOffset" {
Properties {
_OutputMask ("Output Mask", Vector) = (1,1,1,1)
_UVIN ("UV", 2D) = "black" {}
_Tile ("Tile", 2D) = "black" {}
_Offset ("Offset", 2D) = "black" {}
}
SubShader {
Tags {
"RenderType"="Opaque"
}
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#define UNITY_PASS_FORWARDBASE
#include "UnityCG.cginc"
#pragma target 3.0
uniform float4 _OutputMask;
uniform sampler2D _UVIN;
uniform sampler2D _Tile;
uniform sampler2D _Offset;
struct VertexInput {
float4 vertex : POSITION;
float2 texcoord0 : TEXCOORD0;
};
struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
o.uv = v.texcoord0;
o.pos = UnityObjectToClipPos(v.vertex );
return o;
}
float4 frag(VertexOutput i) : COLOR {
// Read inputs
float4 _uvin = tex2D( _UVIN, i.uv );
float4 _tile = tex2D( _Tile, i.uv );
float4 _offset = tex2D( _Offset, i.uv );
float2 xy = _uvin.xy * _tile.xy + _offset.xy;
float x = xy.x - floor(xy.x);
float y = xy.y - floor(xy.y);
// Operator
float4 outputColor = float4(x, y ,0,0);
// Return
return outputColor * _OutputMask;
}
ENDCG
}
}
}

浙公网安备 33010602011771号