type
status
date
slug
summary
tags
category
icon
password
1.概论
1.1 反射分布:不是 “围绕法线”,而是 “围绕头发纤维轴”(T切线)
通用 BXDF 的反射方向由 “表面法线(N)、入射光方向(L)、观察方向(V)” 三者决定,且分布围绕 N 对称;而头发的反射方向由 “纤维轴(Hair Axis,记为 Hx) ” 主导 —— 因为头发是圆柱,所有光交互都围绕这个轴旋转对称,形成 “非对称的多瓣反射”,这是头发 BXDF 最核心的标志。
头发反射模型(如 Marschner模型模型,头发 BXDF 的基础)将头发反射拆为多个 “瓣”:
- 主瓣(R1,Primary Reflection):光线在头发纤维外表面直接反射(类似镜面反射),方向靠近 “纤维轴的镜面方向”,对应头发的 “高光”(比如湿发的亮线);
- 次瓣(R2,Secondary Reflection):光线先进入头发内部,经过一次内部反射后再射出(类似透明材质的折射 + 反射),方向与主瓣错开一个角度,对应头发的 “次高光”(比如干发的柔和反光);
- 更高次的反射(R3、R4):光线在内部多次反射后射出,贡献头发的 “漫反射感”(比如头发的整体颜色)。
这种 “多瓣反射” 在通用 BXDF 中完全不存在 —— 通用模型最多只有 “漫反射 + 一次镜面反射”,且分布对称,而头发的反射是 “围绕纤维轴的非对称多峰分布”(画在极坐标图上是多个分开的峰,而非一个高斯峰)。
2.BXDF
https://www.bilibili.com/video/BV15tSuYJEvh/?spm_id_from=333.337.search-card.all.click&vd_source=608c812e1ac9bd991854f650decaf2ed

2.1 漫反射(kajiya)
Kajiya 模型(1989 年由 James T. Kajiya 和 Timothy L. Kay 提出,所以常叫 “Kajiya-Kay 模型”)的核心是两个公式:圆柱漫反射公式和圆柱高光公式,都以 “毛发切线 T(圆柱轴)” 为核心变量。
毛发的漫反射不是 “平面半球反射”,而是 “圆柱侧面的环形反射”—— 当光线垂直于圆柱轴时,反射最均匀(比如阳光从侧面照发丝,整个侧面都亮);当光线平行于圆柱轴时,反射最弱(比如阳光从头顶照发丝,只有细小的端面反射。
Kajiya 漫反射的核心公式 :
还是比较好理解的

2.2 高光
简易版本

本质上和bphone高光 异曲同工之妙
最简易用
1 - dot(T,H) 也行我们来看看这个公式是怎么来:
圆柱的径向法线(记为
N_radial)必须满足 dot(T, N_radial) = 0(垂直于 T)。此时,要让 dot(N_radial, H) 最大(因为点积越大,反射越对准视线),等价于:- 把 H 分解为 “平行于 T 的分量” 和 “垂直于 T 的分量(径向分量)”;
N_radial要和 H 的 “径向分量” 完全对齐(这样点积最大);
- 而 H 的径向分量长度 =
√(1 - dot(T, H)²)(因为 H 是单位向量,平行分量是dot(T, H),由勾股定理得径向分量长度)。
所以,
√(1 - dot(T, H)²) 就是 “所有可能的 N_radial 中,dot (N_radial, H) 的最大值” —— 这就把 “找最佳径向法线” 的问题,转化为 “计算 H 的径向分量长度”,完美避开了 “存无数法线” 的麻烦。工业版本(Marschner 模型)
核心 关键:
1.高斯函数(Hair_G):
就是正态分布函数
通过
Hair_G(B[0] * BScale, SinThetaL + SinThetaV - Shift)模拟高光的 “瓣状分布”(类似真实头发的主高光条带),其中B[0]是粗糙度的平方(控制高光锐度),Shift是高光偏移(控制条带位置)
float Hair_G(float B, float Theta)
{
return exp(-0.5 * Pow2(Theta) / (B * B)) / (sqrt(2.0 * PI) * B);
}
Theta:对应公式中的\(\Theta\),是 “光线与视线相对于发丝轴的角度偏差”(NX管线代码中是SinThetaL + SinThetaV - Shift),控制 “当前角度是否处于高光最强的位置”;
B:对应公式中的标准差\(\sigma\),由头发的粗糙度决定(如Pow2(GBuffer.Roughness)),控制 “高光瓣的宽度”(B 越大,高光越宽越柔和)。
指数部分:
exp(-0.5 * Pow2(Theta) / (B * B))- 核心作用:当
Theta = 0时(角度偏差为 0,刚好处于高光中心),指数部分 = 1(最大值),高光最强;
- 当
Theta增大时(角度偏离中心),指数部分快速衰减(如Theta = B时,值为\(e^{-0.5} \approx 0.6\);Theta = 2B时,值为\(e^{-2} \approx 0.14\)),模拟高光从中心到边缘的 “渐变衰减”。
归一化因子:
1 / (sqrt(2.0 * PI) * B)- 数学意义:确保整个高斯分布的积分(面积)=1,满足 “概率密度函数” 的定义;
- 渲染意义:避免高光强度随
B(粗糙度)变化时出现 “过亮或过暗”—— 当B增大(高光变宽),归一化因子减小,平衡整体能量(宽高光不会比窄高光更亮,只是范围更广)

2.Marschner 多瓣反射(真实感的关键)
3.TT(透射 - 透射)和 TRT(透射 - 反射 - 透射)组件
(1)次瓣(TT 组件):模拟 “光线穿透发丝” 的透射光
- 核心:光线从发丝一侧进入,内部反射一次后从另一侧透出,形成 “次高光”(比主高光更柔和,色素吸收有限,仅呈现 “浅淡通透色”)。
(2)高次瓣(TRT 组件):模拟 “光线多次反射” 的柔和光
- 核心:光线在发丝内部多次反射后透出,形成类似漫反射的颜色,头发基色的决定者。
3.Marschner详细知识(先看这)
核心关键字: 3条光路
概论
https://www.cnblogs.com/jaffhan/p/7506169.html

Marschner模型的核心是将头发视为半透明的圆柱体,光线与头发纤维相互作用会产生三种主要的光路:
- R路径 - 表面直接反射
光线:空气 → 头发表面 → 直接反弹回眼睛
视觉特征:锐利的主高光,白色/浅色
- TT路径 - 折射→内部传播→再次折射出表面
光线:空气 → 进入头发 → 穿透 → 射出头发 → 进入眼睛
视觉特征:柔和的次高光,在逆光+头发薄的时候主导。(边缘光,轮廓光,带有浅浅的头发颜色)
- TRT路径 - 折射→内部反射→再次折射出表面
光线:空气 → 进入头发 → 内壁反射 → 再次穿透 → 进入眼睛
视觉特征:看起来就是头发漫反射,头发基色的决定位置。即“次高光 + 本体基色。
总结一下:
如果非要争论谁是头发基础颜色的贡献者:
TRT 路径是常规光照的 “核心基础色”。TT 路径是背光场景的 “通透基础色”。这一点需要去代码里看。

实际效果

但是这个游戏里的截图我感觉对于TT和R的感觉我不太赞同
MP NP FP TP都是啥?

说直白点:
MP: 描述在在发丝T这个轴向上,V和L 分别和T的关系,导致高光的分布的不同。(当θ 为0的时候,V和L在对侧,且互相和T夹角为90度,此时高光最大)-------纵向纵向几何分布因子(纵向角度上,哪些位置有光贡献)
NP:描述在横向角度上的 角度筛选器 + 能量分配器。 1.角度筛选,因为每个光路只在特定φ角度贡献能量(说通俗点就是每个光路 都有个最佳观察角 只有视线正好对上 才能看见) 2. 能量分配 每个路径用的函数不同 R用的线性函数 TT用的弱指数函数 TRT用的强指数
涉及两个关键公式:
MP纵向散射函数(Longitudinal Scattering):
这里g是高斯分布(正太分布),β控制散射宽度,αp是倾角偏移量
- θ是什么?
- 物理意义:光线与视线和轴线夹角的和
- 类比实验:手电筒光斑在头发上移动时,光斑中心与头发中轴线的角度差
- 公式位置:
θ = θ_i - θ_o(入射角与出射角的差异)
在工业化实现中,θ=SinThetaL + SinThetaV = dot(T, L) + dot(T, L)
具体的 看头发的纵向夹角θ和最大高光 整个文件解释的非常非常清楚
- β控制散射宽度
- 物理意义:头发表面的微观粗糙度
- 类比实验:
- β小 → 头发光滑 → 光斑集中(锐利高光)
- β大 → 头发毛躁 → 光斑扩散(模糊高光)
在nx内是这样设计的:
float B[] = {Pow2(GBuffer.Roughness), // R路径 β = roughness²
Pow2(GBuffer.Roughness) / 2.0, // TT路径 β = roughness²/2
Pow2(GBuffer.Roughness) * 2.0 // TRT路径 β = roughness²*2
};
- 怎么实现的呢?(省略版本 NX用的完整高斯函数)
g(β, x) = exp(-0.5 * (x/β)²)
= exp(-x²/(2β²))
标准高斯:exp(-x²/(2σ²))
所以你看,就很巧妙 β 直接对应 σ(标准差)
- αp倾角偏移
- 物理意义:毛鳞片倾斜导致的光路偏移
- 类比实验:把头发旋转一定角度,光斑位置会偏移
θ - αp就是补偿这个偏移量!
NP方位角散射(Azimuthal Scattering)
看这里,这是个理想的函数,实际会用窄高斯(nx更简化 直接用指数)
此函数具体理解看NP相关 - Φ观测方位角差 和 理解
Φ观测方位角差是是吗?
狄拉克 δ 函数(Dirac delta function)直接理解为窄高斯
- 毛发的 “高光偏移”:真实毛发的高光不会在固定位置,而是随光照 / 视角绕毛发纤维转动(比如从正面看高光在左侧,侧面看移到中间)。NP 模型通过不同方位角的强度峰,精准还原这种 “跟着纤维转” 的高光。
那么
δ(φ - Φ(p,h)) 表示:
"只有当方位角差φ等于光程决定的相位偏移Φ时,该光线才被看到"
这里的解释 看这里 NP相关 - Φ观测方位角差 和 理解
效果:TRT路径的N_TRT产生了彩色边缘光(且只在特定角度看到) 而其他路径的N_p产生单色高光。
理论上,每个路径的光程、反射 / 透射次数不同,导致它们的 “最佳观察角”(Φ(p,h))完全不同,这是物理规律决定的:
- R 路径(镜面反射):光线不进入纤维,直接在表面反射,类似镜子。物理上,镜面反射的 “最佳观察角” 是φ≈0°(光照方向与视线方向在横截面内完全对称,符合反射定律)。
- TT 路径(单次透射):光线从纤维一侧穿入、直接从另一侧穿出,光程较短。物理上,这种透射的 “最佳观察角” 是φ≈180°(逆光方向,光线从纤维背面穿透过来,与视线方向相反)。
- TRT 路径(二次透射):光线穿入→内部反射一次→穿出,光程最长,受纤维圆柱形结构影响最大。物理上,这种路径的 “最佳观察角” 是φ≈8.9°(由纤维直径、折射率等参数计算得出,是理论推导的特征角)。
项目中如何实现?
在实际项目中,直接用指数函数替代(譬如TRT单调递增 :通过 “单调递增”,在 CosPhi≈0.987~1.0 的极窄区间快速变亮,小于 0.987 时几乎为 0;):
在NX中:
路径 | N(p,φ)形式 | 视觉贡献 | NX代码实现 |
R路径 | N_R ≈ 常数 | 锐利白色主高光 | 0.25 * CosHalfPhi |
TT路径 | N_TT ≈ 平滑衰减 | 边缘光 抡锅光 | exp(-3.65*CosPhi-3.98) |
TRT路径 | N_TRT ≈ 色散函数 | 柔和次高光(头发底色) | exp(17.0*CosPhi-16.78) |
这里的CosPHI 就是 cos(φ)
φ=0°→cos(0°)=1.0→Np=exp(0.22)≈1.25(看起来最大 但是不太可能出现)
φ=8.9°→cos(8.9°)≈0.987→Np=exp(0.001)≈1.0
φ=18°→cos(18°)≈0.951→Np=exp(-0.63)≈0.53
再具体点:
而是用了三个单调性不同的指数 / 线性函数,通过 “有效范围” 和 “强度竞争” 间接模拟出 “各路径在自己特征角最突出” 的效果:
- R 路径:用 “宽范围 + 低强度” 锁定 φ≈0° 的主导权
- 代码 Np_R = 0.25 * CosHalfPhi,是随 φ 增大而单调递减的函数(φ=0° 时最大,φ=180° 时接近 0)。
- 有效范围极宽(φ∈[0°, 120°] 都可见),但强度很低(最大值仅 0.25)。
- 还原逻辑:虽然范围宽,但在 φ≈0° 时,它是唯一 “强度最高” 的路径(此时 TRT 的 Np 虽高,但 R 路径的高光颜色更亮,且无衰减),因此在 φ≈0° 处,R 路径的单色高光主导视觉。
- TT 路径:用 “大角度单调递增” 锁定 φ≈180° 的主导权
- Np_TT = exp (-3.65*CosPhi - 3.98),是随 φ 增大而单调递增的函数(φ=180° 时最大,φ=0° 时接近 0)。
- 有效范围窄(φ∈[120°, 180°]),但强度较高(最大值≈0.718)。
- 还原逻辑:在大角度(φ>120°)时,R 和 TRT 路径的 Np 已接近 0,只有 TT 路径有明显强度,因此在 φ≈180° 处,TT 路径的轻微彩色透射光主导视觉。
- TRT 路径:用 “小角度单调递增 + R 路径压制” 锁定 φ≈8.9° 的主导权
- 代码 Np_TRT = exp (17*CosPhi - 16.78),是随 φ 减小而单调递增的函数(数学上 φ=0° 最大,但实际中被 R 路径压制)。
- 有效范围极窄(φ∈[0°, 30°]),强度最高(最大值≈1.25)。
还原逻辑:
- 在 φ≈0° 时,虽然 TRT 的 Np 很高,但 R 路径的单色高光(更亮、无衰减)会 “压制” TRT 的彩色光(人眼对白光更敏感);
- 在 φ≈8.9° 时,R 路径的 Np 已下降(Np_R≈0.25*cos (4.45°)≈0.248,和 TRT 的 Np≈1.0 相比,TRT 的彩色光开始主导);
- 因此,视觉上 TRT 的彩色光在 φ≈8.9° 处最突出,完美还原理论TRT中8.9度为最大亮度。

NP 和 MP的 协同和本质
Np对应横向(φ 角),Mp则控制纵向(θ 角,沿毛发轴线的角度)。两者协同的目的是(角度互补,锁定最亮位置)他俩是互补的,但是本质不同:
- 分工明确:Mp 负责纵向(θ 角,沿发丝轴线方向),决定 “在哪个纵向角度范围内有光贡献”;Np 负责横向(φ 角,环绕发丝方向),决定 “在哪个横向方位角上有光贡献”—— 两者覆盖 “纵向 + 横向” 两个正交角度,形成完整的 3D 角度约束。
- 协同锁定最亮位置:
- 比如 R 路径(主高光):Mp 的高斯分布锁定纵向 θ≈0°(L 和 V 对侧对称),Np 的线性分布(0.25×CosHalfPhi)锁定横向 φ≈0°(视线与光线方位一致),两者交点就是主高光最亮的点
- 再比如 TRT 路径(基础色 + 次高光):Mp 的高斯分布锁定纵向 θ≈0°~30°(更宽的纵向范围),Np 的指数分布(exp (17.0×CosPhi-16.78))锁定横向 φ≈8.9°(符合内部反射的最佳方位),交点就是 TRT 路径最亮的位置。
MP和NP是协同工作的互补函数,但本质不同:
MP定义的是 纵向角度上,哪些位置有光贡献 (类似画框)
NP定义的是 横向角度上,有光贡献的位置有多亮 (类似在画框内画的颜料浓度)
FP菲涅尔项
菲涅尔效应的本质是:光线入射角越大(越接近掠射角),反射能量越多,透射能量越少(比如水面边缘的强反光)。
Fp就是用数学公式量化这种 “反射 / 透射能量随角度的变化”,确保每个路径的能量分配符合物理规律。且每个光路的设计都是 确保所有有光路的 Fp 都围绕 “能量守恒”(反射 + 透射 = 100%)设计

R 路径(镜面反射):
Fp = Hair_F(sqrt(saturate(0.5 + 0.5 * VoL)))——1 次反射的菲涅尔项- 物理过程:光线仅与毛发表面(1 个界面)发生单次反射,不进入内部。此时
Fp需直接描述 “反射能量占比”。
- 公式解析:
VoL = dot(V, L):视线与光线的夹角余弦,间接反映 “入射角大小”(VoL 越小,入射角越大);sqrt(saturate(0.5 + 0.5 * VoL)):将VoL转换为更符合菲涅尔效应的入射角参数(本质是对角度的映射,让掠射角时参数更敏感);Hair_F(x):标准菲涅尔函数(大概率是 Schlick 近似:F = F0 + (1 - F0) * (1 - x)^5),输出反射能量占比 —— 入射角越大(x 越小),Hair_F(x)越接近 1(全反射),符合 “掠射角反光更强” 的规律。
- 核心逻辑:直接用反射的菲涅尔项,确保镜面反射的能量随角度变化正确(掠射角亮,正视角暗)。
TT 路径(单次透射):
Fp = Pow2(1.0 - f)——2 次透射的能量累积- 物理过程:光线穿过毛发的两个界面(进入时 1 次透射 + 射出时 1 次透射),没有反射。此时
Fp需描述 “两次透射后的能量剩余比例”。
- 公式解析:
f = Hair_F(...):光线在单个界面的反射能量占比(同 R 路径的菲涅尔项);1.0 - f:单次透射的能量占比(因为 “反射 + 透射 = 100% 能量”);Pow2(1.0 - f) = (1 - f)^2:两次透射的能量累积(进入时损失f,射出时再损失f,剩余能量为两次透射的乘积)。
- 核心逻辑:用两次透射的能量乘积,模拟 “光线穿过毛发时被两个界面衰减” 的过程 —— 入射角越大,
f越大(反射越多),Fp越小(透射能量越少),符合 “掠射角难透射” 的规律。
TRT 路径(二次透射):
Fp = Pow2(1.0 - f) * f——2 次透射 + 1 次反射的能量组合- 物理过程:光线经历两个透射界面 + 一个内部反射界面(进入透射→内部反射→射出透射)。此时
Fp需描述 “两次透射 + 一次反射后的能量剩余比例”。
- 公式解析:
(1 - f)^2:同 TT 路径,两次透射的能量占比;f:内部反射的能量占比(光线在毛发内部反射时,仍遵循菲涅尔效应,f越大,反射能量越多);- 整体
(1 - f)^2 * f:三次界面交互的能量乘积(进入透射损失→内部反射保留→射出透射损失)。
- 核心逻辑:比 TT 多乘一个
f,是因为 TRT 路径必须经历 “内部反射”—— 只有反射能量足够(f较大),且两次透射损失较小((1 - f)^2较大)时,TRT 路径的能量才显著,这与 “二次透射需要光线在内部有效反射” 的物理过程完全匹配。
完整的Marschner BRDF:
即:
// 伪代码示例
vec3 EvaluateMarschner(vec3 wi, vec3 wo) {
vec3 result = vec3(0);
// 对每条光路分别计算
result += M_R(longitudinal) * N_R(azimuthal); // 表面反射
result += M_TT(longitudinal) * N_TT(azimuthal); // 透射透射
result += M_TRT(longitudinal) * N_TRT(azimuthal);// 透射反射透射
return result / cos²θi;
}