Unity Shader学习笔记(29)表面着色器(Surface Shader)

发表于2018-01-02
评论1 4.9k浏览
Unity Shader分为表面着色器(Surface Shader)、顶点着色器(Vertex Shader)和片段着色器(Fragment Shader),不过下面这篇文章的重点是给大家介绍Surface Shader,一起来看看吧。

结构

对顶点着色器和片元着色器的进一层封装。主要部分为两个结构体(Input、SurfaceOutput)和编译指令(#pragma surface)。

官网样例:

Shader "Example/Diffuse Bump" {
    Properties {
          _MainTex ("Texture", 2D) = "white" {}
          _BumpMap ("Bumpmap", 2D) = "bump" {}
    }
    SubShader {
        Tags { "RenderType" = "Opaque" }
        CGPROGRAM
        #pragma surface surf Lambert
        struct Input {
            float2 uv_MainTex;
            float2 uv_BumpMap;
        };
        sampler2D _MainTex;
        sampler2D _BumpMap;
        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
            o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
        }
      ENDCG
    } 
    Fallback "Diffuse"
}

编译指令

指明表面着色器使用的表面函数和光照函数,并设置可选参数。格式如下: #pragma surface surfaceFunction lightModel [optionalparams] 

  • 表面函数(surfaceFunction) 
    包含属性有反射率、光滑度、透明度等。使用输入结构体Input来设置各种表面属性。
  • 光照函数
  • 使用表面函数的属性来模拟光照效果。Unity内置基于物理的光照模型Standard和StandardSpecular(在UnityPBSLighting.cginc中定义),非物理光照模型函数Lambert和BlinnPhong(在Lighting.cginc中定义)。

其他可选参数:

  • 自定义修改函数: 
    • vertex:VertexFunction:顶点修改,实现一些顶点动画等。
    • finalcolor:ColorFunction:最终颜色修改,实现雾效等。
    • finalgbuffer:ColorFunction:延迟渲染修改,实现边缘检测等。
    • finalprepass:ColorFunction:prepass base路径修改?
  • 阴影: 
    • addshadow:添加一个阴影投射Pass。一般FallBack通用的光照模式中可以找到,对于顶点动画、透明度等需要特殊处理。
    • fullforwardshadows:在前向渲染路径中支持所有光源类型的阴影。默认是只有平行光,添加这个参数就可以让点光或聚光灯有阴影渲染。
    • noshadow:取消所有阴影。
  • 透明度混合和测试: 
    • alpha、alpha:auto:透明测试。
    • alpha:blend:透明混合。
    • alphatest:VariableName:VariableName用来剔除不满足的片元。
  • 光照: 
    • noambient:不应用任何环境光照或光照探针(light probe)。
    • novertexlights:不应用逐顶点光照。
    • noforwardadd:去掉所有前向渲染的额外Pass,即支持逐像素平行光,其他光源用逐顶点或SH计算。
    • 控制代码生成:
    • exclude_path:deferred, exclude_path:forward, exclude_path:prepass :不需要为特定渲染路径生成代码。

两个结构体

Input结构体(数据来源)

包含主纹理和法线纹理的采样坐标uv_MainTexuv_BumpMap。采样纹理必须以“uv”作为前缀(或用”uv2”前缀表明为次纹理坐标集合)。

Unity会背后准备好数据,可以直接使用这些数据。 

SurfaceOutput(表面属性)

存储表面属性的结构体,作为光照函数输入来计算光照。

// Lighting.cginc中定义
struct SurfaceOutput
{
    fixed3 Albedo;  // diffuse color
    fixed3 Normal;  // tangent space normal, if written
    fixed3 Emission;
    half Specular;  // specular power in 0..1 range,高光反射指数部分。
    fixed Gloss;    // specular intensity,高光反射强度系数。
    fixed Alpha;    // alpha for transparencies
};
// UnityPBSLighing.cginc中定义
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
};
struct SurfaceOutputStandardSpecular
{
    fixed3 Albedo;      // diffuse color
    fixed3 Specular;    // specular color
    fixed3 Normal;      // tangent space normal, if written
    half3 Emission;
    half Smoothness;    // 0=rough, 1=smooth
    half Occlusion;     // occlusion (default 1)
    fixed Alpha;        // alpha for transparencies
};

实例

Shader "Custom/Chapter 17/Normal Extrusion" {
    Properties {
        _ColorTint ("Color Tint", Color) = (1,1,1,1)
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _BumpMap ("Normalmap", 2D) = "bump" {}
        _Amount ("Extrusion Amount", Range(-0.5, 0.5)) = 0.1
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 300
        CGPROGRAM
        // surf - 表面函数
        // CustomLambert - 光照模式(兰伯特漫反射)
        // vertex:myvert - 自定义顶点修改函数
        // finalcolor:mycolor - 自定义最后颜色修改函数
        // addshadow - 生成阴影捕获的Pass.因为修改了顶点位置,需要重新投射
        // exclude_path:deferred/exclude_path:prepass - 不要为延迟渲染路径生成相应的Pss
        // nometa - 不要生成元数据的Pass (that’s used by lightmapping & dynamic global illumination to extract surface information).
        #pragma surface surf CustomLambert vertex:myvert finalcolor:mycolor addshadow exclude_path:deferred exclude_path:prepass nometa
        #pragma target 3.0
        fixed4 _ColorTint;
        sampler2D _MainTex;
        sampler2D _BumpMap;
        half _Amount;
        struct Input {
            float2 uv_MainTex;
            float2 uv_BumpMap;
        };
        // 自定义顶点修改函数:把顶点延法线方向扩展。
        void myvert (inout appdata_full v) {
            v.vertex.xyz += v.normal * _Amount;
        }
        // 表面函数
        void surf (Input IN, inout SurfaceOutput o) {
            fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
            o.Albedo = tex.rgb;
            o.Alpha = tex.a;
            o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
        }
        // 自定义兰伯特光照
        half4 LightingCustomLambert (SurfaceOutput s, half3 lightDir, half atten) {
            half NdotL = dot(s.Normal, lightDir);
            half4 c;
            c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten);
            c.a = s.Alpha;
            return c;
        }
        // 自定义最后颜色修改函数
        void mycolor (Input IN, SurfaceOutput o, inout fixed4 color) {
            color *= _ColorTint;
        }
        ENDCG
    }
    FallBack "Legacy Shaders/Diffuse"
}

表面着色器的渲染流水线

例如生成LightMode为ForwardBase的Pass流程: 
1. 将表面着色器中CGPROGRAMENDCG之间的代码复制过来。 
2. Unity根据上述代码生成结构体v2f_surf(顶点着色器的输出)。如果Input定义了一些变量但没有使用,生成的结构体也不会包含该变量。还会包含阴影纹理坐标、光照纹理坐标、逐顶点光照等。 
3. 生成顶点着色器。 
  3.1. 如果定义了顶点修改函数,会先调用,或填充自定义Input结构体中的变量。Unity会分析该函数修改的数据,通过Input结构体把修改结果存储到v2f_surf相应变量。 
  3.2. 计算v2f_surf中其他变量:顶点位置、纹理坐标、法线方向、逐顶点光照、光照纹理等。 
  3.3. 把v2f_surf传递给片元着色器。 
4. 生成片元着色器。 
  4.1. 将v2f_surf变量(纹理坐标、视角方向)填充到Input结构体。 
  4.2. 调用自定义表面函数,填充SurfaceOutput结构体。 
  4.3. 调用光照函数得到初始的颜色值。如果使用内置的Lambert或BlinnPhong光照函数,Unity还会计算动态全局光照,并添加到光照模型的计算。 
  4.4. 进行其他颜色叠加。例如没有光照烘培,会添加逐顶点光照的影响。 
  4.5. 调用最后的颜色修改函数。

Unity Shader学习笔记系列教程:

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