Unity中用 Vertex & Fragment Shader 实现surface shader
发表于2018-08-15
surface shader隐藏了好多的内部实现,像多光源,阴影,衰减等问题,在surface shader中都是被隐藏实现好了的,而且还是多平台适配的,Vertex & Fragment Shader则还要自己写,还好unity也给我们提供了一些现成方法可以调。
所以总结一下,在写需要接收复杂光源信息的材质时,写surface shader应该会简单不少。
如果是不接收光或很少光的材质时,Vertex & Fragment Shader应该代码更清晰易懂点,速度更快一点。
Unity的surface shader这项技术还是可以省我们不少事的,可以提高开发速度。
注意:以下2个shader,只考虑了只有一个平行光的情况,没有考虑多光源,阴影,衰减等问题。
surface shader的实现可以去看官方的shader资源资源去看,下面的代码是可以继续优化的,这里为了步骤清晰,就多分了一些变量
// 漫反射shader,对应 Legacy Shaders/Diffuse Shader "Z_TestShader/TestDiffuse" { Properties { _Color ("Color", Color) = (1,1,1,1) } SubShader { Tags{ "RenderType" = "Opaque" } Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag // 前向渲染编译指令 #pragma multi_compile_fwdbase #include "UnityCG.cginc" #include "Lighting.cginc" uniform float4 _Color; struct v2f { float4 pos : SV_POSITION; float3 normal : NORMAL; float3 lightDir : TEXCOORD0; }; v2f vert (appdata_full v) { v2f o; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); // 把模型空间的模型法线转到世界空间,以下是等效的2种方法 //o.normal = normalize(mul(v.normal, _World2Object)); o.normal = UnityObjectToWorldNormal(v.normal); // 取光线的方向,以下是等效的2种方法 //o.lightDir = normalize(_WorldSpaceLightPos0.xyz); o.lightDir = ObjSpaceLightDir(v.vertex); o.lightDir = normalize(mul(_Object2World, o.lightDir)); return o; } fixed4 frag (v2f i) : COLOR { // 环境光 float3 ambientLight = UNITY_LIGHTMODEL_AMBIENT.rgb; // 漫反射 fixed diff = max (0, dot(i.normal, i.lightDir)); float3 diffCol = _LightColor0.rgb * diff; // 最终颜色合成 float4 col; col = float4(ambientLight + diffCol, 1) * _Color; return col; } ENDCG } } }
// 贴花(就是把一张纹理贴在另一张的上面)shader,对应Legacy Shaders/Decal Shader "Z_TestShader/TestDecal" { Properties { _Color ("Main Color", Color) = (1,1,1,1) _MainTex ("Base (RGB)", 2D) = "white" {} _DecalTex ("Decal (RGBA)", 2D) = "black" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 250 Pass { Tags { "LightMode" = "ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase #include "UnityCG.cginc" #include "Lighting.cginc" uniform sampler2D _MainTex; uniform sampler2D _DecalTex; uniform fixed4 _Color; float4 _MainTex_ST; float4 _DecalTex_ST; struct v2f { float4 pos : SV_POSITION; float2 uv_MainTex : TEXCOORD0; float2 uv_DecalTex : TEXCOORD1; float3 normal : NORMAL; float3 lightDir : TEXCOORD3; }; v2f vert (appdata_full v) { v2f o; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); // 用材质的 Tiling, Offset 2个参数计算纹理uv o.uv_MainTex = TRANSFORM_TEX(v.texcoord, _MainTex); o.uv_DecalTex = TRANSFORM_TEX(v.texcoord, _DecalTex); //o.normal = normalize(mul(v.normal, _World2Object)); o.normal = UnityObjectToWorldNormal(v.normal); //o.lightDir = normalize(_WorldSpaceLightPos0.xyz); o.lightDir = ObjSpaceLightDir(v.vertex); o.lightDir = normalize(mul(_Object2World, o.lightDir)); return o; } fixed4 frag (v2f i) : COLOR { // 贴花算法 float4 tex = tex2D(_MainTex, i.uv_MainTex); float4 decal = tex2D(_DecalTex, i.uv_DecalTex); tex.rgb = lerp (tex.rgb, decal.rgb, decal.a); tex *= _Color; float3 ambientLight = UNITY_LIGHTMODEL_AMBIENT.rgb; fixed diff = max (0, dot(i.normal, i.lightDir)); float3 diffCol = _LightColor0.rgb * diff; // 最终颜色合成 float4 col; col = float4(ambientLight + diffCol, 1) * tex; return col; } ENDCG } } }
注意:还有一点,如果你要在一个场景里跟 surface shader 放在一起做效果对比的话,记得在 Window->Lighting 界面里,把环境光的 Ambient Source 选项,从SkyBox 换成 Color,因为我发现在 SkyBox 模式下(也就是环境光来自于天空盒),UNITY_LIGHTMODEL_AMBIENT 这个宏得到的环境光是不准确的,至少是跟 surface shader 获得的环境光是有差别的。可能如果想获得一样的环境光还需要经过一些处理才行吧。所以你要想让2种 shader 效果一样,记得要改一下这里。
来自:https://blog.csdn.net/WPAPA/article/details/51399652