时间: 2020-7到12月
修改:20201213
声明:此文为个人学习笔记,自己温习,也乐在交流。如果文章中有侵权,错误,或者有不同的理解,烦请大家多多留言:指正,指教,鄙人会及时纠正。此致崇高敬意!
PBR系列笔记共三篇,本篇为系列第二篇:基于 Unity Builtin BRDF 算法分析
从时间角度来说Builtin管线的东西基本已过时了,但也可以浅入一下。URP是更佳选项,更好的美术效果,更好的性能,更好的架构,更清晰的思路,整体更直观。
目录
- vertex pragram 分析
- fragment pragram 分析
U3D 2019.4 Builtin 管线下的标准 Standard.shader 分析
一、顶点着色器:vertBase 调用 vertForwardBase
vertForwardBase 输入结构体VertexInput
顶点数据。法线和切线信息用的是 half数据。法线是 half3,切线是 half4。其他信息一率使用最佳 float高精度数据传递。
struct VertexInput //1.00
{
float4 vertex : POSITION;
half3 normal : NORMAL;
float2 uv0 : TEXCOORD0;
float2 uv1 : TEXCOORD1;
#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
float2 uv2 : TEXCOORD2;
#endif
#ifdef _TANGENT_TO_WORLD
half4 tangent : TANGENT;
#endif
UNITY_VERTEX_INPUT_INSTANCE_ID
};
vertForwardBase 输出结构体VertexOutputForwardBase
顶点光照部分(ambientOrLightmapUV)用的是 half 存储,其他信息一率使用最佳 float高精度数据传递。
struct VertexOutputForwardBase //1.01 v2f
{
UNITY_POSITION(pos);
//在D3D上,从片段着色器读取屏幕空间坐标需要SM3.0
//#define UNITY_POSITION(pos) float4 pos : SV_POSITION
//声明Clippos
float4 tex : TEXCOORD0; //UV
float4 eyeVec : TEXCOORD1; // eyeVec.xyz | fogCoord 相机到片元的vector
float4 tangentToWorldAndPackedData[3] : TEXCOORD2; // [3x3:tangentToWorld | 1x3:viewDirForParallax or worldPos] //切线矩阵 worldpos
half4 ambientOrLightmapUV : TEXCOORD5; // SH or Lightmap UV
UNITY_LIGHTING_COORDS(6,7)
//在AutoLight.cginc 里面有判断灯光模式 和 阴影模式
//6 _LightCoord 灯光数据 DECLARE_LIGHT_COORDS
//7 _ShadowCoord 阴影数据 UNITY_SHADOW_COORDS
// next ones would not fit into SM2.0 limits, but they are always for SM3.0+
//下一个不符合SM2.0限制,但始终适用于SM3.0 +
#if UNITY_REQUIRE_FRAG_WORLDPOS && !UNITY_PACK_WORLDPOS_WITH_TANGENT
float3 posWorld : TEXCOORD8;
#endif
UNITY_VERTEX_INPUT_INSTANCE_ID //GPU instance
UNITY_VERTEX_OUTPUT_STEREO //VR
};
vertForwardBase 顶点着色器
VertexOutputForwardBase vertForwardBase (VertexInput v) //顶点着色器
{
//输入 VertexInput
//输出 VertexOutputForwardBase
UNITY_SETUP_INSTANCE_ID(v);
VertexOutputForwardBase o;
UNITY_INITIALIZE_OUTPUT(VertexOutputForwardBase, o);
UNITY_TRANSFER_INSTANCE_ID(v, o);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); //VR
//GPU instance
float4 posWorld = mul(unity_ObjectToWorld, v.vertex);
#if UNITY_REQUIRE_FRAG_WORLDPOS
//如果片元要记录它在世界空间中的坐标
#if UNITY_PACK_WORLDPOS_WITH_TANGENT
//使用法线和高度视差效果时,需要切线矩阵
//寄存在矩阵W项中
o.tangentToWorldAndPackedData[0].w = posWorld.x;
o.tangentToWorldAndPackedData[1].w = posWorld.y;
o.tangentToWorldAndPackedData[2].w = posWorld.z;
#else
o.posWorld = posWorld.xyz;
//否则直接寄存在posworld寄存插槽中
#endif
#endif
o.pos = UnityObjectToClipPos(v.vertex);
//裁剪变换
o.tex = TexCoords(v);
o.eyeVec.xyz = NormalizePerVertexNormal(posWorld.xyz - _WorldSpaceCameraPos);
// _WorldSpaceCameraPos相机坐标
// 单位化片元到相机连线向量
float3 normalWorld = UnityObjectToWorldNormal(v.normal);
//顶点法线(非法线图)变换到世界空间
#ifdef _TANGENT_TO_WORLD
//如果定义 切线到世界
float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
//切线判断
float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld.xyz, tangentWorld.w);//1.3
//构建切线空间到世界空间 变换矩阵
o.tangentToWorldAndPackedData[0].xyz = tangentToWorld[0];
o.tangentToWorldAndPackedData[1].xyz = tangentToWorld[1];
o.tangentToWorldAndPackedData[2].xyz = tangentToWorld[2];
#else
//否则
o.tangentToWorldAndPackedData[0].xyz = 0;
o.tangentToWorldAndPackedData[1].xyz = 0;
o.tangentToWorldAndPackedData[2].xyz = normalWorld;
//只存顶点法线在世空间下的信息
#endif
//接收阴影 内含不同的判断
UNITY_TRANSFER_LIGHTING(o, v.uv1); //阴影
o.ambientOrLightmapUV = VertexGIForward(v, posWorld, normalWorld); //1.5
//顶点着色器内的顶点灯光计算
#ifdef _PARALLAXMAP//曲面细分 视差效果 略过(端游才用得上)
TANGENT_SPACE_ROTATION;
half3 viewDirForParallax = mul (rotation, ObjSpaceViewDir(v.vertex));
o.tangentToWorldAndPackedData[0].w = viewDirForParallax.x;
o.tangentToWorldAndPackedData[1].w = viewDirForParallax.y;
o.tangentToWorldAndPackedData[2].w = viewDirForParallax.z;
#endif
UNITY_TRANSFER_FOG_COMBINED_WITH_EYE_VEC(o,o.pos);
//fog 项 雾效
return o;
}
1、TexCoords 函数
对UV处理,和选择。
float4 TexCoords(VertexInput v) //1.1
{
float4 texcoord;
texcoord.xy = TRANSFORM_TEX(v.uv0, _MainTex); // Always source from uv0
//UV0 主贴图纹理
texcoord.zw = TRANSFORM_TEX(((_UVSec == 0) ? v.uv0 : v.uv1), _DetailAlbedoMap);
//detail 纹理 经过面板选择用uv0 还是uv1
return texcoord;
}
2、NormalizePerVertexNormal 函数
顶点到相机的连线向量,是否在顶点着色器中计算归一化还是在片元中进行
// NormalizePerPixelNormal的对应项
//跳过每个顶点的归一化,并期望每个像素进行归一化
half3 NormalizePerVertexNormal (float3 n) //进行浮点运算以避免溢出
{ //1.2
#if (SHADER_TARGET < 30) || UNITY_STANDARD_SIMPLE
//如果shader model 小于3.0 或者simple开启
return normalize(n);
//返回归一化计算
#else
//如果shader model 小于3.0 或者simple开启 以外
return n;
//返回原数据,打算在fragment (pixel) 里面计算
#endif
}
3、CreateTangentToWorldPerVertex 函数
副法线计算,并存储进矩阵。常用于把切线空间下的法线贴图变换到世界空间下。
half3x3 CreateTangentToWorldPerVertex(half3 normal, half3 tangent, half tangentSign)
{ //1.3
//对于奇数负比例变换,我们需要翻转符号
half sign = tangentSign * unity_WorldTransformParams.w;
//w通常为1.0,对于奇数负比例转换,通常为-1.0
half3 binormal = cross(normal, tangent) * sign;
//叉积 判断正反
return half3x3(tangent, binormal, normal);
//返回矩阵数据
}
4、UNITY_TRANSFER_LIGHTING() 宏调用
阴影相关暂时略过。
5、VertexGIForward 函数
inline half4 VertexGIForward(VertexInput v, float3 posWorld, half3 normalWorld)
{ //1.5
half4 ambientOrLightmapUV = 0;
#ifdef LIGHTMAP_ON
//如果定义LIGHTMAP_ON
ambientOrLightmapUV.xy = v.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
//xy输入lightmap uv
ambientOrLightmapUV.zw = 0;
//仅针对动态对象的采样光探针(无静态或动态光照贴图)
#elif UNITY_SHOULD_SAMPLE_SH
//否则如果 SH
#ifdef VERTEXLIGHT_ON
//顶点光照开启
//非重要点光源的近似照度 用一个数组 存各灯光的储数据
ambientOrLightmapUV.rgb = Shade4PointLights (
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
unity_4LightAtten0, posWorld, normalWorld);
#endif
ambientOrLightmapUV.rgb = ShadeSHPerVertex (normalWorld, ambientOrLightmapUV.rgb);
//如果有启用VERTEXLIGHT_ON ShadeSHPerVertex SH光照
#endif
#ifdef DYNAMICLIGHTMAP_ON
//动态lightmap
ambientOrLightmapUV.zw = v.uv2.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
#endif
return ambientOrLightmapUV;
}
输入:v.uv1,v.uv2,世界顶点,世界法线
输出:不同定义判断不同输出。
功能:顶点灯光计算。动态,静态,混合lightmap和SH计算。
5.1、ShadeSHPerVertex 函数
不同的判断计算,输出最终的SH
half3 ShadeSHPerVertex (half3 normal, half3 ambient)
{
#if UNITY_SAMPLE_FULL_SH_PER_PIXEL
// 完全按像素
// 无用事项
#elif (SHADER_TARGET < 30) || UNITY_STANDARD_SIMPLE
// 平台判断 完全按顶点
ambient += max(half3(0,0,0), ShadeSH9 (half4(normal, 1.0)));
#else
// 每顶点L2,每像素L0..L1和伽马校正
// 注意:SH数据始终处于线性状态,并且计算在顶点和像素之间划分
// 将环境转换为线性,并在最后进行最终的伽玛校正(每像素)
#ifdef UNITY_COLORSPACE_GAMMA
ambient = GammaToLinearSpace (ambient);
#endif
ambient += SHEvalLinearL2 (half4(normal, 1.0)); // 没有最大值,因为这只是L2贡献
#endif
return ambient;
}
5.1.1、SHEvalLinearL2 函数
3阶的球谐光照计算,SH多项式。
// 正常应标准化,w = 1.0
half3 SHEvalLinearL2 (half4 normal)
{
half3 x1, x2;
// 二次(L2)多项式中的4个
half4 vB = normal.xyzz * normal.yzzx;
x1.r = dot(unity_SHBr,vB);
x1.g = dot(unity_SHBg,vB);
x1.b = dot(unity_SHBb,vB);
// 最终(第5次)二次(L2)多项式
half vC = normal.x*normal.x - normal.y*normal.y;
x2 = unity_SHC.rgb * vC;
return x1 + x2;
}
简略分析完顶点着色器。略过了GPU instance ,VR,阴影部分。
二、fragForwardBaseInternal 片元着色器
从最佳性能来说,优先去做裁剪剔除判断,减少片元着色阶断的运算压力。
half4 fragForwardBaseInternal (VertexOutputForwardBase i) //片元着色器
{
UNITY_APPLY_DITHER_CROSSFADE(i.pos.xy); //lod剔除裁剪 淡入淡出 //2.1
FRAGMENT_SETUP(s) //片元计算前的数据准备
UNITY_SETUP_INSTANCE_ID(i); //GPU instance
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);//VR
UnityLight mainLight = MainLight (); //灯光信息结构体声明 灯光初始化 //2.3
UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld); //阴影 //2.4
half occlusion = Occlusion(i.tex.xy); //AO //2.5
UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight); ////光照计算 2.6
half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
//BRDF 计算 2.7
c.rgb += Emission(i.tex.xy); // 自发光 2.8
UNITY_EXTRACT_FOG_FROM_EYE_VEC(i);
UNITY_APPLY_FOG(_unity_fogCoord, c.rgb); // fog 项
return OutputForward(c, s.alpha); // alpha 通道处理 2.9
}
1、UNITY_APPLY_DITHER_CROSSFADE
输入:unity_DitherMask(内置程序化计算后的纹理图4X4级)。vpos为clip空间下的i.pos.xy坐标。
unity_LODFade 为内置方法。
功能:像素裁剪剔除,和LOD中淡入淡出效果。
#ifdef LOD_FADE_CROSSFADE //2.1
#define UNITY_APPLY_DITHER_CROSSFADE(vpos) UnityApplyDitherCrossFade(vpos)
sampler2D unity_DitherMask;
void UnityApplyDitherCrossFade(float2 vpos)
{
vpos /= 4; // the dither mask texture is 4x4
//抖动蒙版纹理为4x4
float mask = tex2D(unity_DitherMask, vpos).a;
float sgn = unity_LODFade.x > 0 ? 1.0f : -1.0f;
clip(unity_LODFade.x - mask * sgn);
}
#else
#define UNITY_APPLY_DITHER_CROSSFADE(vpos)
#endif
2、FRAGMENT_SETUP 宏调用,输出结构体FragmentCommonData x。
#define FRAGMENT_SETUP(x) FragmentCommonData x = \
FragmentSetup(i.tex, i.eyeVec.xyz, IN_VIEWDIR4PARALLAX(i), i.tangentToWorldAndPackedData, IN_WORLDPOS(i));
IN_VIEWDIR4PARALLAX 函数 IN_WORLDPOS 函数 见笔记下文
inline FragmentCommonData FragmentSetup (inout float4 i_tex, float3 i_eyeVec, half3 i_viewDirForParallax, float4 tangentToWorld[3], float3 i_posWorld)
{
i_tex = Parallax(i_tex, i_viewDirForParallax); //视差判断 //2.2.1
half alpha = Alpha(i_tex.xy); //透明信息计算 //2.2.2
#if defined(_ALPHATEST_ON)
clip (alpha - _Cutoff); //如果是alpha test 丢弃使用
#endif
FragmentCommonData o = UNITY_SETUP_BRDF_INPUT (i_tex); //2.2.3 //MetallicSetup
//检测金属流还是高光流 不同的shader线路切换 一系列的函数调用都不再相同 得到metallic 计算后的相关数据
o.normalWorld = PerPixelWorldNormal(i_tex, tangentToWorld); //2.2.4
//如果有法线图就得到转换过后的法线图信息 如果没有就使用归一化后的顶点法线 两种方案
o.eyeVec = NormalizePerPixelNormal(i_eyeVec); //2.2.5
//判断平台后 归一化
o.posWorld = i_posWorld;
// NOTE: shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
//注意:着色器依赖于预乘alpha混合(_SrcBlend = One,_DstBlend = OneMinusSrcAlpha)
//alpha Reflectivity metallic 值都会影响abledo
o.diffColor = PreMultiplyAlpha (o.diffColor, alpha, o.oneMinusReflectivity, /*out*/ o.alpha); //2.2.6
return o;
}
功能:部分数据已计算,并为后续的计算做准备。
输入又输出 inout float4 i_tex 如果启用了视差,便修改uv信息。祥见笔记下文Parallax函数部分。
输入部分
i_tex 祥见笔记下文Parallax函数部分
i.eyeVec.xyz 顶点函数输入项归一化后的:相机的连线向量
2.00、IN_VIEWDIR4PARALLAX
输入:不同的定义输入也不一样
输出:判断不一样,输出的值也不一样
功能:视差使用。略。
i.tangentToWorldAndPackedData = float4 tangentToWorldAndPackedData[3]顶点着色器传递过来的矩阵数据。前面3x3方阵保存了计算过后的切线,副法线,法线信息。矩阵第4列保存了 posWorld数。
2.01、IN_WORLDPOS
#if UNITY_REQUIRE_FRAG_WORLDPOS
#if UNITY_PACK_WORLDPOS_WITH_TANGENT
#define IN_WORLDPOS(i) half3(i.tangentToWorldAndPackedData[0].w,i.tangentToWorldAndPackedData[1].w,i.tangentToWorldAndPackedData[2].w)
#else
#define IN_WORLDPOS(i) i.posWorld
#endif
#define IN_WORLDPOS_FWDADD(i) i.posWorld
#else
#define IN_WORLDPOS(i) half3(0,0,0)
#define IN_WORLDPOS_FWDADD(i) half3(0,0,0)
#endif
输入:不同的定义输入也不一样
输出:判断不一样,输出的值也不一样
功能:为得到顶点在世界坐标下的数值 i.posWorld
2.02、输出部分 结构体FragmentCommonData
struct FragmentCommonData
{
half3 diffColor, specColor;
//diffColor 漫反射颜色 specColor 漫反射高光
//注意:出于优化目的,smoothness和oneMinusReflectivity主要用于DX9 SM2.0级别。
//大部分数学运算都是在这些(1-x)值上完成的,这样可以节省一些宝贵的ALU插槽。
half oneMinusReflectivity, smoothness;
//oneMinusReflectivity 1-F0
//smoothness roughness
float3 normalWorld; //worldspace normal vector
float3 eyeVec; //worldspace 相机到顶点 vector
half alpha;
float3 posWorld; //fragment worldspace postion
#if UNITY_STANDARD_SIMPLE
half3 reflUVW; //worldspace pix 到相机 vector 也相当于 invert eyeVec vector
#endif
#if UNITY_STANDARD_SIMPLE
half3 tangentSpaceNormal; //tangentspace normal vector
#endif
};
2.1、Parallax函数
float4 Parallax (float4 texcoords, half3 viewDir) //2.2.1
{
#if !defined(_PARALLAXMAP) || (SHADER_TARGET < 30)
// 如果没有定义_PARALLAXMAP 或者 渲染模型低于3.0 返回原数值
return texcoords;
#else
//否则 视差高度UV偏移计算
half h = tex2D (_ParallaxMap, texcoords.xy).g;
float2 offset = ParallaxOffset1Step (h, _Parallax, viewDir);
return float4(texcoords.xy + offset, texcoords.zw + offset);
#endif
}
输入:float4 texcoords = i.uv ; half3 viewDir = i.eyeVec.xyz
输出:判断结果不一样,输出不一样。inout 如果启用了视差:便修改uv信息
功能:平台区分和视差图的使用,略过
2.2、Alpha 函数
half Alpha(float2 uv) //2.2.2
{
#if defined(_SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A)
return _Color.a;
#else
return tex2D(_MainTex, uv).a * _Color.a;
#endif
}
输入i.tex.xy,透明信息计算,输出half alpha通道信息。
2.3、UNITY_SETUP_BRDF_INPUT 函数
CGINCLUDE
#define UNITY_SETUP_BRDF_INPUT MetallicSetup
ENDCG //定义不同的工作流 MetallicSetup 函数名
在shader内SubShader前指定分支工作流
输入项
FragmentCommonData o = UNITY_SETUP_BRDF_INPUT (i_tex)
如果前面包含视差计算,那么此处的输入i_tex,是经过计算过后的uv信息。
输出项 结构体FragmentCommonData
2.3、MetallicSetup 函数
inline FragmentCommonData MetallicSetup (float4 i_tex) ////112a6
{
half2 metallicGloss = MetallicGloss(i_tex.xy); //002a //函数调取
//得到metallic roughness值
half metallic = metallicGloss.x; //重声明临时变量
half smoothness = metallicGloss.y; //重声明临时变量// this is 1 minus the square root of real roughness m.
//这是1减去实际粗糙度m的平方根 1-CookTorrance_roughness 平方根 平方根概念:比如 9 的平方根是3和-3。
half oneMinusReflectivity; //变量声明
half3 specColor; //变量声明
half3 diffColor = DiffuseAndSpecularFromMetallic (Albedo(i_tex), metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity); //003a
//003a1
FragmentCommonData o = (FragmentCommonData)0; //结构体声明,及给上0参。 //004a
o.diffColor = diffColor; //赋值 //漫反射颜色部分
o.specColor = specColor; //赋值 //F0 漫反射高光
o.oneMinusReflectivity = oneMinusReflectivity; //1-F0 值域为0的反向漫反射高光
o.smoothness = smoothness; //roughness
return o;
}
输入项:如果前面包含视差计算,那么此处的输入i_tex,是经过计算过后的uv信息。
输出项
struct FragmentCommonData
{
half3 diffColor, specColor;
//diffColor 漫反射颜色 specColor 漫反射高光
//注意:出于优化目的,smoothness和oneMinusReflectivity主要用于DX9 SM2.0级别。
//大部分数学运算都是在这些(1-x)值上完成的,这样可以节省一些宝贵的ALU插槽。
half oneMinusReflectivity, smoothness;
//oneMinusReflectivity 1-F0
//smoothness roughness
float3 normalWorld; //worldspace normal vector
float3 eyeVec; //worldspace 相机到顶点 vector
half alpha;
float3 posWorld; //fragment worldspace postion
#if UNITY_STANDARD_SIMPLE
half3 reflUVW; //worldspace pix 到相机 vector 也相当于 invert eyeVec vector
#endif
#if UNITY_STANDARD_SIMPLE
half3 tangentSpaceNormal; //tangentspace normal vector
#endif
};
2.3.4、MetallicGloss函数
half2 MetallicGloss(float2 uv) //002a
{
// 输入 float2 i_tex.xy
// 输出 half2 metallic roughness贴图信息
half2 mg; //变量声明
#ifdef _METALLICGLOSSMAP
//这里用来判断是否被赋值纹理图
#ifdef _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
//如果roughness在albedo A通道
mg.r = tex2D(_MetallicGlossMap, uv).r;
//metallic值使用资源metallic图R通道
mg.g = tex2D(_MainTex, uv).a;
//roughness使用资源的Albedo图A通道
#else
//否则
mg = tex2D(_MetallicGlossMap, uv).ra;
//直接使用Metallic Roughness资源图RA通道
#endif
mg.g *= _GlossMapScale; //最后混合roughness 面板参数0-1强度控制
//从此处也能看出,在正确的工作流中。不会把Metallic给0-1提供变量乱调整
//因为在物质切片中,Albedo与metallic对应关系对于错在渲染中太重要了。
#else
//如果没有纹理图输入
mg.r = _Metallic; //直接 使用面板参数 0-1强度控制
#ifdef _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
//如果roughness在albedo A通道
mg.g = tex2D(_MainTex, uv).a * _GlossMapScale;
// roughness使用资源的Albedo图A通道 并面板参数0-1强度控制
#else
//否则
mg.g = _Glossiness; //直接 使用面板参数Smoothness 0-1强度控制
#endif
#endif
return mg; //返回metallic和roughness
}
输入:i_tex.xy UV信息
输出:metallic 和 roughness
功能:根据不同的条件计算得到metallic 和 roughness,贴图信息与shader面板属性计算都在此
2.3.5、DiffuseAndSpecularFromMetallic函数
inline half3 DiffuseAndSpecularFromMetallic (half3 albedo, half metallic, out half3 specColor, out half oneMinusReflectivity) //003a
{
specColor = lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, metallic);
//specColor 镜面反射率。使用金属度去混合电介质与albedo颜色
//gamma #define unity_ColorSpaceDielectricSpec half4(0.220916301, 0.220916301, 0.220916301, 1.0 - 0.220916301)
//linear #define unity_ColorSpaceDielectricSpec half4(0.04, 0.04, 0.04, 1.0 - 0.04) // standard dielectric reflectivity coef at incident angle (= 4%)
//颜色空间不一样 计算方式不一样 结果顺理就不一样了
//metallic 值越大镜面反射率就是 albedo,值越小就是 unity_ColorSpaceDielectricSpec
//工作流中 PBR 资源制作时 正常情况下会及少画非金属 也就是说只画金属与非金属 非1即0 中间值是半导体 又是金属又是非金 Substance流中可以自动化检测
oneMinusReflectivity = OneMinusReflectivityFromMetallic(metallic); //003a2 特定数值范围内,反向
//oneMinusReflectivity 漫反射率
//如果 metallic = 0 非金属 在gamma空间中漫反射 = albedo * 0.779083699 接近1,漫反射颜色接近白色。结果差不多是albedo的直接颜色 (不同的颜色空间颜色不一样,切记)
//如果 metallic = 1 金属 在gamma空间中的漫反射 = albedo * 0 完全没有漫反射颜色 黑色
//如果 metallic 介于之间 就使用上述插值
return albedo * oneMinusReflectivity;
//返回 漫反射颜色部分
}
输入项
DiffuseAndSpecularFromMetallic (Albedo(i_tex), metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity)
输出项
out half3 specColor 为F0(镜面反射率)项:
specColor = lerp (unity_ColorSpaceDielectricSpec.rgb, albedo, metallic)
//specColor 镜面反射率。使用金属度去混合电介质与albedo颜色
//gamma #define unity_ColorSpaceDielectricSpec half4(0.220916301, 0.220916301, 0.220916301, 1.0 - 0.220916301)
//linear #define unity_ColorSpaceDielectricSpec half4(0.04, 0.04, 0.04, 1.0 - 0.04) // standard dielectric reflectivity coef at incident angle (= 4%)
//颜色空间不一样 计算方式不一样 结果顺理就不一样了
//metallic 值越大镜面反射率就是 albedo,值越小就是 unity_ColorSpaceDielectricSpec
//工作流中 PBR 资源制作时 正常情况下会及少画非金属 也就是说只画金属与非金属 非1即0 中间值是半导体 又是金属又是非金 Substance流中可以自动化检测
out half oneMinusReflectivity(漫反射率)项:
oneMinusReflectivity = OneMinusReflectivityFromMetallic(metallic); //003a2
//oneMinusReflectivity 漫反射率
half3 diffColor 漫反射颜色
2.3.5.0、Albedo
half3 Albedo(float4 texcoords) //003a1
{ //参数为float4 两个UV信息
// 输入 i_tex.xyzw
// 输出 half3 albedo
half3 albedo = _Color.rgb * tex2D (_MainTex, texcoords.xy).rgb;
//获取abledo贴图 并且混合面板颜色
#if _DETAIL
//如果启用面板细节 detail mask属性
#if (SHADER_TARGET < 30)
//如果shader model 小于3.0
// SM20: instruction count limitation
// SM20: no detail mask
half mask = 1; //shader model条件限制 所以直接使用1 不使用Detail mask功能
#else
//否则shader model 不小于3.0 那么
half mask = DetailMask(texcoords.xy); //函数调用 return tex2D (_DetailMask, uv).a;
//mask 值为 面板纹理_DetailMask 的A通道
#endif
half3 detailAlbedo = tex2D (_DetailAlbedoMap, texcoords.zw).rgb;
//获取detail albedo.rgb
#if _DETAIL_MULX2
albedo *= LerpWhiteTo (detailAlbedo * unity_ColorSpaceDouble.rgb, mask); //函数调用
//(1-t) + b * t LerpWhiteTo(half3 b, half t)
//linear #define unity_ColorSpaceDouble fixed4(4.59479380, 4.59479380, 4.59479380, 2.0)
//gamma #define unity_ColorSpaceDouble fixed4(2.0, 2.0, 2.0, 2.0)
//管线不同值不同
#elif _DETAIL_MUL
albedo *= LerpWhiteTo (detailAlbedo, mask);
//(1-t) + b * t LerpWhiteTo(half3 b, half t)
//普通相乘
#elif _DETAIL_ADD
albedo += detailAlbedo * mask;
//普通相加
#elif _DETAIL_LERP
//普通lerp
albedo = lerp (albedo, detailAlbedo, mask);
#endif
#endif
return albedo; //返回最终aldedo
}
输入 i_tex.xyzw 输出 albedo
2.3.5.1、OneMinusReflectivityFromMetallic 函数
inline half OneMinusReflectivityFromMetallic(half metallic) //003a2
{
// We'll need oneMinusReflectivity, so
// 1-reflectivity = 1-lerp(dielectricSpec, 1, metallic) = lerp(1-dielectricSpec, 0, metallic)
// store (1-dielectricSpec) in unity_ColorSpaceDielectricSpec.a, then
// 1-reflectivity = lerp(alpha, 0, metallic) = alpha + metallic*(0 - alpha) =
// = alpha - metallic * alpha
//gamma #define unity_ColorSpaceDielectricSpec half4(0.220916301, 0.220916301, 0.220916301, 1.0 - 0.220916301)
//linear #define unity_ColorSpaceDielectricSpec half4(0.04, 0.04, 0.04, 1.0 - 0.04) // standard dielectric reflectivity coef at incident angle (= 4%)
half oneMinusDielectricSpec = unity_ColorSpaceDielectricSpec.a;
//gamma 1.0 - 0.220916301 = 0.779083699
//linear 1.0 - 0.04 = 0.96
//取值
return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
//gamma 0.779083699 - metallic * 0.779083699
//linear 0.96 - metallic * 0.96
}
把原始metallic反向过后的计算,得到新的metallic。
2.4、PerPixelWorldNormal函数
float3 PerPixelWorldNormal(float4 i_tex, float4 tangentToWorld[3])
{//2.2.4
#ifdef _NORMALMAP
// 如果材质球使用了法线贴图
half3 tangent = tangentToWorld[0].xyz; //切线
half3 binormal = tangentToWorld[1].xyz; //副法线
half3 normal = tangentToWorld[2].xyz; //法线
#if UNITY_TANGENT_ORTHONORMALIZE
//如果需要对切线空间的3个坐标值进行正交单位化
normal = NormalizePerPixelNormal(normal);
// ortho-normalize Tangent 单位化法向量
tangent = normalize (tangent - normal * dot(tangent, normal));
// recalculate Binormal 如果原本切线与法线相互垂直,则dot(tangent, normal)为0
// 如果不垂直,则切线等于三角形斜边,法线为一个直角边
// tangent - normal * dot(tanget, normal) 为另一边
half3 newB = cross(normal, tangent);
// 调整法线和切线使之相互垂直之后,重新计算副法线
binormal = newB * sign (dot (newB, binormal));
#endif
half3 normalTangent = NormalInTangentSpace(i_tex);
// 切线空间下的法向量
float3 normalWorld = NormalizePerPixelNormal(tangent * normalTangent.x + binormal * normalTangent.y + normal * normalTangent.z);
// @TODO:看看我们是否也可以在SM2.0上进行压缩
//法线贴图 从切线空间转到世界空间 单位化法向量
#else
float3 normalWorld = normalize(tangentToWorld[2].xyz);
//顶点法线的归一化
#endif
return normalWorld;
}
输出处理后的法线项,像把法线贴图从切线空间转换到世界空间
2.4.1、NormalizePerPixelNormal 函数
float3 NormalizePerPixelNormal (float3 n)
{
#if (SHADER_TARGET < 30) || UNITY_STANDARD_SIMPLE
return n;
#else
return normalize((float3)n); //进行浮动以避免溢出
#endif
}
2.4.2、NormalInTangentSpace 函数
#ifdef _NORMALMAP
// 如果有定义 _NORMALMAP
half3 NormalInTangentSpace(float4 texcoords)
{
half3 normalTangent = UnpackScaleNormal(tex2D (_BumpMap, texcoords.xy), _BumpScale);
// 解法线,并使用_BumpScale控制强度
#if _DETAIL && defined(UNITY_ENABLE_DETAIL_NORMALMAP)
// 如果定义 _DETAIL 和已定义 UNITY_ENABLE_DETAIL_NORMALMAP
half mask = DetailMask(texcoords.xy);
half3 detailNormalTangent = UnpackScaleNormal(tex2D (_DetailNormalMap, texcoords.zw), _DetailNormalMapScale);
#if _DETAIL_LERP
normalTangent = lerp(
normalTangent,
detailNormalTangent,
mask);
#else
normalTangent = lerp(
normalTangent,
BlendNormals(normalTangent, detailNormalTangent),
mask);
#endif
#endif
return normalTangent;
}
#endif
解法线,并添加强度控制,并且还包含多层法线的使用,很好的叠加方法。
2.5、跟上面2.4.1 同一方法
2.6、PreMultiplyAlpha 函数
inline half3 PreMultiplyAlpha (half3 diffColor, half alpha, half oneMinusReflectivity, out half outModifiedAlpha)
{ //2.2.6
#if defined(_ALPHAPREMULTIPLY_ON)
//注意:着色器依赖于预乘alpha混合(_SrcBlend = One,_DstBlend = OneMinusSrcAlpha)
//透明度从“漫反射”组件中“删除”
diffColor *= alpha;
#if (SHADER_TARGET < 30)
// SM2.0:指令计数限制
//相反,它会牺牲部分基于物理的透明度,其中反射率会影响透明度
// SM2.0:使用未修改的Alpha
outModifiedAlpha = alpha;
#else
//反射率从其他组件中“消除”,包括透明度
// outAlpha = 1-(1-alpha)*(1-reflectivity) = 1-(oneMinusReflectivity - alpha*oneMinusReflectivity) =
// = 1-oneMinusReflectivity + alpha*oneMinusReflectivity
outModifiedAlpha = 1 - oneMinusReflectivity + alpha * oneMinusReflectivity;
#endif
#else
outModifiedAlpha = alpha;
#endif
return diffColor;
}
o.diffColor = PreMultiplyAlpha (o.diffColor, alpha, o.oneMinusReflectivity, /*out*/ o.alpha);
输入:o.diffColor 漫反射颜色,alpha 计算过后的alpha值,o.oneMinusReflectivity 1-F0
输出:o.diffColor 经过alpha 混合处理过后的漫反射颜色值, /out/ o.alpha 经过不同的处理得到不同的 alpha 混合
3、MainLight 函数
UnityLight mainLight = MainLight ();
结构体 UnityLight
struct UnityLight
{ //2.6.01
half3 color;
half3 dir;
half ndotl; // Deprecated: Ndotl is now calculated on the fly and is no longer stored. Do not used it.
//不推荐使用:Ndotl现在可以即时计算,不再存储。 不要使用它。
};
UnityLight 结构体初始化
UnityLight MainLight () //113a
{
UnityLight l;
l.color = _LightColor0.rgb;
l.dir = _WorldSpaceLightPos0.xyz;
return l;
}
4、UNITY_LIGHT_ATTENUATION 函数
阴影相关,略过。
5、Occlusion函数
half occlusion = Occlusion(i.tex.xy);
half Occlusion(float2 uv) //115a
{
#if (SHADER_TARGET < 30)
// SM20:指令计数限制
// SM20:更简单的遮挡
return tex2D(_OcclusionMap, uv).g;
#else
half occ = tex2D(_OcclusionMap, uv).g;
return LerpOneTo (occ, _OcclusionStrength);
//控制条反向下
#endif
}
获取AO贴图
6、FragmentGI函数
inline UnityGI FragmentGI (FragmentCommonData s, half occlusion, half4 i_ambientOrLightmapUV, half atten, UnityLight light, bool reflections)
{ //2.6
UnityGIInput d;
d.light = light;
d.worldPos = s.posWorld;
d.worldViewDir = -s.eyeVec; //worldspace 相机向量
d.atten = atten; //光照衰减
#if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
//如果静态lightmap或者动态lightmap开启
d.ambient = 0;
d.lightmapUV = i_ambientOrLightmapUV;
#else //如果静态lightmap或者动态lightmap开启 之外
d.ambient = i_ambientOrLightmapUV.rgb;
// i_ambientOrLightmapUV.rgb 环境光照RGB颜色
d.lightmapUV = 0;
#endif
//两个反射探针(反射球)各项属性
d.probeHDR[0] = unity_SpecCube0_HDR;//记录全局光照所要使用的光探针
d.probeHDR[1] = unity_SpecCube1_HDR;
#if defined(UNITY_SPECCUBE_BLENDING) || defined(UNITY_SPECCUBE_BOX_PROJECTION)
d.boxMin[0] = unity_SpecCube0_BoxMin; // .w holds lerp value for blending
// .w保留lerp值以进行混合
#endif
#ifdef UNITY_SPECCUBE_BOX_PROJECTION
d.boxMax[0] = unity_SpecCube0_BoxMax;
d.probePosition[0] = unity_SpecCube0_ProbePosition;
d.boxMax[1] = unity_SpecCube1_BoxMax;
d.boxMin[1] = unity_SpecCube1_BoxMin;
d.probePosition[1] = unity_SpecCube1_ProbePosition;
#endif
if(reflections)
{//如果反射
Unity_GlossyEnvironmentData g = UnityGlossyEnvironmentSetup(s.smoothness, -s.eyeVec, s.normalWorld, s.specColor); //2.6.1
// 获取IBL计算所需的结构体
//s.specColor = fresnel0 参数都没用上
// Replace the reflUVW if it has been compute in Vertex shader. Note: the compiler will optimize the calcul in UnityGlossyEnvironmentSetup itself
//如果reflUVW已在Vertex着色器中计算,则将其替换。 注意:编译器将在UnityGlossyEnvironmentSetup自身中优化计算
//结构体 Unity_GlossyEnvironmentData 返回两个数据 half roughness,half3 reflUVW
#if UNITY_STANDARD_SIMPLE
//如果是简化版 就直接使用simple版本的
g.reflUVW = s.reflUVW;
#endif
return UnityGlobalIllumination (d, occlusion, s.normalWorld, g); //2.6.2
// 间接照明的漫反射 + 镜面反射部分
}
else //否则
{
return UnityGlobalIllumination (d, occlusion, s.normalWorld); //2.6.3
// 间接照明的漫反射
}
}
UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);
s 为结构体,occlusion AO项,i.ambientOrLightmapUV lightmapuv或者是SH,atten 阴影,mainLight 结构体,输出 6.00
UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);
6.1、UnityGlossyEnvironmentSetup 函数
Unity_GlossyEnvironmentData g = UnityGlossyEnvironmentSetup(s.smoothness, -s.eyeVec, s.normalWorld, s.specColor);
// 获取IBL计算所需的结构体
//s.specColor = fresnel0 参数都没用上
// Replace the reflUVW if it has been compute in Vertex shader. Note: the compiler will optimize the calcul in UnityGlossyEnvironmentSetup itself
//如果reflUVW已在Vertex着色器中计算,则将其替换。 注意:编译器将在UnityGlossyEnvironmentSetup自身中优化计算
//结构体 Unity_GlossyEnvironmentData 返回两个数据 half roughness,half3 reflUVW
Unity_GlossyEnvironmentData UnityGlossyEnvironmentSetup(half Smoothness, half3 worldViewDir, half3 Normal, half3 fresnel0)
{ // 2.6.1
//输入
// half Smoothness
// half3 worldViewDir
// half3 Normal
// half3 fresnel0
//输出结构体 Unity_GlossyEnvironmentData 返回两个数据 half roughness ,half3 reflUVW
Unity_GlossyEnvironmentData g; //结构体声明 //2.6.1.00
g.roughness /* perceptualRoughness */ = SmoothnessToPerceptualRoughness(Smoothness); //2.6.1.01
//函数调用 return (1 - smoothness); 反向下参数控制更加好调节效果
g.reflUVW = reflect(-worldViewDir, Normal); //cubemap的UV
return g;
}
输出结构体 Unity_GlossyEnvironmentData 返回两个数据 half roughness,half3 reflUVW
struct Unity_GlossyEnvironmentData
{ //2.6.1.00
// - Deferred case have one cubemap
// - Forward case can have two blended cubemap (unusual should be deprecated).
//-Deferred的情况只有一个立方体贴图
//-Forward案例可以具有两个混合的多维数据集映射(不建议使用,不建议使用)。
// Surface properties use for cubemap integration
//表面属性用于立方体贴图集成
half roughness; // CAUTION: This is perceptualRoughness but because of compatibility this name can't be change :(
//注意:这是perceptualRoughness,但是由于兼容性,该名称不能更改:(
half3 reflUVW;
//反射3维UV坐标 cubemap采样常用 IBL中常用计算
};
6.1.1、SmoothnessToPerceptualRoughness 函数
float SmoothnessToPerceptualRoughness(float smoothness)
{
return (1 - smoothness);
}
6.2 UnityGlobalIllumination 函数 全局光照
inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half3 normalWorld)
{ //2.6.3
return UnityGI_Base(data, occlusion, normalWorld); //2.6.2.1
}
inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half3 normalWorld, Unity_GlossyEnvironmentData glossIn)
{ //2.6.2
//输入
// UnityGIInput
// {
// UnityLight light; //UnityLight 结构体 包含color dir ndotl
// float3 worldPos;
// half3 worldViewDir;
// half atten;
// half3 ambient;
// float4 lightmapUV;
// float4 boxMax[2];
// float4 probePosition[2];
// float4 probeHDR[2];
// }
// 输入 Unity_GlossyEnvironmentData
// {
// half roughness;
// half3 reflUVW;
// };
//输出UnityGI返回五个数值 color dir ndotl diffuse specular
UnityGI o_gi = UnityGI_Base(data, occlusion, normalWorld); //2.6.2.1
//lightmap 计算
o_gi.indirect.specular = UnityGI_IndirectSpecular(data, occlusion, glossIn); //2.6.2.2
//间接反射
return o_gi;
}
输入
输出
6.2.1、UnityGI_Base函数
UnityGI o_gi = UnityGI_Base(data, occlusion, normalWorld);
inline UnityGI UnityGI_Base(UnityGIInput data, half occlusion, half3 normalWorld)
{ //2.6.2.1
UnityGI o_gi;//结构体声明 color dir ndotl diffuse specular
ResetUnityGI(o_gi); //2.6.2.1.0
//具有光照贴图支持的基本传递负责处理ShadowMask /出于性能原因在此处进行混合
#if defined(HANDLE_SHADOWS_BLENDING_IN_GI)
half bakedAtten = UnitySampleBakedOcclusion(data.lightmapUV.xy, data.worldPos); //烘焙阴影
float zDist = dot(_WorldSpaceCameraPos - data.worldPos, UNITY_MATRIX_V[2].xyz); //当前片元的Z 深度
float fadeDist = UnityComputeShadowFadeDistance(data.worldPos, zDist); //计算阴影淡化
data.atten = UnityMixRealtimeAndBakedShadows(data.atten, bakedAtten, UnityComputeShadowFade(fadeDist)); //混合动态阴影和静态阴影
#endif
o_gi.light = data.light;
o_gi.light.color *= data.atten; //对亮度进行衰减
#if UNITY_SHOULD_SAMPLE_SH //间接光 diffuse 第一步计算 球谐光照
o_gi.indirect.diffuse = ShadeSHPerPixel(normalWorld, data.ambient, data.worldPos);
#endif
#if defined(LIGHTMAP_ON)
// Baked lightmaps
half4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, data.lightmapUV.xy); //获取lm
half3 bakedColor = DecodeLightmap(bakedColorTex); //解压lightmap
#ifdef DIRLIGHTMAP_COMBINED //定向光照贴图技术 directional lightmap 略
fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER (unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy);
o_gi.indirect.diffuse += DecodeDirectionalLightmap (bakedColor, bakedDirTex, normalWorld);
#if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
ResetUnityLight(o_gi.light);
o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap (o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
#endif
#else // not directional lightmap 如果没有定向光照贴图
o_gi.indirect.diffuse += bakedColor; //间接光 diffuse 第二步计算 加上 lightmap
#if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
// 当定义lightmap开启 并且 没有开启shadow mask 并且 开启阴影
// 经常会碰到烘了lightmap(已经有阴影) 但角色的实时阴影加进不了 lightmap阴影里,此方法应该是较好的解决
ResetUnityLight(o_gi.light); //重置参数,清零
o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap(o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld);
//lambert lightmap 和阴影 阴影颜色做混合 减去部分光照
#endif
#endif
#endif
#ifdef DYNAMICLIGHTMAP_ON //动态lightmap 略
// Dynamic lightmaps
fixed4 realtimeColorTex = UNITY_SAMPLE_TEX2D(unity_DynamicLightmap, data.lightmapUV.zw);
half3 realtimeColor = DecodeRealtimeLightmap (realtimeColorTex);
#ifdef DIRLIGHTMAP_COMBINED
half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw);
o_gi.indirect.diffuse += DecodeDirectionalLightmap (realtimeColor, realtimeDirTex, normalWorld);
#else
o_gi.indirect.diffuse += realtimeColor;
#endif
#endif
o_gi.indirect.diffuse *= occlusion; //间接光 diffuse 第三步计算 乘上 AO
return o_gi;
}
6.2.1.1、ResetUnityGI 函数
ResetUnityGI(o_gi);
inline void ResetUnityGI(out UnityGI outGI)
{ //2.6.2.1.0
ResetUnityLight(outGI.light); //传递赋值 初始化
outGI.indirect.diffuse = 0; //初始化
outGI.indirect.specular = 0;//初始化
}
6.2.1.1.1、ResetUnityLight函数
ResetUnityLight(o_gi.light);
inline void ResetUnityLight(out UnityLight outLight)
{
outLight.color = half3(0, 0, 0);
outLight.dir = half3(0, 1, 0); // Irrelevant direction, just not null//不相关的方向,但不为null
outLight.ndotl = 0; // Not used 未使用
}
6.2.1.2、ShadeSHPerPixel 函数
half3 ShadeSHPerPixel (half3 normal, half3 ambient, float3 worldPos)
{
half3 ambient_contrib = 0.0;
#if UNITY_SAMPLE_FULL_SH_PER_PIXEL
// 完全按像素 片元中每顶点计算球谐
#if UNITY_LIGHT_PROBE_PROXY_VOLUME
if (unity_ProbeVolumeParams.x == 1.0)
ambient_contrib = SHEvalLinearL0L1_SampleProbeVolume(half4(normal, 1.0), worldPos);
else
ambient_contrib = SHEvalLinearL0L1(half4(normal, 1.0));
#else
ambient_contrib = SHEvalLinearL0L1(half4(normal, 1.0));
#endif
ambient_contrib += SHEvalLinearL2(half4(normal, 1.0));
ambient += max(half3(0, 0, 0), ambient_contrib);
#ifdef UNITY_COLORSPACE_GAMMA
ambient = LinearToGammaSpace(ambient);
#endif
#elif (SHADER_TARGET < 30) || UNITY_STANDARD_SIMPLE
// 完全按像素
// 无事。 SH的环境上的Gamma转换在顶点着色器中进行,请参见ShadeSHPerVertex。
#else
// 每顶点L2,每像素L0..L1和伽马校正
// 在这种情况下,环境始终是线性的,请参见ShadeSHPerVertex()
#if UNITY_LIGHT_PROBE_PROXY_VOLUME
if (unity_ProbeVolumeParams.x == 1.0)
ambient_contrib = SHEvalLinearL0L1_SampleProbeVolume (half4(normal, 1.0), worldPos);
else
ambient_contrib = SHEvalLinearL0L1 (half4(normal, 1.0));
#else
ambient_contrib = SHEvalLinearL0L1 (half4(normal, 1.0));
#endif
ambient = max(half3(0, 0, 0), ambient + ambient_contrib); // 之前在顶点着色器中包含L2贡献。
#ifdef UNITY_COLORSPACE_GAMMA
ambient = LinearToGammaSpace (ambient);
#endif
#endif
return ambient;
}
6.2.1.3、SubtractMainLightWithRealtimeAttenuationFromLightmap 函数
inline half3 SubtractMainLightWithRealtimeAttenuationFromLightmap (half3 lightmap, half attenuation, half4 bakedColorTex, half3 normalWorld)
{
//让我们尝试使实时阴影在已经包含表面的表面上起作用
//烘烤的灯光和主要太阳光的阴影。
half3 shadowColor = unity_ShadowColor.rgb;
half shadowStrength = _LightShadowData.x;
//摘要:
// 1)通过从实时阴影遮挡的位置减去估计的光贡献来计算阴影中的可能值:
// a)保留其他烘焙的灯光和反弹光
// b)消除了背向灯光的几何图形上的阴影
// 2)锁定用户定义的ShadowColor。
// 3)选择原始的光照贴图值(如果它是最暗的)。
//提供良好的照明估计,就好像在烘焙过程中光线会被遮盖一样。
//保留反射光和其他烘烤的光
//几何体上没有阴影,远离光
half ndotl = LambertTerm (normalWorld, _WorldSpaceLightPos0.xyz); //普通lambert 但有平台判断 有性能优化区分
half3 estimatedLightContributionMaskedByInverseOfShadow = ndotl * (1- attenuation) * _LightColor0.rgb; //阴影区分
half3 subtractedLightmap = lightmap - estimatedLightContributionMaskedByInverseOfShadow; //减去光照贴图
// 2)允许用户定义场景的整体环境并在实时阴影变得太暗时控制情况。
half3 realtimeShadow = max(subtractedLightmap, shadowColor);
realtimeShadow = lerp(realtimeShadow, lightmap, shadowStrength);
// 3)选择最暗的颜色 取小
return min(lightmap, realtimeShadow);
}
6.2.1.3.1、LambertTerm 函数
inline half DotClamped (half3 a, half3 b)
{
#if (SHADER_TARGET < 30)
return saturate(dot(a, b));
#else
return max(0.0h, dot(a, b));
#endif
}
inline half LambertTerm (half3 normal, half3 lightDir)
{
return DotClamped (normal, lightDir);
}
6.2.2、UnityGI_IndirectSpecular 函数
inline half3 UnityGI_IndirectSpecular(UnityGIInput data, half occlusion, Unity_GlossyEnvironmentData glossIn)
{ //2.6.2.2
half3 specular;
// 变量声明
#ifdef UNITY_SPECCUBE_BOX_PROJECTION
// 我们将直接在glossIn中调整reflUVW(因为我们将它分别两次传递给probe0和probe1两次传递给Unity_GlossyEnvironment)
// 因此请保留原始内容以传递给BoxProjectedCubemapDirection
half3 originalReflUVW = glossIn.reflUVW;
glossIn.reflUVW = BoxProjectedCubemapDirection (originalReflUVW,
data.worldPos,
data.probePosition[0],
data.boxMin[0],
data.boxMax[0]);
// 获取立方体贴图的方向向量 视线向量相对于片元的法向量相反反射向量延长
#endif
#ifdef _GLOSSYREFLECTIONS_OFF
specular = unity_IndirectSpecColor.rgb;
#else
half3 env0 = Unity_GlossyEnvironment (UNITY_PASS_TEXCUBE(unity_SpecCube0), data.probeHDR[0], glossIn);
// 环境球
#ifdef UNITY_SPECCUBE_BLENDING //反射球混合
const float kBlendFactor = 0.99999;
float blendLerp = data.boxMin[0].w;
UNITY_BRANCH
if (blendLerp < kBlendFactor)
{
#ifdef UNITY_SPECCUBE_BOX_PROJECTION
glossIn.reflUVW = BoxProjectedCubemapDirection (originalReflUVW, data.worldPos, data.probePosition[1], data.boxMin[1], data.boxMax[1]);
#endif
half3 env1 = Unity_GlossyEnvironment (UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1,unity_SpecCube0), data.probeHDR[1], glossIn);
specular = lerp(env1, env0, blendLerp);
}
else
{
specular = env0;
}
#else
specular = env0;
#endif
#endif
return specular * occlusion; //最后乘上AO
}
6.2.2.1、BoxProjectedCubemapDirection 函数
inline float3 BoxProjectedCubemapDirection (float3 worldRefl, float3 worldPos, float4 cubemapCenter, float4 boxMin, float4 boxMax)
{
//我们有一个有效的反射探头吗?
UNITY_BRANCH
if (cubemapCenter.w > 0.0)
{
float3 nrdir = normalize(worldRefl);
#if 1
float3 rbmax = (boxMax.xyz - worldPos) / nrdir;
float3 rbmin = (boxMin.xyz - worldPos) / nrdir;
float3 rbminmax = (nrdir > 0.0f) ? rbmax : rbmin;
#else // 优化版本
float3 rbmax = (boxMax.xyz - worldPos);
float3 rbmin = (boxMin.xyz - worldPos);
float3 select = step (float3(0,0,0), nrdir);
float3 rbminmax = lerp (rbmax, rbmin, select);
rbminmax /= nrdir;
#endif
float fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z);
worldPos -= cubemapCenter.xyz;
worldRefl = worldPos + nrdir * fa;
}
return worldRefl;
}
6.2.2.2、Unity_GlossyEnvironment 函数
6Unity_GlossyEnvironment 函数nt (UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1,unity_SpecCube0), data.probeHDR[1], glossIn);
half3 Unity_GlossyEnvironment (UNITY_ARGS_TEXCUBE(tex), half4 hdr, Unity_GlossyEnvironmentData glossIn)
{
half perceptualRoughness = glossIn.roughness /* perceptualRoughness */ ;//感知粗糙度
// TODO:注意:从Morten重新映射可能仅适用于离线卷积,请参见对运行时卷积的影响!
//现在禁用
#if 0
float m = PerceptualRoughnessToRoughness(perceptualRoughness); // m是实际粗糙度参数
const float fEps = 1.192092896e-07F; // 最小,使得1.0 + FLT_EPSILON!= 1.0(+ 1e-4h在这里不好。显然是非常错误的)
float n = (2.0/max(fEps, m*m))-2.0; // 重新映射为规格功率。 参见等式。 21英寸 --> https://dl.dropboxusercontent.com/u/55891920/papers/mm_brdf.pdf
n /= 4; // 从n_dot_h公式重新映射到n_dot_r。 请参见“预卷积多维数据集与路径跟踪器”部分 --> https://s3.amazonaws.com/docs.knaldtech.com/knald/1.0.0/lys_power_drops.html
perceptualRoughness = pow( 2/(n+2), 0.25); // 重新映射回实际粗糙度的平方根(0.25既包含转换的sqrt根,又包含从粗糙度到perceptualRoughness的sqrt)
#else
// MM:出乎意料地非常接近上述#if 0'ed代码。
perceptualRoughness = perceptualRoughness * (1.7 - 0.7*perceptualRoughness);
#endif
half mip = perceptualRoughnessToMipmapLevel(perceptualRoughness);
half3 R = glossIn.reflUVW;
half4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(tex, R, mip);
return DecodeHDR(rgbm, hdr);
}
6.2.2.2.1、PerceptualRoughnessToRoughness 函数
float PerceptualRoughnessToRoughness(float perceptualRoughness)
{
return perceptualRoughness * perceptualRoughness;
}
6.2.2.2.2、perceptualRoughnessToMipmapLevel 函数
half perceptualRoughnessToMipmapLevel(half perceptualRoughness)
{
return perceptualRoughness * UNITY_SPECCUBE_LOD_STEPS;
} //#define UNITY_SPECCUBE_LOD_STEPS (6)
6.2.2.2.3、UNITY_SAMPLE_TEXCUBE_LOD 函数
#define UNITY_SAMPLE_TEXCUBE_LOD(tex,coord,lod) tex.SampleLevel (sampler##tex,coord, lod)
#define UNITY_SAMPLE_TEXCUBE_LOD(tex,coord,lod) texCUBElod (tex, half4(coord, lod))
#define UNITY_SAMPLE_TEXCUBE_SAMPLER_LOD(tex,samplertex,coord,lod) UNITY_SAMPLE_TEXCUBE_LOD(tex,coord,lod)
6.2.2.2.4、DecodeHDR 函数
// Decodes HDR textures
// handles dLDR, RGBM formats
inline half3 DecodeHDR (half4 data, half4 decodeInstructions)
{
// Take into account texture alpha if decodeInstructions.w is true(the alpha value affects the RGB channels)
half alpha = decodeInstructions.w * (data.a - 1.0) + 1.0;
// If Linear mode is not supported we can skip exponent part
#if defined(UNITY_COLORSPACE_GAMMA)
return (decodeInstructions.x * alpha) * data.rgb;
#else
# if defined(UNITY_USE_NATIVE_HDR)
return decodeInstructions.x * data.rgb; // Multiplier for future HDRI relative to absolute conversion.
# else
return (decodeInstructions.x * pow(alpha, decodeInstructions.y)) * data.rgb;
# endif
#endif
}
7、UNITY_BRDF_PBS 3 函数 (从手游来说性能上最好的)
half4 BRDF3_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
float3 normal, float3 viewDir,
UnityLight light, UnityIndirect gi)
{ //2.7
float3 reflDir = reflect (viewDir, normal);
half nl = saturate(dot(normal, light.dir));
half nv = saturate(dot(normal, viewDir));
// Vectorize Pow4 to save instructions 向量化Pow4以保存说明
half2 rlPow4AndFresnelTerm = Pow4 (float2(dot(reflDir, light.dir), 1-nv)); // use R.L instead of N.H to save couple of instructions
//使用R.L代替N.H保存指令
half rlPow4 = rlPow4AndFresnelTerm.x; // power exponent must match kHorizontalWarpExp in NHxRoughness() function in GeneratedTextures.cpp
// 幂指数必须与GeneratedTextures.cpp的NHxRoughness()函数中的kHorizontalWarpExp相匹配
half fresnelTerm = rlPow4AndFresnelTerm.y; // 简化版 (1-h . wi)4(次方)
half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity)); //掠射角项
half3 color = BRDF3_Direct(diffColor, specColor, rlPow4, smoothness); //函数调用 直接光部分
//2.7.1
color *= light.color * nl;
color += BRDF3_Indirect(diffColor, specColor, gi, grazingTerm, fresnelTerm); //间接光部分
//2.7.2
return half4(color, 1);
}
输入和输出
half4 BRDF3_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
float3 normal, float3 viewDir,
UnityLight light, UnityIndirect gi)
{ //2.7
float3 reflDir = reflect (viewDir, normal);
half nl = saturate(dot(normal, light.dir));
half nv = saturate(dot(normal, viewDir));
// Vectorize Pow4 to save instructions 向量化Pow4以保存说明
half2 rlPow4AndFresnelTerm = Pow4 (float2(dot(reflDir, light.dir), 1-nv)); // use R.L instead of N.H to save couple of instructions
//使用R.L代替N.H保存指令
half rlPow4 = rlPow4AndFresnelTerm.x; // power exponent must match kHorizontalWarpExp in NHxRoughness() function in GeneratedTextures.cpp
// 幂指数必须与GeneratedTextures.cpp的NHxRoughness()函数中的kHorizontalWarpExp相匹配
half fresnelTerm = rlPow4AndFresnelTerm.y; // 简化版 (1-h . wi)4(次方)
half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity)); //掠射角项
half3 color = BRDF3_Direct(diffColor, specColor, rlPow4, smoothness); //函数调用 直接光部分
//2.7.1
color *= light.color * nl;
color += BRDF3_Indirect(diffColor, specColor, gi, grazingTerm, fresnelTerm); //间接光部分
//2.7.2
return half4(color, 1);
}
7.1、Pow4 函数
inline half Pow4 (half x)
{
return x*x*x*x;
}
7.2、BRDF3_Direct 函数
sampler2D_float unity_NHxRoughness;
half3 BRDF3_Direct(half3 diffColor, half3 specColor, half rlPow4, half smoothness)
{ //基于blinn-phong 光照模型的优化实现
half LUT_RANGE = 16.0; //必须与GeneratedTextures.cpp中的NHxRoughness()函数中的范围匹配
//查找纹理以保存指令
half specular = tex2D(unity_NHxRoughness, half2(rlPow4, SmoothnessToPerceptualRoughness(smoothness))).r * LUT_RANGE;
#if defined(_SPECULARHIGHLIGHTS_OFF)
//shader 内定义开关
specular = 0.0;
#endif
return diffColor + specular * specColor;
}
7.2.1、SmoothnessToPerceptualRoughness函数
float SmoothnessToPerceptualRoughness(float smoothness)
{
return (1 - smoothness);
}
7.3、BRDF3_Indirect 函数
half3 BRDF3_Indirect(half3 diffColor, half3 specColor, UnityIndirect indirect, half grazingTerm, half fresnelTerm)
{
half3 c = indirect.diffuse * diffColor;
c += indirect.specular * lerp (specColor, grazingTerm, fresnelTerm);
return c;
}
8、Emission 函数
half3 Emission(float2 uv)
{
#ifndef _EMISSION
return 0;
#else
return tex2D(_EmissionMap, uv).rgb * _EmissionColor.rgb;
#endif
}
9、OutputForward 函数
half4 OutputForward (half4 output, half alphaFromSurface)
{
#if defined(_ALPHABLEND_ON) || defined(_ALPHAPREMULTIPLY_ON)
output.a = alphaFromSurface;
#else
UNITY_OPAQUE_ALPHA(output.a);
#endif
return output;
}
UNITY_OPAQUE_ALPHA 函数
#define UNITY_OPAQUE_ALPHA(outputAlpha) outputAlpha = 1.0
必读参考资料:
Substance PBR 指导手册
Substance Academyacademy.substance3d.com[图片上传失败...(image-d16b4a-1639498321449)]
八猴 PBR 指导手册
https://marmoset.co/posts/basic-theory-of-physically-based-rendering/marmoset.co
SIGGRAPH 2012年原版文章: 2012-Physically-Based Shading at Disney
本人整理过后的中文版:
MasterWangdaoyong/Shader-Graphgithub.com
SIGGRAPH 2017年原版文章: 2017-Reflectance Models (BRDF)
https://cgg.mff.cuni.cz/~pepca/lectures/pdf/pg2-05-brdf.en.pdfcgg.mff.cuni.cz
闫令琪(闫神):
GAMES: Graphics And Mixed Environment Seminargames-cn.org
希望也有学101,102,201,202的朋友讨论沟通:)
个人学习注释笔记地址:
https://github.com/MasterWangdaoyong/Shader-Graph/tree/main/Unity_SourceCodegithub.com
https://github.com/MasterWangdaoyong/Shader-Graph/tree/main/Unity_SourceCodegithub.com
毛星云(大佬):
毛星云:【基于物理的渲染(PBR)白皮书】(三)迪士尼原则的BRDF与BSDF相关总结zhuanlan.zhihu.com雨轩先行者同类资料:
雨轩:Unity PBR Standard Shader 实现详解 (四)BRDF函数计算zhuanlan.zhihu.com熊新科: 源码解析 第10章节 第11章节
冯乐乐:入门精要 第18章节