赞
踩
ShaderForge是适用于Unity,基于节点的着色器编辑器 。它可让你更轻松地创建自己的着色器 ,而不需要使用代码。只需要经过连线编译,就能很好的实现一些基于材质shader的效果或特效。用来做一些简单的特效,亦或是学习了解shader的语法,都是一个很好的选择。
以下是做的一些效果图,我将以卡通效果和屏幕等宽的outline来进行相关研究介绍。
一.什么是屏幕等宽的描边(outline)?
屏幕等宽是指无论离摄像机镜头远近都是固定的像素,作为描边宽度。shaderforge是有描边节点的,只需要设置描边宽度和颜色,就可以实现描边了。但是为什么不用它自带的呢?
因为它不是屏幕等宽的描边,当你的镜头离模型很近时,他的描边也会变粗,如下图:
这样会影响观察效果,所以在大多情况下,我们应该无论离摄像机镜头远近都是固定的像素的描边宽度,这样才是合理的。如下图:
二.修改shaderforge的源代码,创建自定义节点、属性
用过shaderforge的同学应该都知道,这个插件做出来的shader只需要在可视化界面中做做连线,修改参数等操作就可以做出一些不错的效果了,拿卡通效果和描边来说,如下图:
但是当你需要实现一些其他效果或者方案时,如果你找不到相关节点,比如我要做屏幕等宽的描边,但是右边的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面板中根据需要调节。
如果要实现等宽描边,把这块删掉重写(主要是对顶点、片元函数的代码块的修改,其他属性基本不变),代码如下:
- 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个属性节点)
三、修改源代码的步骤:
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块,如下代码:
- 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里已经过时不可用。
至此,shaderforge的编辑器扩展及源代码修改的工作完成。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。