The fundamental of Unity Shader

Created by miccall (转载请注明出处 miccall.tech)

宏定义
条件编译
预定义着色器预处理器宏
预编译
前向渲染
基本的数据初始化
InitializeFragmentNormal
Albedo
UnityLight

MacroDefinition 宏定义 :

define命令是C类语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本

// #define <宏名> <字符串>  例:
#define PI 3.1415926
// #define <宏名> (<参数表>) <宏体>  例: 
#define A(x) x

解出定义:


#undef <宏名> 

条件编译 :

当标识符已经被定义过(一般是用#define命令定义),
则对程序段1进行编译,否则编译程序段2。:


#ifdef 标识符
        程序段1
#else
        程序段2
#endif

其中#else部分也可以没有,即:

#ifdef
        程序段1
#denif

这里的“程序段”可以是语句组,也可以是命令行。

例子:


// 我们有一个数据类型,在Windows平台中,应该使用long类型表示,而在其他平台应该使用float表示
#ifdef WINDOWS
        #define MYTYPE long
#else
        #define MYTYPE float
#endif

// 调试模式下才输出一些 
#ifdef DEBUG
        print ("device_open(%p)\n", file);
#endif

还有另一种方式:

#ifndef 标识符
        程序段1
#else
        程序段2
#endif

将“ifdef”改为“ifndef”。它的作用是:若标识符未被定义则编译程序段1,
否则编译程序段2。这种形式与第一种形式的作用相反。

还有这种:

#if 表达式
        程序段1
#else
        程序段2
#endif

当指定的表达式值为真(非零)时就编译程序段1,否则编译程序段2。
可以事先给定一定条件,使程序在不同的条件下执行不同的功能。

复杂表达式用法:


#if defined (表达式1) || defined (表达式2) ||  !defined(表达式3)

else if 判断 :

#if 条件1
   代码段1
#elif 条件2
    代码段2
    ...
#elif 条件n
    代码段n

#else
    代码段 n+1
#endif

shader 预定义着色器预处理器宏

  1. 目标平台

SHADER_API_D3D11    // Direct3D 11
SHADER_API_GLCORE    // 桌面OpenGL“核心”(GL 3/4)
SHADER_API_GLES        // OpenGL ES 2.0
SHADER_API_GLES3    // OpenGL ES 3.0 / 3.1
SHADER_API_METAL    // iOS / Mac Metal
SHADER_API_VULKAN    // 福尔康
SHADER_API_D3D11_9X    // 通用Windows平台 Direct3D 11“功能级别9.x”目标
SHADER_API_PS4        // PlayStation 4. SHADER_API_PSSL也是定义的。
SHADER_API_XBOXONE    // Xbox One
SHADER_API_PSP2        // PlayStation Vita

  1. 目标模型

#if SHADER_TARGET < 30
    // less than Shader model 3.0:
    // very limited Shader capabilities, do some approximation
#else
    // decent capabilities, do a better thing
#endif

  1. Unity版本

UNITY_VERSION

包含Unity版本的数值。例如,UNITY_VERSION适用501于Unity 5.0.1。
如果您需要编写使用不同内置着色器功能的着色器,则可以将其用于版本比较。
例如,


#if UNITY_VERSION >= 500

预处理程序检查仅传递版本5.0.0或更高版本。

  1. 正在编译着色器阶段

预处理宏


SHADER_STAGE_VERTEX,
SHADER_STAGE_FRAGMENT,
SHADER_STAGE_DOMAIN,
SHADER_STAGE_HULL,
SHADER_STAGE_GEOMETRY,
SHADER_STAGE_COMPUTE

正在编制每个着色阶段时定义。通常,在像素
之间共享着色器代码时,它们很有用着色器和计算着色器,以处理某些事情必须略有不同的情况。

  1. 阴影贴图宏
  2. 常量缓冲区宏
  3. 纹理/采样器声明宏

预编译:

#pragma vertex name    
// 编译name函数为顶点着色器

#pragma fragment name    
// 编译name函数为片元着色器

#pragma geometry name    
// 编译name函数为DX10的几何着色器
// 注:会自动开启#pragma target 4.0

#pragma hull name    
// 编译name函数为DX10的壳着色器
// 注:会自动开启#pragma target 5.0

#pragma domain name    
// 编译name函数为DX10的域着色器
// 注:会自动开启#pragma target 5.0

#pragma target name    
// 表明编译目标

#pragma only_renderers space_separated_names    
// 只为指定的渲染平台渲染着色器
/*    
包括下列值:
d3d9:Direct3D 9
d3d11:Direct3D 11/12
glcore:OpenGL 3.x/4.x
gles:OpenGL ES 2.0
gles:OpenGL ES 3.x
metal:IOS&Mac Metal
d3d11_9x:Direct3D 11 9.x特性等级一般用于WSA平台
xbox360:Xbox 360
xboxone:Xbox One
ps4:PlayStation 4
psp2:PlayStation Vita
n3ds:Nintendo 3DS
wiiu:Nintendo Wii U
*/ 


#pragma exclude_renderers space_separated_names    
// 排除指定的渲染平台 
/*    
参数同上
*/ 


#pragma multi_compile ...    
// 为shader创建多个稍微有点区别的shader变体。这个Shader被称为宏着色器(mega shader)或者超着色器(uber shader)。实现原理:根据不同的情况,使用不同的预处理器指令,来多次编译Shader代码
// https://blog.csdn.net/ecidevilin/article/details/52882400


#pragma enable_d3d11_debug_symbols    
// 生成d3d11的调试信息
// 可以在VS2012(或以上)使用图形调试器调试 shader


#pragma hardware_tier_variants renderer_name    
// 针对所选渲染器的每个硬件层级
// 生成每个已编译的Shader的多重Shader硬件变体
// Unity官方文档:多重着色器变体[https://docs.unity3d.com/Manual/SL-MultipleProgramVariants.html]

前向渲染

前向渲染再unity中有两个pass 分别计算不同的灯光,分别是
Tags {“LightMode” = “ForwardBase”}

Tags {“LightMode” = “ForwardAdd”}

基本的数据初始化

vert 中

o.pos = UnityObjectToClipPos(v.vertex);

o.worldPos = mul(unity_ObjectToWorld, v.vertex);

o.normal = UnityObjectToWorldNormal(v.normal);

o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);

frag中

float3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);

InitializeFragmentNormal

法线是在切线空间的,我们直接获取可以得到

float3 normal : NORMAL;
float4 tangent : TANGENT;

在vert中:

o.normal = UnityObjectToWorldNormal(v.normal);
o.tangent = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);

其次,就是binormal的计算,它可以在vert计算,也可以在fragment计算

float3 CreateBinormal (float3 normal, float3 tangent, float binormalSign) 
{
    return cross(normal, tangent.xyz) * (binormalSign * unity_WorldTransformParams.w);
}

float3 binormal = CreateBinormal(i.normal, i.tangent.xyz, i.tangent.w);

这样我们三个坐标轴都有了,最后,构造切线空间矩阵,直接把法线转换到世界空间:

normal = normalize(
    tangentSpaceNormal.x * tangent +
    tangentSpaceNormal.y * binormal +
    tangentSpaceNormal.z * normal
);

如果要使用detail法线贴图 ,就要使用到blendnormal 混合

float3 mainNormal = UnpackScaleNormal(tex2D(_NormalMap, i.uv.xy), _BumpScale);
float3 detailNormal = UnpackScaleNormal(tex2D(_DetailNormalMap, i.uv.zw), _DetailBumpScale);
float3 tangentSpaceNormal = BlendNormals(mainNormal, detailNormal); 

Albedo

他的计算使用了DiffuseAndSpecularFromMetallic 函数

inline half3 DiffuseAndSpecularFromMetallic (half3 albedo, half metallic, out half3 specColor, out half oneMinusReflectivity)
{
    specColor = lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, metallic);
    oneMinusReflectivity = OneMinusReflectivityFromMetallic(metallic);
    return albedo * oneMinusReflectivity;
}

unity_ColorSpaceDielectricSpec 是一个sRGB 值 :56,56,56 (线性空间 0.04 ) alpha 为 0.96 (one minus sRGB 55; 1.0 - 0.04 = 0.96)

specColor 是根据 metallic 增大而增大的,[0.04 , albedo]

由于金属没有 albedo , 所以中间要存储一个 oneminus

最后这个 ref 是 [ 0.96 : 0 ] 的 。乘以albedo 才是最后的值。

half oneMinusReflectivity;
half3 specColor;
Albedo = DiffuseAndSpecularFromMetallic (Albedo, Metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);

UnityLight

unity的light 分为 直接光 UnityLight 和 间接光 UnityIndirect ,这些光都存储在 UnityGI 里面
UnityLight 要计算 color; dir; ndotl;

#if defined( POINT) || defined( SPOT) || defined( POINT_COOKIE ) 
    light.dir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos);
#else
    light.dir = _WorldSpaceLightPos0.xyz;
#endif

UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos);
light.color = _LightColor0.rgb * attenuation; ;
light.ndotl = DotClamped(i.normal, light.dir);

UnityIndirect 要计算 diffuse; specular;

UnityIndirect indirectLight;
    indirectLight.diffuse = 0;
    indirectLight.specular = 0;

#if defined(VERTEXLIGHT_ON)
    indirectLight.diffuse = i.vertexLightColor;
#endif

#if defined(FORWARD_BASE_PASS)
    indirectLight.diffuse += max(0, ShadeSH9(float4(i.normal, 1)));
#endif

return indirectLight;