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函数,我们还需要定义 GetPreDefineVariableRowsEvaluateVariable,他们会在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
        }
    }
}

参考

Tiling And Offset 节点

posted @ 2025-05-28 22:55  dewxin  阅读(24)  评论(0)    收藏  举报