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 预定义着色器预处理器宏
- 目标平台
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
- 目标模型
#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
- Unity版本
UNITY_VERSION
包含Unity版本的数值。例如,UNITY_VERSION适用501于Unity 5.0.1。
如果您需要编写使用不同内置着色器功能的着色器,则可以将其用于版本比较。
例如,
#if UNITY_VERSION >= 500
预处理程序检查仅传递版本5.0.0或更高版本。
- 正在编译着色器阶段
预处理宏
SHADER_STAGE_VERTEX,
SHADER_STAGE_FRAGMENT,
SHADER_STAGE_DOMAIN,
SHADER_STAGE_HULL,
SHADER_STAGE_GEOMETRY,
SHADER_STAGE_COMPUTE
正在编制每个着色阶段时定义。通常,在像素
之间共享着色器代码时,它们很有用着色器和计算着色器,以处理某些事情必须略有不同的情况。
- 阴影贴图宏
- 常量缓冲区宏
- 纹理/采样器声明宏
预编译:
#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;