当前位置:   article > 正文

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

unity shader forge源码解读

 

ShaderForge简介

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

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

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

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

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

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

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

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

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

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

  1. Pass {
  2. Name "Outline"
  3. Tags {
  4. }
  5. Cull Front
  6. CGPROGRAM
  7. #pragma vertex vert
  8. #pragma fragment frag
  9. #include "UnityCG.cginc"
  10. #pragma fragmentoption ARB_precision_hint_fastest
  11. #pragma multi_compile_shadowcaster
  12. #pragma multi_compile_fog
  13. #pragma only_renderers d3d9 d3d11 glcore gles gles3
  14. #pragma target 3.0
  15. uniform float _OutLineSlider;
  16. uniform float4 _OutLineColor;
  17. struct VertexInput {
  18. float4 vertex : POSITION;
  19. float3 normal : NORMAL;
  20. };
  21. struct VertexOutput {
  22. float4 pos : SV_POSITION;
  23. UNITY_FOG_COORDS(0)
  24. };
  25. VertexOutput vert (VertexInput v) {
  26. VertexOutput o = (VertexOutput)0;
  27. o.pos = UnityObjectToClipPos( float4(v.vertex.xyz + v.normal*lerp(0,0.05,_OutLineSlider),1) );
  28. o.pos = UnityObjectToClipPos( v.vertex );
  29. UNITY_TRANSFER_FOG(o,o.pos);
  30. return o;
  31. }
  32. float4 frag(VertexOutput i) : COLOR {
  33. return fixed4(_OutLineColor.rgb,0);
  34. }
  35. ENDCG
  36. }

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

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

  1. Shader "Shader Forge/Toon03" {
  2. Properties {
  3. _Texture ("Texture", 2D) = "white" {}
  4. _OutlineColor ("OutlineColor", Color) = (0,0,0,1)
  5. _OutlineWidth("OutlineWidth",Range(0, 1))=0.01
  6. _SepColor ("SepColor", 2D) = "white" {}
  7. }
  8. SubShader {
  9. Cull Off
  10. //使用cull front把外表面裁掉
  11. Tags {
  12. "RenderType"="Opaque"
  13. "Queue" = "Geometry"
  14. }
  15. Pass {
  16. Name "Outline"
  17. Cull Front
  18. CGPROGRAM
  19. #pragma vertex vert
  20. #pragma fragment frag
  21. #include "UnityCG.cginc"
  22. #pragma fragmentoption ARB_precision_hint_fastest
  23. #pragma multi_compile_shadowcaster
  24. #pragma multi_compile_fog
  25. #pragma only_renderers d3d9 d3d11 glcore gles gles3
  26. #pragma target 3.0
  27. uniform float4 _OutlineColor;
  28. uniform float _OutlineWidth;
  29. struct VertexInput {
  30. float4 vertex : POSITION;
  31. float3 normal : NORMAL;
  32. };
  33. struct VertexOutput {
  34. float4 pos : SV_POSITION;
  35. UNITY_FOG_COORDS(0)
  36. };
  37. VertexOutput vert (VertexInput v) {
  38. VertexOutput o = (VertexOutput)0;
  39. float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
  40. float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
  41. normal.z = -0.5;
  42. //这两行代码就是保持边缘曲线远近距离大小一样的关键
  43. float dist = distance(_WorldSpaceCameraPos, mul(unity_ObjectToWorld, v.vertex));
  44. pos = pos + float4(normalize(normal), 0) * _OutlineWidth * 0.01 * dist;
  45. o.pos = mul(UNITY_MATRIX_P, pos);
  46. UNITY_TRANSFER_FOG(o,o.pos);
  47. return o;
  48. }
  49. float4 frag(VertexOutput i) : SV_Target{
  50. return float4(_OutlineColor.rgb, 1);
  51. }
  52. ENDCG
  53. }
  54. Pass {
  55. Name "FORWARD"
  56. Tags {
  57. "LightMode"="ForwardBase"
  58. }
  59. CGPROGRAM
  60. #pragma vertex vert
  61. #pragma fragment frag
  62. #define UNITY_PASS_FORWARDBASE
  63. #include "UnityCG.cginc"
  64. #include "AutoLight.cginc"
  65. #include "Lighting.cginc"
  66. #pragma multi_compile_fwdbase_fullshadows
  67. #pragma multi_compile_fog
  68. #pragma only_renderers d3d9 d3d11 glcore gles gles3
  69. #pragma target 3.0
  70. uniform sampler2D _Texture; uniform float4 _Texture_ST;
  71. uniform sampler2D _SepColor; uniform float4 _SepColor_ST;
  72. struct VertexInput {
  73. float4 vertex : POSITION;
  74. float3 normal : NORMAL;
  75. };
  76. struct VertexOutput {
  77. float4 pos : SV_POSITION;
  78. float4 posWorld : TEXCOORD0;
  79. float3 normalDir : TEXCOORD1;
  80. LIGHTING_COORDS(2,3)
  81. UNITY_FOG_COORDS(4)
  82. };
  83. VertexOutput vert (VertexInput v) {
  84. VertexOutput o = (VertexOutput)0;
  85. o.normalDir = UnityObjectToWorldNormal(v.normal);
  86. o.posWorld = mul(unity_ObjectToWorld, v.vertex);
  87. o.pos = UnityObjectToClipPos( v.vertex );
  88. UNITY_TRANSFER_FOG(o,o.pos);
  89. TRANSFER_VERTEX_TO_FRAGMENT(o)
  90. return o;
  91. }
  92. float4 frag(VertexOutput i) : COLOR {
  93. i.normalDir = normalize(i.normalDir);
  94. float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
  95. float3 normalDirection = i.normalDir;
  96. float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
  97. float3 halfDirection = normalize(viewDirection+lightDirection);
  98. // Lighting:
  99. float attenuation = LIGHT_ATTENUATION(i);
  100. float node_1974 = max(0,dot(halfDirection,i.normalDir));
  101. float2 node_1679 = float2(node_1974,node_1974);
  102. float4 _SepColor_var = tex2D(_SepColor,TRANSFORM_TEX(node_1679, _SepColor));
  103. float node_6399 = (max(0,dot(i.normalDir,lightDirection))*attenuation);
  104. float2 node_3750 = float2(node_6399,node_6399);
  105. float4 _Texture_var = tex2D(_Texture,TRANSFORM_TEX(node_3750, _Texture));
  106. float3 finalColor = (_SepColor_var.rgb+_Texture_var.rgb);
  107. fixed4 finalRGBA = fixed4(finalColor,1);
  108. UNITY_APPLY_FOG(i.fogCoord, finalRGBA);
  109. return finalRGBA;
  110. }
  111. ENDCG
  112. }
  113. Pass {
  114. Name "FORWARD_DELTA"
  115. Tags {
  116. "LightMode"="ForwardAdd"
  117. }
  118. Blend One One
  119. CGPROGRAM
  120. #pragma vertex vert
  121. #pragma fragment frag
  122. #define UNITY_PASS_FORWARDADD
  123. #include "UnityCG.cginc"
  124. #include "AutoLight.cginc"
  125. #include "Lighting.cginc"
  126. #pragma multi_compile_fwdadd_fullshadows
  127. #pragma multi_compile_fog
  128. #pragma only_renderers d3d9 d3d11 glcore gles gles3
  129. #pragma target 3.0
  130. uniform sampler2D _Texture; uniform float4 _Texture_ST;
  131. uniform sampler2D _SepColor; uniform float4 _SepColor_ST;
  132. struct VertexInput {
  133. float4 vertex : POSITION;
  134. float3 normal : NORMAL;
  135. };
  136. struct VertexOutput {
  137. float4 pos : SV_POSITION;
  138. float4 posWorld : TEXCOORD0;
  139. float3 normalDir : TEXCOORD1;
  140. LIGHTING_COORDS(2,3)
  141. UNITY_FOG_COORDS(4)
  142. };
  143. VertexOutput vert (VertexInput v) {
  144. VertexOutput o = (VertexOutput)0;
  145. o.normalDir = UnityObjectToWorldNormal(v.normal);
  146. o.posWorld = mul(unity_ObjectToWorld, v.vertex);
  147. o.pos = UnityObjectToClipPos( v.vertex );
  148. UNITY_TRANSFER_FOG(o,o.pos);
  149. TRANSFER_VERTEX_TO_FRAGMENT(o)
  150. return o;
  151. }
  152. float4 frag(VertexOutput i) : COLOR {
  153. i.normalDir = normalize(i.normalDir);
  154. float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.posWorld.xyz);
  155. float3 normalDirection = i.normalDir;
  156. float3 lightDirection = normalize(lerp(_WorldSpaceLightPos0.xyz, _WorldSpaceLightPos0.xyz - i.posWorld.xyz,_WorldSpaceLightPos0.w));
  157. float3 halfDirection = normalize(viewDirection+lightDirection);
  158. // Lighting:
  159. float attenuation = LIGHT_ATTENUATION(i);
  160. float node_1974 = max(0,dot(halfDirection,i.normalDir));
  161. float2 node_1679 = float2(node_1974,node_1974);
  162. float4 _SepColor_var = tex2D(_SepColor,TRANSFORM_TEX(node_1679, _SepColor));
  163. float node_6399 = (max(0,dot(i.normalDir,lightDirection))*attenuation);
  164. float2 node_3750 = float2(node_6399,node_6399);
  165. float4 _Texture_var = tex2D(_Texture,TRANSFORM_TEX(node_3750, _Texture));
  166. float3 finalColor = (_SepColor_var.rgb+_Texture_var.rgb);
  167. fixed4 finalRGBA = fixed4(finalColor * 1,0);
  168. UNITY_APPLY_FOG(i.fogCoord, finalRGBA);
  169. return finalRGBA;
  170. }
  171. ENDCG
  172. }
  173. }
  174. FallBack "Diffuse"
  175. CustomEditor "ShaderForgeMaterialInspector"
  176. }

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

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

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

三、修改源代码的步骤:

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

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

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

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

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

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

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

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

修改的代码: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的属性的值。

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

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

 

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

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

 

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

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

 

在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判断。

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

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

  1. Pass {
  2. Name "Outline"
  3. Tags {
  4. }
  5. Cull Front
  6. CGPROGRAM
  7. #pragma vertex vert
  8. #pragma fragment frag
  9. #include "UnityCG.cginc"
  10. #pragma fragmentoption ARB_precision_hint_fastest
  11. #pragma multi_compile_shadowcaster
  12. #pragma multi_compile_fog
  13. #pragma only_renderers d3d9 d3d11 glcore gles gles3
  14. #pragma target 3.0
  15. uniform float _Outline;
  16. uniform float4 _OutlineColor;
  17. struct VertexInput {
  18. float4 vertex : POSITION;
  19. float3 normal : NORMAL;
  20. };
  21. struct VertexOutput {
  22. float4 pos : SV_POSITION;
  23. UNITY_FOG_COORDS(0)
  24. };
  25. VertexOutput vert (VertexInput v) {
  26. VertexOutput o = (VertexOutput)0;
  27. float4 pos =mul(UNITY_MATRIX_MV, v.vertex);
  28. float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
  29. normal.z = -0.5;
  30. float dist = distance(_WorldSpaceCameraPos, mul(unity_ObjectToWorld, v.vertex));
  31. pos = pos + float4(normalize(normal), 0) * _Outline * 0.01 * dist;
  32. o.pos = mul(UNITY_MATRIX_P, pos);
  33. UNITY_TRANSFER_FOG(o,o.pos);
  34. return o;
  35. }
  36. float4 frag(VertexOutput i) : SV_Target {
  37. return float4(_OutlineColor.rgb,1);
  38. }
  39. ENDCG
  40. }

是不是跟之前我们方案一里面通过修改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里已经过时不可用。

 

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/107772
推荐阅读
相关标签
  

闽ICP备14008679号