之前零零散散的看了一些unity内置的cginc,对一些函数方法做了些注释,
主要是以下几个文件
UnityStandardBRDF.cginc
UnityCG.cginc
UnityGlobalIllumination.cginc
UnityStandardUtil.cginc
UnityStandardShadow.cginc
UnityStandardCore.cginc
注释如下:
UnityStandardBRDF.cginc
/V的计算过程,描述遮蔽性
// Ref: http://jcgt.org/published/0003/02/03/paper.pdf
inline half SmithJointGGXVisibilityTerm (half NdotL, half NdotV, half roughness)
{
#if 0
// Original formulation:
// lambda_v = (-1 + sqrt(a2 * (1 - NdotL2) / NdotL2 + 1)) * 0.5f;
// lambda_l = (-1 + sqrt(a2 * (1 - NdotV2) / NdotV2 + 1)) * 0.5f;
// G = 1 / (1 + lambda_v + lambda_l);
// Reorder code to be more optimal
half a = roughness;
half a2 = a * a;
half lambdaV = NdotL * sqrt((-NdotV * a2 + NdotV) * NdotV + a2);
half lambdaL = NdotV * sqrt((-NdotL * a2 + NdotL) * NdotL + a2);
// Simplify visibility term: (2.0f * NdotL * NdotV) / ((4.0f * NdotL * NdotV) * (lambda_v + lambda_l + 1e-5f));
return 0.5f / (lambdaV + lambdaL + 1e-5f); // This function is not intended to be running on Mobile,
// therefore epsilon is smaller than can be represented by half
#else
// Approximation of the above formulation (simplify the sqrt, not mathematically correct but close enough)
half a = roughness;
half lambdaV = NdotL * (NdotV * (1 - a) + a);
half lambdaL = NdotV * (NdotL * (1 - a) + a);
return 0.5f / (lambdaV + lambdaL + 1e-5f);
#endif
}
//法线分布函数
//为避免正切计算,d做了公式转换,其他的基本是标准的GGX,只是加了个1e-7f防止除0异常
inline float GGXTerm (float NdotH, float roughness)
{
float a2 = roughness * roughness;
float d = (NdotH * a2 - NdotH) * NdotH + 1.0f; // 2 mad
return UNITY_INV_PI * a2 / (d * d + 1e-7f); // This function is not intended to be running on Mobile,
// therefore epsilon is smaller than what can be represented by half
}
//Unity BRDF的Diffuse项比Disney BRDF多乘一个π,Unity的Diffuse会更亮
//Unity BRDF的高光项比Disney BRDF多除以一个π,Unity的高光会更暗。
//Unity BRDF使用的[公式]是近似值而非精确值。
// Main Physically Based BRDF
// Derived from Disney work and based on Torrance-Sparrow micro-facet model
//
// BRDF = kD / pi + kS * (D * V * F) / 4
// I = BRDF * NdotL
//
// * NDF (depending on UNITY_BRDF_GGX):
// a) Normalized BlinnPhong
// b) GGX
// * Smith for Visiblity term
// * Schlick approximation for Fresnel
half4 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half smoothness,
float3 normal, float3 viewDir,
UnityLight light, UnityIndirect gi)
{
float perceptualRoughness = SmoothnessToPerceptualRoughness (smoothness);
float3 halfDir = Unity_SafeNormalize (float3(light.dir) + viewDir);
// NdotV should not be negative for visible pixels, but it can happen due to perspective projection and normal mapping
// In this case normal should be modified to become valid (i.e facing camera) and not cause weird artifacts.
// but this operation adds few ALU and users may not want it. Alternative is to simply take the abs of NdotV (less correct but works too).
// Following define allow to control this. Set it to 0 if ALU is critical on your platform.
// This correction is interesting for GGX with SmithJoint visibility function because artifacts are more visible in this case due to highlight edge of rough surface
// Edit: Disable this code by default for now as it is not compatible with two sided lighting used in SpeedTree.
#define UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV 0
#if UNITY_HANDLE_CORRECTLY_NEGATIVE_NDOTV
// The amount we shift the normal toward the view vector is defined by the dot product.
half shiftAmount = dot(normal, viewDir);
normal = shiftAmount < 0.0f ? normal + viewDir * (-shiftAmount + 1e-5f) : normal;
// A re-normalization should be applied here but as the shift is small we don't do it to save ALU.
//normal = normalize(normal);
half nv = saturate(dot(normal, viewDir)); // TODO: this saturate should no be necessary here
#else
half nv = abs(dot(normal, viewDir)); // This abs allow to limit artifact
#endif
half nl = saturate(dot(normal, light.dir));
float nh = saturate(dot(normal, halfDir));
half lv = saturate(dot(light.dir, viewDir));
half lh = saturate(dot(light.dir, halfDir));
// Diffuse term
//DisneyDiffuse 用于计算 (1+(FD90 - 1)(1-cosl)^5)*(1+(FD90 - 1)(1-cosv)^5)
half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness) * nl;
//V=G/(4*cosv*cosl) V用于描述可见性
//G(几何遮蔽函数),unity选择的是Height-Correlated Masking and Shadowing形式
// Specular term
// HACK: theoretically we should divide diffuseTerm by Pi and not multiply specularTerm!
// BUT 1) that will make shader look significantly darker than Legacy ones
// and 2) on engine side "Non-important" lights have to be divided by Pi too in cases when they are injected into ambient SH
float roughness = PerceptualRoughnessToRoughness(perceptualRoughness);
#if UNITY_BRDF_GGX
// GGX with roughtness to 0 would mean no specular at all, using max(roughness, 0.002) here to match HDrenderloop roughtness remapping.
roughness = max(roughness, 0.002);
half V = SmithJointGGXVisibilityTerm (nl, nv, roughness);
法线分布函数
float D = GGXTerm (nh, roughness);
#else
// Legacy
half V = SmithBeckmannVisibilityTerm (nl, nv, roughness);
half D = NDFBlinnPhongNormalizedTerm (nh, PerceptualRoughnessToSpecPower(perceptualRoughness));
#endif
half specularTerm = V*D * UNITY_PI; // Torrance-Sparrow model, Fresnel is applied later
# ifdef UNITY_COLORSPACE_GAMMA
specularTerm = sqrt(max(1e-4h, specularTerm));
# endif
// specularTerm * nl can be NaN on Metal in some cases, use max() to make sure it's a sane value
specularTerm = max(0, specularTerm * nl);
#if defined(_SPECULARHIGHLIGHTS_OFF)
specularTerm = 0.0;
#endif
// surfaceReduction = Int D(NdotH) * NdotH * Id(NdotL>0) dH = 1/(roughness^2+1)
half surfaceReduction;
# ifdef UNITY_COLORSPACE_GAMMA
surfaceReduction = 1.0-0.28*roughness*perceptualRoughness; // 1-0.28*x^3 as approximation for (1/(x^4+1))^(1/2.2) on the domain [0;1]
# else
surfaceReduction = 1.0 / (roughness*roughness + 1.0); // fade \in [0.5;1]
# endif
// To provide true Lambert lighting, we need to be able to kill specular completely.
specularTerm *= any(specColor) ? 1.0 : 0.0;
half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity));
//diffColor*gi.diffuse是间接光的diffuse项
//surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv)是间接光的高光项
//light.color是渲染方程中入射radiance的大小
//diffColor*light.color*diffuseTerm+specularTerm * light.color * FresnelTerm (specColor, lh)则是应用Disney BRDF的直接光照部分
half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm)
+ specularTerm * light.color * FresnelTerm (specColor, lh)
+ surfaceReduction * gi.specular * FresnelLerp (specColor, grazingTerm, nv);
return half4(color, 1);
}
-------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------
UnityCG.cginc
//在Directional Lightmap模式中,Unity会把光照信息烘焙成一个domain light 的光照,相当于变成了一个单入射光源的照明情况,然后BRDF选择半兰伯特,最后除以一个平衡系数
inline half3 DecodeDirectionalLightmap (half3 color, fixed4 dirTex, half3 normalWorld)
{
}
//ComputeNonStereoScreenPos函数解释:
//Pos是Clip Space的坐标,XY分量的坐标范围是[-w,w],这个函数实际上是作一个坐标变换,将XY分量变换到[0,w]区间。ZW分量保持不变。也就是说当这个值被传到片元着色器中后,XY分量就在
//[0,1],就是屏幕空间坐标。也就是说实际上是按照当前着色点的屏幕空间坐标去采样Screen Space Shadow Map的
// Projected screen position helpers
#define V2F_SCREEN_TYPE float4
inline float4 ComputeNonStereoScreenPos(float4 pos) {
}
//标准的Normal Offset Bias方案。片元朝着背离表面法线的方向偏移,偏移量正比于表面法线于光线夹角的正弦值,比例系数由外部指定。
float4 UnityClipSpaceShadowCasterPos(float4 vertex, float3 normal)
{
}
//UNITY_FOG_LERP_COLOR的实现
//就是根据雾因子在雾的颜色和物体的颜色间做个线性插值。
#define UNITY_FOG_LERP_COLOR(col,fogCol,fogFac) col.rgb = lerp((fogCol).rgb, (col).rgb, saturate(fogFac))
-------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------
UnityGlobalIllumination.cginc
//环境光,作为间接光(indirect light)通过IBL( Image Based Lighting)来实现
//UnityGI_Base函数返回的颜色值为间接光的漫反射部分
//Unity内置了unity_Lightmap、unity_SHAr等全局变量,来从预先烘焙好的Lightmap贴图或light probe中读取颜色,其中UNITY_SHOULD_SAMPLE_SH代码段处理的是从light probe中读取颜色值。一般渲染时静态物体读取lightmap,非静态物体读取light probe
//UnityGI_Base会先做实时Shadow与GI的混合,然后再用来阻挡光源
//UnityGI_Base首先初始化UnityGI数据结构。之后处理光源衰减,最后再计算间接光照。间接光照有3个来源,光照探针,LightMap和实时GI。
inline UnityGI UnityGI_Base(UnityGIInput data, half occlusion, half3 normalWorld)
{
UnityGI o_gi;
ResetUnityGI(o_gi);
// Base pass with Lightmap support is responsible for handling ShadowMask / blending here for performance reason
//Shadow与GI的混合
#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);
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;
//UNITY_SHOULD_SAMPLE_SH处理从light probe中读取颜色值。一般渲染时静态物体读取lightmap,非静态物体读取light probe
//ShadeSHPerPixel:从光照探针中重构出着色点Irradiance的算法
#if UNITY_SHOULD_SAMPLE_SH
o_gi.indirect.diffuse = ShadeSHPerPixel(normalWorld, data.ambient, data.worldPos);
#endif
//LighgtMap计算部分,LightMap部分的代码非常直接,可以看到主要在处理3个东西,Lightmap,Directional Lightmap和Shadow Mask
#if defined(LIGHTMAP_ON)
// Baked lightmaps
half4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, data.lightmapUV.xy);
half3 bakedColor = DecodeLightmap(bakedColorTex);
#ifdef DIRLIGHTMAP_COMBINED
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;
#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
#endif
#endif
//实时GI,处理了带和不带方向两种情况
#ifdef DYNAMICLIGHTMAP_ON
// 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;
return o_gi;
}
//UnityGI_IndirectSpecular返回的颜色值为间接光的镜面反射部分。
//Unity用reflection probe来保存预先烘焙好的环境光反射贴图,通过内置变量unity_SpecCube0,unity_SpecCube1访问
inline half3 UnityGI_IndirectSpecular(UnityGIInput data, half occlusion, Unity_GlossyEnvironmentData glossIn)
{
half3 specular;
#ifdef UNITY_SPECCUBE_BOX_PROJECTION
// we will tweak reflUVW in glossIn directly (as we pass it to Unity_GlossyEnvironment twice for probe0 and probe1), so keep original to pass into 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);
//Indirect Specular主要有env0和env1插值得到。而env0和env1均由Unity_GlossyEnvironment函数计算得到
specular = lerp(env1, env0, blendLerp);
}
else
{
specular = env0;
}
#else
specular = env0;
#endif
#endif
return specular * occlusion;
}
//从UnityStandardCore.cginc文件的FragmentGI调用到这个接口
inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half3 normalWorld, Unity_GlossyEnvironmentData glossIn)
{
}
-------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------
UnityStandardUtil.cginc
// Diffuse/Spec Energy conservation
//Unity提供了specular setup工作流程来控制漫反射和镜面反射比例
//代码中用1减去镜面反射比例,得到漫反射比例。当传入的specColor为白色时,SpecularStrength返回1,结果漫反射比例为0,发生完美镜面反射。
//计算得到的diffColor和specColor作为比例系数用于最终漫反射和镜面反射计算:
//BRDF = kD / pi + kS * (D * V * F) / 4
//代码中的 kD和kS对应着diffColor和specColor。
inline half3 EnergyConservationBetweenDiffuseAndSpecular (half3 albedo, half3 specColor, out half oneMinusReflectivity)
{
oneMinusReflectivity = 1 - SpecularStrength(specColor);
#if !UNITY_CONSERVE_ENERGY
return albedo;
#elif UNITY_CONSERVE_ENERGY_MONOCHROME
return albedo * oneMinusReflectivity;
#else
return albedo * (half3(1,1,1) - specColor);
#endif
}
//“Diffuse和Specular互斥”能量守恒。
//根据金属度计算漫反射和镜面反射比例,当metallic为1时,反射率接近1,函数返回的diffColor接近0,表示几乎不反射漫反射。
//Unity的内置变量unity_ColorSpaceDielectricSpec定义了绝缘体的高光颜色和反射率,不完全为0,是一个经验值
inline half OneMinusReflectivityFromMetallic(half metallic)
{
// 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
half oneMinusDielectricSpec = unity_ColorSpaceDielectricSpec.a;
return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
}
-------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------
UnityStandardShadow.cginc
//CSM 间接光照的实时阴影部分
void vertShadowCaster (VertexInput v
, out float4 opos : SV_POSITION
#ifdef UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT
, out VertexOutputShadowCaster o
#endif
#ifdef UNITY_STANDARD_USE_STEREO_SHADOW_OUTPUT_STRUCT
, out VertexOutputStereoShadowCaster os
#endif
)
{
UNITY_SETUP_INSTANCE_ID(v);
#ifdef UNITY_STANDARD_USE_STEREO_SHADOW_OUTPUT_STRUCT
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(os);
#endif
//TRANSFER_SHADOW_CASTER_NOPOS实现坐标变换
TRANSFER_SHADOW_CASTER_NOPOS(o,opos)
#if defined(UNITY_STANDARD_USE_SHADOW_UVS)
o.tex = TRANSFORM_TEX(v.uv0, _MainTex);
#ifdef _PARALLAXMAP
TANGENT_SPACE_ROTATION;
o.viewDirForParallax = mul (rotation, ObjSpaceViewDir(v.vertex));
#endif
#endif
}
-------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------
UnityStandardCore.cginc
//读取法线贴图主要由PerPixelWorldNormal函数实现
//tangentToWorld切换转到世界空间的矩阵,3行4列
float3 PerPixelWorldNormal(float4 i_tex, float4 tangentToWorld[3])
{
#ifdef _NORMALMAP
half3 tangent = tangentToWorld[0].xyz;//切线
half3 binormal = tangentToWorld[1].xyz;//副切线
half3 normal = tangentToWorld[2].xyz;//法线
#if UNITY_TANGENT_ORTHONORMALIZE
normal = NormalizePerPixelNormal(normal);
// ortho-normalize Tangent
tangent = normalize (tangent - normal * dot(tangent, normal));
// recalculate Binormal
half3 newB = cross(normal, tangent);
binormal = newB * sign (dot (newB, binormal));
#endif
//去读取法线贴图,得到切线空间的法线
//NormalInTangentSpace函数在读取法线时会根据_BumpScale对法线在tb方向进行缩放
half3 normalTangent = NormalInTangentSpace(i_tex);
//将法线从切线空间转换到世界空间,再归一化
float3 normalWorld = NormalizePerPixelNormal(tangent * normalTangent.x + binormal * normalTangent.y + normal * normalTangent.z); // @TODO: see if we can squeeze this normalize on SM2.0 as well
#else
float3 normalWorld = normalize(tangentToWorld[2].xyz);
#endif
return normalWorld;
}
// parallax transformed texcoord is used to sample occlusion
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);
half alpha = Alpha(i_tex.xy);
#if defined(_ALPHATEST_ON)
clip (alpha - _Cutoff);
#endif
FragmentCommonData o = UNITY_SETUP_BRDF_INPUT (i_tex);
//读取法线贴图主要由PerPixelWorldNormal实现
o.normalWorld = PerPixelWorldNormal(i_tex, tangentToWorld);
o.eyeVec = NormalizePerPixelNormal(i_eyeVec);
o.posWorld = i_posWorld;
// NOTE: shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
o.diffColor = PreMultiplyAlpha (o.diffColor, alpha, o.oneMinusReflectivity, /*out*/ o.alpha);
return o;
}
//FragmentGI的实现
//初始化UnityGIInput数据结构,初始化Unity_GlossyEnvironmentData数据结构,和调用UnityGlobalIllumination函数执行间接光照的计算
inline UnityGI FragmentGI (FragmentCommonData s, half occlusion, half4 i_ambientOrLightmapUV, half atten, UnityLight light, bool reflections)
{
........
}