什么是Surface Shader

       Surface shader是unity引擎特有的一种shader,可以直接表示物体表面的光照模型。在Unlit shader中,必须自己编写顶点和片段着色器,而且对于多个光源的情况,还是编写多个renderpass,使用ForwardAddForwardBase来区分。而在Surface Shader中,顶点着色器是可选的,而片元着色器则被一个功能相近的表面着色器取代,同时,不需要在手工处理多个光源的情况。光照模型可以使用Unity内置的光照模型,当然也可以编写自己的。

默认的表面着色器

       Create ➤ Shader ➤ Standard Surface Shader可以创建一个表面着色器,里面的代码如下。

Shader "Custom/SurfaceShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
	
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows
        
        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
        
        sampler2D _MainTex;
        
        struct Input {
            float2 uv_MainTex;
        };
		
        half _Glossiness;
        half _Metallic;
        fixed4 _Color;
        
        // Add instancing support for this shader. You need to check 'Enable Instancing' on
        materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about
        instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_CBUFFER_START(Props)
        // put more per-instance properties here
        UNITY_INSTANCING_CBUFFER_END
        
        void surf (Input IN, inout SurfaceOutputStandard o) {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG 
    }
    FallBack "Diffuse"
}

       接下来解释该程序。

Pragmas

       表示表面着色器的Pragmas语法如下

#pragma surface <surfaceFunc> <lightModel> [vertex:<vertFunc>] [<shadowType>]

       宏中必须指定表面着色函数,还有使用的光照模型,指定顶点着色函数,还有阴影类型都是可选的,比如如下都是正确的。

#pragma surface surf Standard fullforwardshadows
#pragma surface surf Lambert vertex:vert

Standard,Lambert,Blinn-phong都是Unity内置的光照模型

SurfaceOutputStandard

       SurfaceOutputStandard对象在UnityPBSLighting.cginc文件中被定义,Unity的表面着色器会自动include一系列头文件,不需要再次手工引入。

struct SurfaceOutputStandard
{
	fixed3 Albedo;     // base (diffuse or specular) color
	fixed3 Normal;     // tangent space normal, if written
	half3 Emission;
	half Metallic;     // 0=non-metal, 1=metal
	half Smoothness;   // 0=rough, 1=smooth
	half Occlusion;    // occlusion (default 1)
	fixed Alpha;       // alpha for transparencies
};

       如果熟悉PBR,那对这个表面对象的各个参数应该比较了解了吧。

Surface Function

       shader的主角来啦。

void surf (Input IN, inout SurfaceOutputStandard o) {
    fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    o.Albedo = c.rgb;
    // Metallic and smoothness come from slider variables
    o.Metallic = _Metallic;
    o.Smoothness = _Glossiness;
    o.Alpha = c.a;
}

       在表面着色函数中,并没有使用SurfaceOutputStandard对象的全部参数,只对其中常用的四个参数进行了赋值。

Lighting Model

       如果想理解内置shader,就一定少不了光照模型,不过Unity Standard lighting function非常复杂,我使用更简单更好理解的Lambert光照模型进行讲解,该函数可以在Lighting.cginc找到。

inline fixed4 LightingLambert (SurfaceOutput s, UnityGI gi)
{
    fixed4 c;
    UnityLight light = gi.light;
    fixed diff = max (0, dot (s.Normal, light.dir));
    c.rgb = s.Albedo * light.color * diff;
    c.a = s.Alpha;
    #ifdef UNITY_LIGHT_FUNCTION_APPLY_INDIRECT
        c.rgb += s.Albedo * gi.indirect.diffuse;
    #endif
    return c; 
}

        UnityGI是一个unity内置的数据结构,用来计算照射到物体表面的光线(比如直接光和各种间接光),使用全局光照系统(global illumination system)。GI相比直接使用一个简单的环境光常量(Ambient)能更好的表现真实的物理环境。

使用另一个光照模型

       修改这个shader,使用BlinnPhong光照模型。

Shader "Custom/SurfaceShaderBlinnPhong" {
    Properties {
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
		_SpecColor ("Specular Material Color", Color) = (1,1,1,1) 
		_Shininess ("Shininess", Range (0.03, 1)) = 0.078125
	}
	SubShader {
	    Tags { "RenderType"="Opaque" }
	    LOD 200
	    CGPROGRAM
	    #pragma surface surf BlinnPhong fullforwardshadows 
	    #pragma target 3.0
	    
	    sampler2D _MainTex;
	    float _Shininess;
	    
	    struct Input {
            float2 uv_MainTex;
        };
	    
	    fixed4 _Color;
	    UNITY_INSTANCING_CBUFFER_START(Props)
	        // put more per-instance properties here
	    UNITY_INSTANCING_CBUFFER_END
	    
	    void surf (Input IN, inout SurfaceOutput o) {
	        fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Specular = _Shininess;
            o.Gloss = c.a;
            o.Alpha = 1.0f;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

       这个代码有人可能会问,_SpecColor用在哪里了,其实用在BlinnPhong的光照模型中了。

inline fixed4 UnityPhongLight (SurfaceOutput s, half3 viewDir, UnityLight light)
{
    half3 h = normalize (light.dir + viewDir);
    
    fixed diff = max (0, dot (s.Normal, light.dir));
    
    float nh = max (0, dot (s.Normal, h));
    float spec = pow (nh, s.Specular*128.0) * s.Gloss;
    
    fixed4 c;
    c.rgb = s.Albedo * light.color * diff + light.color * _SpecColor.rgb * spec;
    c.a = s.Alpha;
    return c;
}

编写自己的Lighting Model

函数声明

       编写自己的光照模型函数,有两种形式,一种纯漫反射模型(和视角方向无关),另一种和视角有关。

half4 Lighting<Name> (SurfaceOutput s, UnityGI gi);
half4 Lighting<Name> (SurfaceOutput s, half3 viewDir, UnityGI gi);

       当然这是在前向渲染的情况下,在使用延迟渲染的情况下,还有额外的两个函数:

half4 Lighting<Name>_Deferred (SurfaceOutput s, UnityGI gi, out half4 outDiffuseOcclusion, out half4 outSpecSmoothness, out half4 outNormal);
half4 Lighting<Name>_PrePass (SurfaceOutput s, half4 light);

不过这次就不用了

编写Surface Function

       表面着色器就很简单了

void surf (Input IN, inout SurfaceOutput o) {
    fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    o.Albedo = c.rgb;
    o.Alpha = 1.0f;
}

编写自定义的光照函数

       Unity没有内建的Phong模型,只有BlinnPhong模型,所以我就实现一下Phong光照了。

       首先实现光照模型函数:

inline fixed4 LightingPhong (SurfaceOutput s, half3 viewDir, UnityGI gi)
{
    UnityLight light = gi.light;
    
    float nl = max(0.0f, dot(s.Normal, light.dir));
    float3 diffuseTerm = nl * s.Albedo.rgb * light.color;
    float3 reflectionDirection = reflect(-light.dir, s.Normal);
    float3 specularDot = max(0.0, dot(viewDir, reflectionDirection)); //no more ambient
    float3 specular = pow(specularDot, _Shininess);
    float3 specularTerm = specular * _SpecColor.rgb * light.color.rgb;
    
    float3 finalColor = diffuseTerm.rgb + specularTerm;
    
    fixed4 c;
    c.rgb = finalColor;
    c.a = s.Alpha;
    
    #ifdef UNITY_LIGHT_FUNCTION_APPLY_INDIRECT
        c.rgb += s.Albedo * gi.indirect.diffuse;
    #endif
    
    return c; 
}

       但是除此之外,还需要提供一个额外的GI函数,用来计算GI。和BlinnPhong模型相同即可:

inline void LightingPhong_GI (SurfaceOutput s, UnityGIInput data, inout UnityGI gi)
{
    gi = UnityGlobalIllumination (data, 1.0, s.Normal);
}

       然后修改pragma来使用Phong模型

#pragma surface surf Phong fullforwardshadows

完整代码

       完整代码如下:

Shader "Custom/SurfaceShaderCustomPhong" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _SpecColor ("Specular Material Color", Color) = (1,1,1,1) 
        _Shininess ("Shininess", Range (0.03, 128)) = 0.078125
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        CGPROGRAM
        #pragma surface surf Phong fullforwardshadows 
        #pragma target 3.0
        
        sampler2D _MainTex;
        float _Shininess;
        fixed4 _Color;
        
        struct Input {
            float2 uv_MainTex;
        };

        UNITY_INSTANCING_CBUFFER_START(Props)
        UNITY_INSTANCING_CBUFFER_END

        inline void LightingPhong_GI (SurfaceOutput s, UnityGIInput data, inout UnityGI gi)
        {
            gi = UnityGlobalIllumination (data, 1.0, s.Normal);
        }

        inline fixed4 LightingPhong (SurfaceOutput s, half3 viewDir, UnityGI gi)
        {
            UnityLight light = gi.light;

            float nl = max(0.0f, dot(s.Normal, light.dir));
            float3 diffuseTerm = nl * s.Albedo.rgb * light.color;

            float3 reflectionDirection = reflect(-light.dir, s.Normal);
            float3 specularDot = max(0.0, dot(viewDir, reflectionDirection)); 
            float3 specular = pow(specularDot, _Shininess);
            float3 specularTerm = specular * _SpecColor.rgb * light.color.rgb;

            float3 finalColor = diffuseTerm.rgb + specularTerm;

            fixed4 c;
            c.rgb = finalColor;
            c.a = s.Alpha;

            #ifdef UNITY_LIGHT_FUNCTION_APPLY_INDIRECT
                c.rgb += s.Albedo * gi.indirect.diffuse;
            #endif
            return c;
        }
        
        void surf (Input IN, inout SurfaceOutput o) {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            o.Alpha = 1.0f;
        }
        ENDCG
    }
    FallBack "Diffuse"
}