大家好,我是阿赵。继续介绍Unity可视化Shader编辑插件ASE的用法。上一篇已经介绍了很多ASE常用的节点。这一篇通过几个小例子,来看看这些节点是怎样连接使用的。
这篇的内容可能会比较长,最终是做了一个遮挡X光的效果,不过把这个效果拆分成多个步骤,每个步骤是一个小例子。
一、贴图+颜色
首先来一个最简单的,值显示模型的贴图和颜色。
先准备一个需要展示的模型:
然后新建一个ASE的shader
在实际的项目中,我一直很喜欢用这个Unlit的shader模板。这是因为Surface类型虽然功能非常的多,而且效果很好,但可控性不如单纯的顶点片段着色器这么好。而这个Unlit,其实就是单纯的顶点片段着色器。如果需要光照模型、法线贴图等功能,实际上也是可以在Unlit的基础上,自己实现的。
创建了shader之后,打开节点编辑器,看到了这样一个情况。然后按着键盘t键再点击鼠标,创建一个贴图采样节点
双击节点名称,可以修改变量名。我这里把这张贴图的名字改成了MainTex
注意看属性面板,虽然节点的名称改成了MainTex,但实际上属性名称是_MainTex:
接下来,把这个贴图的RGBA输出,拖动到输出节点的FragColor接收接口
如果想给这个节点一张默认的贴图,可以点击select按钮,然后从弹出窗口中选择
接下来看看效果,先保存一下这个shader
然后在shader被选中的情况下右键创建一个材质球
这样就可以快速的创建一个使用该shader的材质球了。当然你也可以随便先创建一个材质球,然后再把材质球的shader改成刚刚编辑的shader,只是我觉得这样比较快。
可以看到,由于刚才在shader里面指定了贴图,所以新建的材质球就已经有一张默认贴图了。不过其实我不太建议这样做,因为这样会导致资源依赖的问题。我这里这样做,只是为了做一个功能的展示。
把这个材质球赋予给模型,就能看到模型已经被赋予贴图的效果了。
接下来回到节点编辑界面,按着大键盘的数字5,然后鼠标点击,创建一个颜色节点,并且命名为mainColor。
这里需要注意一点,贴图类型的参数,是默认就是对外可编辑的,所以刚才我们可以直接在材质球上面看得到贴图的选项。但其他类型的节点参数,默认都是常量,如果需要把它变成可以编辑的参数,需要修改一下:
把这个参数的类型改成Property就可以。
接下来按着键盘的m键,然后点击鼠标,创建一个乘法的节点:
然后把mainTex和mainColor的RGBA输出分别拖到乘法节点的A和B输入口。
最后把乘法节点的输出口拖到FragColor。
最后保存一下shader,然后回到场景看看,会发现,现在模型已经可以控制颜色了。
这是一个最简单的例子,创建了贴图采样和颜色节点,并且用乘法节点把它们连起来,再输出到最终的颜色。
二、光照模型
在刚才那个例子的基础上,我们再加工一点东西,加上光照的效果。
在节点编辑器的空白地方鼠标右键,然后搜索world normal,创建一个世界空间法线节点,这个节点是获取了模型的法线方向,并转成了世界空间坐标
再创建一个世界空间下的灯光方向节点
按着句号键点鼠标左键,或者搜索dot,创建一个点乘节点,并把世界法线和世界灯光方向的输出连到点乘节点上
最后把点乘的结果,拖动到刚才贴图和颜色的乘法节点上。
可以看到,原来乘法节点只有A和B两个输入,但当我们拖动的时候,出现了C输入。其实所有允许多个输入的节点,都会出现这样的情况,默认只有2个,然后你可以继续的拖动其他结果进去作为输入,节点会自动增加输入接口。
这个时候保存一下节点,看看模型,会发现已经有了最基本的光照效果了。
刚才的世界法线和世界灯光方向点乘,其实就是实现了最简单的Lambert光照模型。
接下来再加工一下,新增一个乘法节点,然后把dot的结果输入到乘法节点的A参数。这里注意一个情况,B参数我并没有再新建节点输入,而是直接选择乘法节点,在属性栏里面,可以直接指定B的值。这里我输入B的值是0.5。
继续创建一个Add加法节点,然后把乘法节点输入到A参数,再指定B参数为0.5.。
最后把加法的结果输入到原来的乘法C参数
这时候保存shader,看看模型,会看到模型比刚才亮了一些,阴影没那么重。
这是因为刚才的dot后乘以0.5再加0.5,是Half-Lambert光照模型的算法。
三、边缘光
由于前面的步骤介绍得比较详细,浪费了不少篇章,所以接下来会加快一点速度。
新建一个Unlit模板的ASE的shader
新建一个颜色节点,命名为rimColor。然后新建worldNormal和worldSpaceViewDir节点。其中worldNormal刚才用过,是世界空间法线。而worldSpaceViewDir是世界空间的观察方向。
给worldSpaceViewDir连一个Normalize标准化节点,然后再和WorldNormal做dot点乘,再用OneMinus(一减)节点让dot的结果被1减,得到的结果连到FragColor输入
这时候看模型是这样的:
现在模型的边缘光已经有一点点感觉了,接下来做一个非常常用的操作,先对刚才的结果做一个次方(Power)的操作,然后再做一个乘法的操作。这个操作可以记下来,可以用于控制一些效果的范围和强度
通过调节powVal,可以调节边缘光的范围,通过调节mulVal,可以控制边缘光的强度
把颜色乘上去,就可以控制边缘光的颜色了
最后还要做一步,现在的模型并不是透明的,需要把它设置一下混合模式
选择输出节点,找到BlendMode,选择一下Alpha Blend
这时候就看到模型变透明了,效果就做完了
四、遮挡X光效果
这里再新建一个叫做XRay的ASE的Shader,使用的是上一篇文章介绍的2个pass的模板。
可以看到,这里就有2个输出节点了。
重新打开刚才上一步做的RimColor边缘光的shader编辑界面,然后框选除了输出节点以外的其他节点,按Ctrl+C复制:
然后回到XRay的shader节点编辑界面,按Ctrl+V,把节点复制到XRay里面,并且把复制过来的节点的输出连接到XRay第一个输出节点。
打开第二步做的DiffuseTex的带光照和贴图的Shader的编辑界面,然后框选除了输出节点以外的其他节点,按Ctrl+C复制:
又回到了XRay的编辑界面,按Ctrl+V把节点复制过来,并且把输出连到UnlitPass2的输出。
上面的步骤,其实就是把之前做的2个shader的效果复制过来,分别连接到这个新Shader的2个Pass的输出而已。做XRay效果其实就是这么简单,一个pass显示正常颜色,另外一个pass显示透明的边缘光而已。
不过2个pass之间怎样去决定谁应该什么时候渲染呢?这里是需要通过ZTest来控制了。当前面没东西遮挡的时候,也就是ZTest LEqual的时候,显示正常颜色,当前面有东西遮挡,也就是ZTest Greater的时候,显示边缘光。然后由于边缘光需要半透明混合,所以还需要加一个ZWrite Off。
然后问题来了,选择一下这两个输出节点,看到了Pass里面并没有ZTest和ZWrite的选项,怎么办呢?
上一篇文章已经说过了,需要什么可以编辑的参数,去模板加就行,所以打开模板,然后在两个Pass里面都加上ZTest和ZWrite。
再回到节点编辑器,就可以看到Pass里面已经有这两个选项了:
于是修改,注意,模板里面的第一个Pass的名字,在节点属性里面会显示成shader的名字,比如我这个Shader叫做XRay,所以第一个输出节点就叫XRay,但点进去属性里面,还是显示UnlitPass1的,这个不要搞错。第一个Pass是显示边缘光,所以ZWrite要off,ZTest要Greater,然后混合模式用AlphaBlend:
第二个Pass显示的是正常的漫反射颜色,所以ZWrite用默认,也就是On,ZTest选择Less or Equal。最后混合模式选Off。
现在回到模型场景,建一个Cube挡在模型前面,然后调整一下边缘光的颜色和强度,就可以看到我们的最终效果了。
五、总结
这样一个XRay透视效果,我分了挺多步骤来说明,主要是越简单的操作步骤越难说,如果大家都已经很熟悉ASE的操作了,我只是讲节点的连接思路,那会快很多。
最后附上XRay的例子的代码,可以粘贴到Shader里面,应该就能打开看节点了:
bash
// Made with Amplify Shader Editor
// Available at the Unity Asset Store - http://u3d.as/y3X
Shader "XRay"
{
Properties
{
_mainTex("mainTex", 2D) = "white" {}
_rimColor("rimColor", Color) = (0.8109627,0.7688679,1,0)
_mainColor("mainColor", Color) = (1,1,1,1)
_powVal("powVal", Float) = 2
_mulVal("mulVal", Float) = 1
[HideInInspector] _texcoord( "", 2D ) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Name "UnlitPass1"
ZTest Greater
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#ifndef UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX
//only defining to not throw compilation error over Unity 5.5
#define UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input)
#endif
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float4 color : COLOR;
float3 ase_normal : NORMAL;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
#ifdef ASE_NEEDS_FRAG_WORLD_POSITION
float3 worldPos : TEXCOORD0;
#endif
float4 ase_texcoord1 : TEXCOORD1;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
uniform float4 _rimColor;
uniform float _powVal;
uniform float _mulVal;
v2f vert ( appdata v )
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
UNITY_TRANSFER_INSTANCE_ID(v, o);
float3 ase_worldNormal = UnityObjectToWorldNormal(v.ase_normal);
o.ase_texcoord1.xyz = ase_worldNormal;
//setting value to unused interpolator channels and avoid initialization warnings
o.ase_texcoord1.w = 0;
float3 vertexValue = float3(0, 0, 0);
#if ASE_ABSOLUTE_VERTEX_POS
vertexValue = v.vertex.xyz;
#endif
vertexValue = vertexValue;
#if ASE_ABSOLUTE_VERTEX_POS
v.vertex.xyz = vertexValue;
#else
v.vertex.xyz += vertexValue;
#endif
o.vertex = UnityObjectToClipPos(v.vertex);
#ifdef ASE_NEEDS_FRAG_WORLD_POSITION
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
#endif
return o;
}
fixed4 frag (v2f i ) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
fixed4 finalColor;
#ifdef ASE_NEEDS_FRAG_WORLD_POSITION
float3 WorldPosition = i.worldPos;
#endif
float3 ase_worldNormal = i.ase_texcoord1.xyz;
float3 worldSpaceViewDir10 = WorldSpaceViewDir( float4( 0,0,0,1 ) );
float3 normalizeResult12 = normalize( worldSpaceViewDir10 );
float dotResult13 = dot( ase_worldNormal , normalizeResult12 );
finalColor = ( _rimColor * ( pow( ( 1.0 - dotResult13 ) , _powVal ) * _mulVal ) );
return finalColor;
}
ENDCG
}
Pass
{
Name "UnlitPass2"
Tags { "LightMode"="ForwardBase" }
Blend Off
ZWrite On
ZTest LEqual
CGPROGRAM
#ifndef UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX
//only defining to not throw compilation error over Unity 5.5
#define UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input)
#endif
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#define ASE_NEEDS_FRAG_WORLD_POSITION
struct appdata
{
float4 vertex : POSITION;
float4 color : COLOR;
float4 ase_texcoord : TEXCOORD0;
float3 ase_normal : NORMAL;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
#ifdef ASE_NEEDS_FRAG_WORLD_POSITION
float3 worldPos : TEXCOORD0;
#endif
float4 ase_texcoord1 : TEXCOORD1;
float4 ase_texcoord2 : TEXCOORD2;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
//This is a late directive
uniform sampler2D _mainTex;
uniform float4 _mainTex_ST;
uniform float4 _mainColor;
v2f vert(appdata v )
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
UNITY_TRANSFER_INSTANCE_ID(v, o);
float3 ase_worldNormal = UnityObjectToWorldNormal(v.ase_normal);
o.ase_texcoord2.xyz = ase_worldNormal;
o.ase_texcoord1.xy = v.ase_texcoord.xy;
//setting value to unused interpolator channels and avoid initialization warnings
o.ase_texcoord1.zw = 0;
o.ase_texcoord2.w = 0;
float3 vertexValue = float3(0, 0, 0);
#if ASE_ABSOLUTE_VERTEX_POS
vertexValue = v.vertex.xyz;
#endif
vertexValue = vertexValue;
#if ASE_ABSOLUTE_VERTEX_POS
v.vertex.xyz = vertexValue;
#else
v.vertex.xyz += vertexValue;
#endif
o.vertex = UnityObjectToClipPos(v.vertex);
#ifdef ASE_NEEDS_FRAG_WORLD_POSITION
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
#endif
return o;
}
fixed4 frag(v2f i ) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
fixed4 finalColor;
#ifdef ASE_NEEDS_FRAG_WORLD_POSITION
float3 WorldPosition = i.worldPos;
#endif
float2 uv_mainTex = i.ase_texcoord1.xy * _mainTex_ST.xy + _mainTex_ST.zw;
float3 ase_worldNormal = i.ase_texcoord2.xyz;
float3 worldSpaceLightDir = UnityWorldSpaceLightDir(WorldPosition);
float dotResult4 = dot( ase_worldNormal , worldSpaceLightDir );
finalColor = ( tex2D( _mainTex, uv_mainTex ) * _mainColor * ( ( dotResult4 * 0.5 ) + 0.5 ) );
return finalColor;
}
ENDCG
}
}
CustomEditor "ASEMaterialInspector"
Fallback "Diffuse"
}
/*ASEBEGIN
Version=18500
0;306;1661;681;1937.559;441.4886;1.45936;True;True
Node;AmplifyShaderEditor.WorldSpaceLightDirHlpNode;3;-976.1194,139.55;Inherit;False;False;1;0;FLOAT;0;False;4;FLOAT3;0;FLOAT;1;FLOAT;2;FLOAT;3
Node;AmplifyShaderEditor.WorldNormalVector;2;-986.5195,-64.54977;Inherit;False;False;1;0;FLOAT3;0,0,1;False;4;FLOAT3;0;FLOAT;1;FLOAT;2;FLOAT;3
Node;AmplifyShaderEditor.DotProductOpNode;4;-693.6849,42.34929;Inherit;False;2;0;FLOAT3;0,0,0;False;1;FLOAT3;0,0,0;False;1;FLOAT;0
Node;AmplifyShaderEditor.SimpleMultiplyOpNode;5;-534.3663,61.05127;Inherit;False;2;2;0;FLOAT;0;False;1;FLOAT;0.5;False;1;FLOAT;0
Node;AmplifyShaderEditor.ColorNode;6;-993.7325,-285.175;Inherit;False;Property;_mainColor;mainColor;2;0;Create;True;0;0;False;0;False;1,1,1,1;1,1,1,1;True;0;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
Node;AmplifyShaderEditor.SamplerNode;7;-1024.732,-503.175;Inherit;True;Property;_mainTex;mainTex;0;0;Create;True;0;0;False;0;False;-1;None;138df4511c079324cabae1f7f865c1c1;True;0;False;white;Auto;False;Object;-1;Auto;Texture2D;8;0;SAMPLER2D;;False;1;FLOAT2;0,0;False;2;FLOAT;0;False;3;FLOAT2;0,0;False;4;FLOAT2;0,0;False;5;FLOAT;1;False;6;FLOAT;0;False;7;SAMPLERSTATE;;False;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
Node;AmplifyShaderEditor.SimpleAddOpNode;8;-363.3666,73.05125;Inherit;False;2;2;0;FLOAT;0;False;1;FLOAT;0.5;False;1;FLOAT;0
Node;AmplifyShaderEditor.ColorNode;18;-579.6888,383.1555;Inherit;False;Property;_rimColor;rimColor;1;0;Create;True;0;0;False;0;False;0.8109627,0.7688679,1,0;0.8109627,0.7688679,1,1;True;0;5;COLOR;0;FLOAT;1;FLOAT;2;FLOAT;3;FLOAT;4
Node;AmplifyShaderEditor.NormalizeNode;12;-1045.079,582.2842;Inherit;False;1;0;FLOAT3;0,0,0;False;1;FLOAT3;0
Node;AmplifyShaderEditor.WorldNormalVector;11;-1264.591,398.5844;Inherit;False;False;1;0;FLOAT3;0,0,1;False;4;FLOAT3;0;FLOAT;1;FLOAT;2;FLOAT;3
Node;AmplifyShaderEditor.DotProductOpNode;13;-996.485,444.3506;Inherit;False;2;0;FLOAT3;0,0,0;False;1;FLOAT3;0,0,0;False;1;FLOAT;0
Node;AmplifyShaderEditor.RangedFloatNode;14;-783.7252,675.4164;Inherit;False;Property;_powVal;powVal;3;0;Create;True;0;0;False;0;False;2;2;0;0;0;1;FLOAT;0
Node;AmplifyShaderEditor.OneMinusNode;15;-868.7095,556.3414;Inherit;False;1;0;FLOAT;0;False;1;FLOAT;0
Node;AmplifyShaderEditor.SimpleMultiplyOpNode;19;-486.6632,600.4702;Inherit;False;2;2;0;FLOAT;0;False;1;FLOAT;0;False;1;FLOAT;0
Node;AmplifyShaderEditor.PowerNode;16;-645.6777,609.1974;Inherit;False;False;2;0;FLOAT;0;False;1;FLOAT;1;False;1;FLOAT;0
Node;AmplifyShaderEditor.WorldSpaceViewDirHlpNode;10;-1251.096,622.5241;Inherit;False;1;0;FLOAT4;0,0,0,1;False;4;FLOAT3;0;FLOAT;1;FLOAT;2;FLOAT;3
Node;AmplifyShaderEditor.SimpleMultiplyOpNode;9;-660.4999,-365.2596;Inherit;True;3;3;0;COLOR;0,0,0,0;False;1;COLOR;0,0,0,0;False;2;FLOAT;0;False;1;COLOR;0
Node;AmplifyShaderEditor.SimpleMultiplyOpNode;20;-283.6566,462.576;Inherit;False;2;2;0;COLOR;0,0,0,0;False;1;FLOAT;0;False;1;COLOR;0
Node;AmplifyShaderEditor.RangedFloatNode;17;-621.9011,755.5342;Inherit;False;Property;_mulVal;mulVal;4;0;Create;True;0;0;False;0;False;1;1;0;0;0;1;FLOAT;0
Node;AmplifyShaderEditor.TemplateMultiPassMasterNode;0;0,0;Float;False;True;-1;2;ASEMaterialInspector;100;11;XRay;cb43803649bb8db429b4b5d8ba318e3a;True;UnlitPass1;0;0;UnlitPass1;2;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;True;1;RenderType=Opaque=RenderType;False;0;True;2;5;False;-1;10;False;-1;0;1;False;-1;0;False;-1;False;False;False;False;False;False;False;False;False;False;False;False;False;False;True;2;False;-1;True;2;False;-1;False;True;1;LightMode=ForwardBase;False;0;Diffuse;0;0;Standard;1;Vertex Position,InvertActionOnDeselection;1;0;2;True;True;False;;False;0
Node;AmplifyShaderEditor.TemplateMultiPassMasterNode;1;2.219055,-151.1632;Float;False;False;-1;2;ASEMaterialInspector;100;11;New Amplify Shader;cb43803649bb8db429b4b5d8ba318e3a;True;UnlitPass2;0;1;UnlitPass2;2;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;False;True;1;RenderType=Opaque=RenderType;False;0;True;0;5;False;-1;10;False;-1;0;5;False;-1;10;False;-1;False;False;False;False;False;False;False;False;False;False;False;False;False;False;True;0;False;-1;True;3;False;-1;False;True;1;LightMode=ForwardBase;False;0;;0;0;Standard;0;False;0
WireConnection;4;0;2;0
WireConnection;4;1;3;0
WireConnection;5;0;4;0
WireConnection;8;0;5;0
WireConnection;12;0;10;0
WireConnection;13;0;11;0
WireConnection;13;1;12;0
WireConnection;15;0;13;0
WireConnection;19;0;16;0
WireConnection;19;1;17;0
WireConnection;16;0;15;0
WireConnection;16;1;14;0
WireConnection;9;0;7;0
WireConnection;9;1;6;0
WireConnection;9;2;8;0
WireConnection;20;0;18;0
WireConnection;20;1;19;0
WireConnection;0;0;20;0
WireConnection;1;0;9;0
ASEEND*/
//CHKSM=6DD70292189FAD2C0FC85A4698D11D5C972718B6