Unity中用 Vertex & Fragment Shader 实现surface shader

发表于2018-08-15
评论0 3.4k浏览
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

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引