SurfaceShader处理方式

作为Unity支持的ShaderLab 之一,也是Standard Shader 采用的形式。 SurfaceShader 实际上是UnlitShader的逐片元处理

此系列需要一点ShaderLab知识,主要讨论为什么。接下来会由浅入深从生成的源代码分析。

一、基本代码结构

Shader "Custom/SampleSurfaceShader"
{
    SubShader
    {
    Tags { "RenderType" = "Opaque" }
    CGPROGRAM

    //1. surface 处理函数 光照处理函数
    #pragma surface surf Lambert
    
    //2. 输入结构
    struct Input {
      float4 color : COLOR;
    };

    //3. 处理函数
    void surf(Input IN, inout SurfaceOutput o) {
      o.Albedo = 1;
    }
        ENDCG
    }

    // 不支持的情况被使用
    FallBack FallBackShaderName
}

以上结构是比较简单的结构。接下来逐条解释:

    1. surf 处理函数 surf 对应 void surf(Input IN, inout SurfaceOutput o)
    1. Lambert 光照处理函数 Lambert 对应 inline fixed4 LightingLambert (SurfaceOutput s, UnityGI gi), 此函数在 Lighting.cginc文件中可找到。
    1. 输入数据结构 根据需要添加,主要是经过surf处理函数给 输出结构SurfaceOutput 赋值。
    1. 输出数据结构 根据需要添加,主要是经过光照处理函数Lambert函数 确定fragment 的输出。

** 这里面几个点(处理函数surf、 光照函数、 输入结构、输出结构)需要细说,在下面展开讨论.**

二、从代码深究基本结构

首先框架是Unity定的,所以就从UnityShader 流程入手。 Unity内置辅助函数文件: Unity安装目录 Unity的Shader编译工具: Unity安装目录.exe Unity 两个内置光照模型: * Lambert: 用于漫射光照 * BlinnPhong : 用于镜面反射光照。

先看规则:

    1. 通过 #pragma surface a b指定 处理函数a 和 光照函数b
    1. 处理函数的形式结构 void surf (输入结构类型 IN, inout 输出结构类型 o) { /*一些代码*/ }
    1. 光照函数,光照函数是以 half4 Lighting开头的常规函数, 比如 LightingSample, 使用时只需要写 #pragma surface surf Sample
      1. half4 Lighting (SurfaceOutput s, UnityGI gi); 在不依赖于视图方向的光照模型的前向渲染路径中使用此函数。
      1. half4 Lighting (SurfaceOutput s, half3 viewDir, UnityGI gi); 在依赖于视图方向的光照模型的前向渲染路径中使用此函数。
      1. half4 Lighting_Deferred (SurfaceOutput s, UnityGI gi, out half4 outDiffuseOcclusion, out half4 outSpecSmoothness, out half4 outNormal); 在延迟光照路径中使用此函数。
      1. half4 Lighting_PrePass (SurfaceOutput s, half4 light); 在光照预通道(旧版延迟)光照路径中使用此函数。

    请注意无需声明所有函数。光照模型不一定会使用视图方向。同样,如果仅在前向渲染中使用光照模型,请勿声明 _Deferred 和 _Prepass 函数。这确保了使用视图方向的着色器仅编译到前向渲染。

    1. 输入结构需要对应处理函数surf 输入参数
    1. 输出结构需要对应处理函数surf 传出参数,并且需要对应光照函数的输入参数

另外提一点,查看ShaderLab编译文件的方法: ShaderLab最终会 编译为平台的 vertfragment Shader 平台生成代码: 引擎中选中ShaderLab--> Inspector 面板的Compile and show code 的下拉菜单选择编译平台的选项--> 点击Compile and show code可查看生成代码。 ShaderLab生成: 引擎中选中ShaderLab--> 点击 Inspector 面板的Show genterated code 可查看ShaderLab生成代码

生成代码分析

SurfaceShader

Shader "Custom/SampleSurfaceShader"
{
    SubShader
    {
    Tags { "RenderType" = "Opaque" }
    CGPROGRAM
     #pragma surface surf Lambert
    struct Input {
      float4 color : COLOR;
    };
    void surf(Input IN, inout SurfaceOutput o) {
      o.Albedo = 1;
    }
        ENDCG
    }
    FallBack "Diffuse"
}
  • 生成代码对不同Rendering mode 生成一个 pass。 生成代码结构

  • Pass 编译为 Unlit形式, 再根据 INSTANCING_ON 和非 INSTANCING_ON 再生成代码 对应的 vertfragment 代码 Pass结构

  • 由于代码比较长,只分析 非 INSTANCING_ON Pass子结构

  • 输入输出结构根据 LIGHTMAP_ON 和 非 LIGHTMAP_ON 选项再生成代码。 另外实现了 此模式下对应的 vert_surffrag_surf 对应Pass的#pragma vertex vert_surf#pragma fragment frag_surf Pass funcs

此时代码还是挺多。

vert_surf

// vertex shader
v2f_surf vert_surf (appdata_full v) {
  
  // ...
  v2f_surf o;
  // ...
  return o;
}

#ifndef LIGHTMAP_ON
    // half-precision fragment shader registers:
    #ifdef UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS
    #define FOG_COMBINED_WITH_WORLD_POS
    struct v2f_surf {
      UNITY_POSITION(pos);
      float3 worldNormal : TEXCOORD0;
      float4 worldPos : TEXCOORD1;
      #if UNITY_SHOULD_SAMPLE_SH
      half3 sh : TEXCOORD2; // SH
      #endif
      UNITY_LIGHTING_COORDS(3,4)
      #if SHADER_TARGET >= 30
      float4 lmap : TEXCOORD5;
      #endif
      UNITY_VERTEX_INPUT_INSTANCE_ID
      UNITY_VERTEX_OUTPUT_STEREO
    };
    #endif
    // high-precision fragment shader registers:
    #ifndef UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS
    // ...
    #endif
  #endif

frag_surf

// fragment shader
fixed4 frag_surf (v2f_surf IN) : SV_Target {
  // ...
  // prepare and unpack data

  // 1. 这个Input 就是我们代码声明的Input结构类型
  Input surfIN;
  #ifdef FOG_COMBINED_WITH_TSPACE
  UNITY_EXTRACT_FOG_FROM_TSPACE(IN);
  #elif defined FOG_COMBINED_WITH_WORLD_POS
  UNITY_EXTRACT_FOG_FROM_WORLD_POS(IN);
  #else
  UNITY_EXTRACT_FOG(IN);
  #endif

  // 2. 给surfIN默认值初始化。 surfIN = (Input)0;
  UNITY_INITIALIZE_OUTPUT(Input,surfIN);

  // ...
  #ifdef UNITY_COMPILER_HLSL
  SurfaceOutput o = (SurfaceOutput)0;
  #else
  SurfaceOutput o;
  #endif
  // ...

  // 3. 
  o.Normal = IN.worldNormal;
  normalWorldVertex = IN.worldNormal;

  // 4. 调用我们SurfaceShader的 surf函数。
  // call surface function
  surf (surfIN, o);

  // UNITY_LIGHT_ATTENUATION 拥有不同类型的定义
  //#ifdef DIRECTIONAL
  //#   define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) fixed destName = UNITY_SHADOW_ATTENUATION(input, worldPos);
  //#endif
  // 。。。
  // compute lighting & shadowing factor
  UNITY_LIGHT_ATTENUATION(atten, IN, worldPos)
  fixed4 c = 0;

  // Setup lighting environment
  UnityGI gi;
  UNITY_INITIALIZE_OUTPUT(UnityGI, gi);
  
  //...
  
  // Call GI (lightmaps/SH/reflections) lighting function
  UnityGIInput giInput;
  UNITY_INITIALIZE_OUTPUT(UnityGIInput, giInput);
  
  //...

  // 5. lightmap处理
  LightingLambert_GI(o, giInput, gi);

  // 6. 我们ShaderLab使用的光照函数
  // realtime lighting: call lighting function
  c += LightingLambert (o, gi);
  // ...
  return c;
}

三、小节:

  • SurfaceShader 会编译为 UnlitShader形式 vertfragment
  • 编译后的vert的输入结构为 appdata_full类型
  • SurfaceShader 的处理函数surf 和光照函数都在fragment中进行。
  • 光照函数传入参数是 SurfaceShader的 out传出结构, 所以类型需要一致。

四、自定义光照函数

函数需要以 half4 Lighting 开头,并且传出结构fixed3 Albedo;fixed3 Normal;fixed3 Emission;fixed Alpha; 是必须的成员。

  • half4 Lighting (传出结构类型 s, half3 lightDir, half atten)

  • half4 Lighting (传出结构类型 s, half3 lightDir, half3 viewDir, half atten)

  • inline half4 Lighting(传出结构类型 s, half3 viewDir, UnityGI gi)

  • inline half4 Lighting(传出结构类型 s, half3 viewDir, UnityGI gi)


文章作者: Yonggang Long
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yonggang Long !
 上一篇
2022-08-10 Yonggang Long
下一篇 
2022-08-10 Yonggang Long
  目录