unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码

首先欢迎大家关注订阅我的小专栏,里面会定时更新一些游戏开发的内容。

网址:https://xiaozhuanlan.com/xiaochengzi

本专栏讲述利用Unity进行游戏开发使用到的一些热门技术、框架、shader、优化技巧等等,偏向在工作中的实际应用,也为广大游戏开发者提供一些宝贵的经验。
专栏内容:
1.unity优化技巧
2.工作中常用的一些热门通用技术。
3.unity shader 讲解
4.游戏框架
5.unity一些好用的插件、组件的介绍
6.自己写的一些辅助插件、辅助工具
7.安卓、IOS打包、真机调试、Profiler性能分析技巧专讲
8.Jenkins自动化构建出包(编写Windows批处理命令)
9.游戏中的帧同步、状态同步技术专讲
持续更新。。。
关于我:
目前在职盛趣游戏(原盛大游戏),担任游戏客户端程序开发一职,在职1年多。

ShaderForge简介

ShaderForge是适用于Unity,基于节点的着色器编辑器 。它可让你更轻松地创建自己的着色器 ,而不需要使用代码。只需要经过连线编译,就能很好的实现一些基于材质shader的效果或特效。用来做一些简单的特效,亦或是学习了解shader的语法,都是一个很好的选择。

以下是做的一些效果图,我将以卡通效果和屏幕等宽的outline来进行相关研究介绍。

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第1张图片

一.什么是屏幕等宽的描边(outline)?

屏幕等宽是指无论离摄像机镜头远近都是固定的像素,作为描边宽度。shaderforge是有描边节点的,只需要设置描边宽度和颜色,就可以实现描边了。但是为什么不用它自带的呢?

因为它不是屏幕等宽的描边,当你的镜头离模型很近时,他的描边也会变粗,如下图:

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第2张图片

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第3张图片

这样会影响观察效果,所以在大多情况下,我们应该无论离摄像机镜头远近都是固定的像素的描边宽度,这样才是合理的。如下图:

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第4张图片

二.修改shaderforge的源代码,创建自定义节点、属性

用过shaderforge的同学应该都知道,这个插件做出来的shader只需要在可视化界面中做做连线,修改参数等操作就可以做出一些不错的效果了,拿卡通效果和描边来说,如下图:

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第5张图片

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第6张图片

但是当你需要实现一些其他效果或者方案时,如果你找不到相关节点,比如我要做屏幕等宽的描边,但是右边的main节点中的outlineWidth是非屏幕等宽的描边。那么该怎么办?两种方案解决。

1.直接打开该shader,修改名字为outline的pass块,把自动生成的非等宽描边的计算删掉,重新写一个屏幕等宽的描边计算公式,代码如下:

Pass {
            Name "Outline"
            Tags {
            }
            Cull Front
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma fragmentoption ARB_precision_hint_fastest
            #pragma multi_compile_shadowcaster
            #pragma multi_compile_fog
            #pragma only_renderers d3d9 d3d11 glcore gles gles3 
            #pragma target 3.0
            uniform float _OutLineSlider;
            uniform float4 _OutLineColor;
            struct VertexInput {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            struct VertexOutput {
                float4 pos : SV_POSITION;
                UNITY_FOG_COORDS(0)
            };
            VertexOutput vert (VertexInput v) {
                VertexOutput o = (VertexOutput)0;
                o.pos = UnityObjectToClipPos( float4(v.vertex.xyz + v.normal*lerp(0,0.05,_OutLineSlider),1) );
                o.pos = UnityObjectToClipPos( v.vertex );
                UNITY_TRANSFER_FOG(o,o.pos);
                return o;
            }
            float4 frag(VertexOutput i) : COLOR {
                return fixed4(_OutLineColor.rgb,0);
            }
            ENDCG
        }

这段代码是当有连线连到outlineWidth和color时shaderforge自动生成的outline描边的代码pass块,然后你的模型就会显示一个描边了,宽度和颜色可以在shader面板中根据需要调节。

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第7张图片

如果要实现等宽描边,把这块删掉重写(主要是对顶点、片元函数的代码块的修改,其他属性基本不变),代码如下:

Shader "Shader Forge/Toon03" {
    Properties {
        _Texture ("Texture", 2D) = "white" {}
        _OutlineColor ("OutlineColor", Color) = (0,0,0,1)
		_OutlineWidth("OutlineWidth",Range(0, 1))=0.01
        _SepColor ("SepColor", 2D) = "white" {}
    }
    SubShader {
		Cull Off
		//使用cull front把外表面裁掉

        Tags {
            "RenderType"="Opaque"
			"Queue" = "Geometry"
        }
        Pass {
            Name "Outline"

            Cull Front
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma fragmentoption ARB_precision_hint_fastest
            #pragma multi_compile_shadowcaster
            #pragma multi_compile_fog
            #pragma only_renderers d3d9 d3d11 glcore gles gles3 
            #pragma target 3.0

            uniform float4 _OutlineColor;
			uniform float _OutlineWidth;

            struct VertexInput {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            struct VertexOutput {
                float4 pos : SV_POSITION;
                UNITY_FOG_COORDS(0)
            };
            VertexOutput vert (VertexInput v) {
               VertexOutput o = (VertexOutput)0;     


				float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
				float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
				normal.z = -0.5;
				//这两行代码就是保持边缘曲线远近距离大小一样的关键
				float dist = distance(_WorldSpaceCameraPos, mul(unity_ObjectToWorld, v.vertex));
				pos = pos + float4(normalize(normal), 0) * _OutlineWidth * 0.01 * dist;
                o.pos = mul(UNITY_MATRIX_P, pos);
				
				
				
				UNITY_TRANSFER_FOG(o,o.pos);
                return o;
            }
            float4 frag(VertexOutput i) : SV_Target{
			  return float4(_OutlineColor.rgb, 1);
            }
            ENDCG
        }
	
        Pass {
            Name "FORWARD"
            Tags {
                "LightMode"="ForwardBase"
            } 
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #define UNITY_PASS_FORWARDBASE
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"
            #pragma multi_compile_fwdbase_fullshadows
            #pragma multi_compile_fog
            #pragma only_renderers d3d9 d3d11 glcore gles gles3 
            #pragma target 3.0
            uniform sampler2D _Texture; uniform float4 _Texture_ST;
            uniform sampler2D _SepColor; uniform float4 _SepColor_ST;
            struct VertexInput {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            struct VertexOutput {
                float4 pos : SV_POSITION;
                float4 posWorld : TEXCOORD0;
                float3 normalDir : TEXCOORD1;
                LIGHTING_COORDS(2,3)
                UNITY_FOG_COORDS(4)
            };
            VertexOutput vert (VertexInput v) {
                VertexOutput o = (VertexOutput)0;
                o.normalDir = UnityObjectToWorldNormal(v.normal);
                o.posWorld = mul(unity_ObjectToWorld, v.vertex);
                o.pos = UnityObjectToClipPos( v.vertex );
                UNITY_TRANSFER_FOG(o,o.pos);
                TRANSFER_VERTEX_TO_FRAGMENT(o)
                return o;
            }
            float4 frag(VertexOutput i) : COLOR {
                i.normalDir = normalize(i.normalDir);
                float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
                float3 normalDirection = i.normalDir;
                float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
                float3 halfDirection = normalize(viewDirection+lightDirection);
////// Lighting:
                float attenuation = LIGHT_ATTENUATION(i);
                float node_1974 = max(0,dot(halfDirection,i.normalDir));
                float2 node_1679 = float2(node_1974,node_1974);
                float4 _SepColor_var = tex2D(_SepColor,TRANSFORM_TEX(node_1679, _SepColor));
                float node_6399 = (max(0,dot(i.normalDir,lightDirection))*attenuation);
                float2 node_3750 = float2(node_6399,node_6399);
                float4 _Texture_var = tex2D(_Texture,TRANSFORM_TEX(node_3750, _Texture));
                float3 finalColor = (_SepColor_var.rgb+_Texture_var.rgb);
                fixed4 finalRGBA = fixed4(finalColor,1);
                UNITY_APPLY_FOG(i.fogCoord, finalRGBA);
                return finalRGBA;
            }
            ENDCG
        }
        Pass {
            Name "FORWARD_DELTA"
            Tags {
                "LightMode"="ForwardAdd"
            }
            Blend One One
            
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #define UNITY_PASS_FORWARDADD
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"
            #pragma multi_compile_fwdadd_fullshadows
            #pragma multi_compile_fog
            #pragma only_renderers d3d9 d3d11 glcore gles gles3 
            #pragma target 3.0
            uniform sampler2D _Texture; uniform float4 _Texture_ST;
            uniform sampler2D _SepColor; uniform float4 _SepColor_ST;
            struct VertexInput {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            struct VertexOutput {
                float4 pos : SV_POSITION;
                float4 posWorld : TEXCOORD0;
                float3 normalDir : TEXCOORD1;
                LIGHTING_COORDS(2,3)
                UNITY_FOG_COORDS(4)
            };
            VertexOutput vert (VertexInput v) {
                VertexOutput o = (VertexOutput)0;
                o.normalDir = UnityObjectToWorldNormal(v.normal);
                o.posWorld = mul(unity_ObjectToWorld, v.vertex);
                o.pos = UnityObjectToClipPos( v.vertex );
                UNITY_TRANSFER_FOG(o,o.pos);
                TRANSFER_VERTEX_TO_FRAGMENT(o)
                return o;
            }
            float4 frag(VertexOutput i) : COLOR {
                i.normalDir = normalize(i.normalDir);
                float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
                float3 normalDirection = i.normalDir;
                float3 lightDirection = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz - i.posWorld.xyz,_WorldSpaceLightPos0.w));
                float3 halfDirection = normalize(viewDirection+lightDirection);
////// Lighting:
                float attenuation = LIGHT_ATTENUATION(i);
                float node_1974 = max(0,dot(halfDirection,i.normalDir));
                float2 node_1679 = float2(node_1974,node_1974);
                float4 _SepColor_var = tex2D(_SepColor,TRANSFORM_TEX(node_1679, _SepColor));
                float node_6399 = (max(0,dot(i.normalDir,lightDirection))*attenuation);
                float2 node_3750 = float2(node_6399,node_6399);
                float4 _Texture_var = tex2D(_Texture,TRANSFORM_TEX(node_3750, _Texture));
                float3 finalColor = (_SepColor_var.rgb+_Texture_var.rgb);
                fixed4 finalRGBA = fixed4(finalColor * 1,0);
                UNITY_APPLY_FOG(i.fogCoord, finalRGBA);
                return finalRGBA;
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
    CustomEditor "ShaderForgeMaterialInspector"
}

这是完整的shader代码,包括卡通效果和描边处理。(重点看outline的pass块里面的顶点函数和片元函数,主要这边的算法不一样)。

2.第二种方案,就是这篇文章的重点了。大家导入shaderforge插件后,会看到根目录下面有一个shaderforge.dll文件。这个dll就是shaderforge的源代码生成的dll。里面包含了shaderforge的所有内容(创建节点、连线事件、自动生成shader代码等等)。你把所有其他与shaderforge有关的文件和资源全部删掉,只留下这个dll就能进行可视化shader制作了。

所以,我们要做的就是找到shaderforge的源代码,在main节点下创建一个outlineEqualWidth的属性节点,然后连线到这个节点后,能够自动生成等宽描边的shader代码,这就完成了。最后,改完源代码,创建一个新的工程,把源代码导入进去,生成dll,替换原来的dll就完事了,自定义的等宽描边就可以使用了!(下图的倒数第5个属性节点)

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第8张图片

三、修改源代码的步骤:

shaderforge的源码在GitHub上就有,文件夹名字叫code,在ShaderForge-》Editor目录下,和InternalResourse同级目录,有的插件在Editor目录下会放置一个源代码的压缩包,解压后源代码也会出来。

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第9张图片

(注意:如果源代码和dll都在unity工程里,会报很多错误,同类代码冲突。所以,先把shaderforge.dll给删除,后面我们生成自己的dll,删除后只要源代码还在,之前做的效果是不会出问题的,这俩本来就是一个东西)

shaderforge的源代码研究起来不简单,注释很少,没也有相关教程和文档,所以只能摸索研究了,所以希望这篇博客对其他同学有所帮助。

1.首先找到SF_FeatureChecker.cs脚本,这个脚本是定义和创建main节点里各个属性。

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第10张图片

在这里加上outlineEqualWidth字段。定义等宽描边属性

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第11张图片

在 Initialize函数中创建等宽描边属性节点(直接复制outlineWidth的那行代码,把strID(olewid)这个改了就行,其他的参数不变)。这下在可视化视图中就有outlineEqualWidth了。

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第12张图片

现在连线到这个节点后还没有任何效果,我们继续来。

2.找到SF_FeatureChecker.cs脚本。在UpdateAvailability() 函数中修改一行代码,然后添加一行代码。

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第13张图片

修改的代码:editor.mainNode.outlineColor.SetAvailable((editor.mainNode.outlineEqualWidth.IsConnectedAndEnabled()||editor.mainNode.outlineWidth.IsConnectedAndEnabled()) && !deferredPp);

这行加上 editor.mainNode.outlineEqualWidth.IsConnectedAndEnabled()。的判断,不然outlineColor不能与我们自定义描边进行融合处理。

添加的代码:

 editor.mainNode.outlineEqualWidth.SetAvailable(!deferredPp);

这样当我们连线到这个节点属性时,就可以有后续交互了。

3.找到SF_PassSetting.cs脚本。加上一个字段,后面进行计算描边的时候会得到连线到outlineEqualWidth的属性的值。

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第14张图片

4.最后进入我们最核心的脚本SF_Evaluator.cs。

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第15张图片

passType是定义pass块类型的,这里大家可以创建一个OutlineEqual字段。我这里没这么做,因为两种描边只是在顶点和片元函数中的计算处理中不同,其他地方都一样,我这里定义了一个bool值,用来判断是否是等宽屏幕的描边。

 

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第16张图片

下面就行进行自动生成shader代码的处理了。首先在OutlinePass函数中加上isOutlineEqualWidth=false。说明这里是非等宽描边的处理。 

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第17张图片

然后把这个函数复制出来一个,名字改成OutlineEqualPass。在这个函数的基础上去改变一些代码。

 unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第18张图片

首先加上isOutlineEqualWidth =ture。下面就是生成代码的函数了,我们挨个去修改。

找到Fragment()片元函数。修改两处。

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第19张图片

 unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第20张图片

在2593行添加一个分支判断,如果是等宽描边生成的片元函数头部取得的是 SV_Target 。

 else if (currentPass == PassType.Outline&&isOutlineEqualWidth==true) {  //添加一段等宽描边的判断
                string vface = "";
                if (dependencies.frag_facing)
                {
                    vface = ", float facing : VFACE";
                }
                App("float4 frag(VertexOutput i" + vface + ") : SV_Target {");
            }

2664行非等宽描边加上&&isOutlineEqualWidth==false判断,然后在加上等宽描边的分支判断。

  else if( currentPass == PassType.Outline&&isOutlineEqualWidth==false ) {
                App( "return fixed4(" + ps.n_outlineColor + ",0);" );
            }
            ///程广英自定义的代码
            else if (currentPass == PassType.Outline && isOutlineEqualWidth == true)
            {
                App("return float4(" + ps.n_outlineColor + ",1);");
            }

 

片元函数修改完了,接下来修改顶点函数的代码处理就行了。找到Vertex()函数。  

2466行的 if( currentPass == PassType.Outline &&isOutlineEqualWidth==false) 这行代码添加一个&&isOutlineEqualWidth==false判断。

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第21张图片

然后添加一个if分支进行等宽描边的计算处理。如上图,这些代码不在细讲。APP()函数里面的内容就是后面连线到outlineEqualWidth后自动生成的代码段。等宽描边的计算原理主要就是这些公司,n_outlineEqualWidth这个字段的声明之前已经做过了,我这边连线的是slider节点,slider的值会传到这里进行计算。

至此,所有工作已经完成,当利用shaderforge进行可视化编辑时,如果有描边处理,连接到了outlineEqualWidth后,会自动生成相关pass块,如下代码:

Pass {
            Name "Outline"
            Tags {
            }
            Cull Front
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma fragmentoption ARB_precision_hint_fastest
            #pragma multi_compile_shadowcaster
            #pragma multi_compile_fog
            #pragma only_renderers d3d9 d3d11 glcore gles gles3 
            #pragma target 3.0
            uniform float _Outline;
            uniform float4 _OutlineColor;
            struct VertexInput {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            struct VertexOutput {
                float4 pos : SV_POSITION;
                UNITY_FOG_COORDS(0)
            };
            VertexOutput vert (VertexInput v) {
                VertexOutput o = (VertexOutput)0;
                float4 pos =mul(UNITY_MATRIX_MV, v.vertex);
                float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
                normal.z = -0.5;
                float dist = distance(_WorldSpaceCameraPos, mul(unity_ObjectToWorld, v.vertex));
                pos = pos + float4(normalize(normal), 0) * _Outline * 0.01 * dist;
                o.pos = mul(UNITY_MATRIX_P, pos);
                UNITY_TRANSFER_FOG(o,o.pos);
                return o;
            }
            float4 frag(VertexOutput i) : SV_Target {
                return float4(_OutlineColor.rgb,1);
            }
            ENDCG
        }

是不是跟之前我们方案一里面通过修改shader代码里面的代码一样呢?

四、生成dll文件

源代码修改好后,在visual studio中创建一个C#类库(名字叫ShaderForge),添加两个引用,unityEditor.dll和unityEngine.dll

这两个dll在你的unity文件夹里自己找,都有的。

然后把code(修改好的源代码)文件夹导入进来,生成解决方案即可生成shaderforge.dll。然后把这个dll替换掉之前unity工程里的shaderforge.dll。如果有源代码的话源代码可以删除掉了,不然会有代码冲突。

注意:

1.生成时,目标框架更改为.Net Framework3.5及以下,高版本的dll导入到unity2018不支持。

2.如果生成时报错(public修饰符不可用、XX方法已过时等等)。定位到这些错误,如果有if Unity_2018 #else  #else if字样。保留#ifUNITY_2018  #else之间的代码。把#else    #endif之间的代码删掉,说明其中的代码中一些方法在unity2018里已经过时不可用。

unity shaderforge编辑器扩展相关的研究(修改其源代码实现一些自定义节点或功能、属性)——创建屏幕等宽的outline(描边)节点和实现代码_第22张图片

 

至此,shaderforge的编辑器扩展及源代码修改的工作完成。 

你可能感兴趣的:(unity,Shader)