Deep Read

Kajiya–Kay 模型

前置知识:Lambert 漫反射、Phong 高光、反射定律。用于头发/毛发/拉丝这类纤维状表面的经验着色模型,见 各向异性各向异性前置知识:反射定律、微表面 BRDF(NDF、粗糙度、半程向量),见 [[Physically Based Rendering]]。 各向异性的原理 保持灯光和相机不动,只旋转物体: - 普通金属不管转到哪个方向,高光形状都差不多,这是各向同性; - 拉丝金属转动后,高光会跟着拉丝方向变化,这是各向异性。 区别不在表面是否粗糙,而在微观结构有没有统一方向。 同一个表面有两个粗糙度 微表面理论中的粗糙度,描述的是微表面法线的分散程度。法线越集中,反射光越汇聚,高光越锐利;法线越发散,反射光越散开,高光越模糊。 随机凹凸表面的微表面法线在各个方位上均匀分布。物体绕法线旋转后,法线分布没有变化,所以只需要一个粗糙度描述——这就是各向同性。 拉丝金属、缎面和头发的微观结构带有方向。沿沟槽或纤维方向,表面变化较小;垂直该方向,表面起伏更明显。于是切平面中的两个方向具有不同的法线分布,也就需要两个粗糙度描述——这就是各向异性。 image.png 高光为什么垂直于沟槽拉长 把一条沟槽看成细圆柱。平行光照到圆柱后,每个命中点的反射方向由该点法线决定。 - 沿圆柱轴线取一排点,

问题:纤维没有唯一法线

Blinn–Phong 高光以表面法线 n\mathbf{n} 为核心:靠半程向量与法线的对齐 nh\mathbf{n}\cdot\mathbf{h} 来定强度。

但一根头发是一条无限细的圆柱纤维,它没有唯一法线——横截面是个圆,圆周上每一点的法线都朝不同方向,构成一整圈垂直于切线的法线。

圆柱表面上,任意一点的切平面里其实有两个方向的切线:

  1. 沿轴切线——顺着纤维走向(发根到发梢)的那条;
  2. 周向切线——绕着横截面圆走的那条(沿圆周)。

Kajiya–Kay 的核心思路:用切线 t\mathbf{t}(沿轴切线)替代法线 n\mathbf{n} 来做着色。 下面推它的高光项——Unity 里用 Kajiya–Kay 主要就是用这一项,漫反射走标准 Lambert / PBR。

image.png

约定:t\mathbf{t} 为单位切线,l\mathbf{l} 指向光源,v\mathbf{v} 指向眼睛,都是单位向量。记 θl\theta_lt\mathbf{t}l\mathbf{l} 的夹角,θv\theta_vt\mathbf{t}v\mathbf{v} 的夹角。

image.png

高光项:从 Blinn–Phong 出发

先回顾 Blinn–Phong 高光。它以半程向量 h=l+v^\mathbf{h}=\widehat{\mathbf{l}+\mathbf{v}} 为核心: Ψspec=ks(nh)p,h=l+v^\Psi_\text{spec} = k_s\,(\mathbf{n}\cdot\mathbf{h})^{p},\qquad \mathbf{h}=\widehat{\mathbf{l}+\mathbf{v}}

下面分三步,把它从「平面」搬到「发丝」。

第一步:Blinn–Phong 的规则——h\mathbf{h} 和法线 n\mathbf{n} 同向时最亮。

镜面反射定律:

光要想正好反射进你眼睛,入射光和视线必须关于法线对称。而"l 和 v 关于法线对称",恰好就等于"它俩的正中间 h 和法线 n 同向"。

所以:h 和 n 同向 → 光正好反射进眼睛 → 高光最亮。 这就是 Blinn–Phong 的核心。h 偏离 n 越多,高光越暗。

image.png

第二步:发丝的法线全都垂直于发丝方向 t\mathbf{t}

  发丝是个圆柱,表面任意一点的法线都从中轴沿半径朝外戳出,而半径方向和中轴方向(发丝方向 t\mathbf{t})天然成直角。所以发丝一圈的法线,每一根都垂直于 t\mathbf{t},合起来就是「垂直于 t\mathbf{t} 的一整圈方向」——像一把圆柱刷子,刷毛全朝侧面戳,没有一根顺着发丝。

第三步:两步合起来——h\mathbf{h} 垂直于 t\mathbf{t} 时最亮。

  第一步要「h\mathbf{h} 和某条法线同向」才最亮;第二步说发丝的法线全都垂直于 t\mathbf{t}。所以 h\mathbf{h} 要和其中某条法线同向,它自己也必须垂直于 t\mathbf{t}。只要 h\mathbf{h} 垂直于 t\mathbf{t},这一圈里就一定有一条法线正好和它同向 → 完美镜面 → 最亮;只要 h\mathbf{h} 有一点沿着 t\mathbf{t} 的分量(不垂直),就对不上任何法线 → 变暗。

发丝的法线只朝侧面(垂直 t\mathbf{t}),所以负责对准反射的 h\mathbf{h} 也只有垂直 t\mathbf{t} 时,才能对上一根、把光反进眼睛。

image.png

写成公式。

从 Blinn–Phong 公式: Ψspec=ks(nh)p\Psi_\text{spec} = k_s\,(\mathbf{n}\cdot\mathbf{h})^{p}

  • 因为:nh=cos(n,h)\mathbf{n}\cdot\mathbf{h} = \cos(\mathbf{n},\mathbf{h})
  • 所以: Ψspec=kscosp(n,h)\Psi_\text{spec} = k_s\,\cos^{p}(\mathbf{n},\mathbf{h})

换到发丝(把 cos\cos 换成 sin\sin):

  • 因为:发丝的法线全都垂直于 t\mathbf{t},高光最亮的条件是 ht\mathbf{h}\perp\mathbf{t}
  • 因为:cos(t,h)\cos(\mathbf{t},\mathbf{h})ht\mathbf{h}\parallel\mathbf{t}=1=1ht\mathbf{h}\perp\mathbf{t}=0=0,方向正好反了;而 sin(t,h)\sin(\mathbf{t},\mathbf{h})ht\mathbf{h}\perp\mathbf{t}=1=1,正合适;
  • 所以:把对齐度量 cos(n,h)\cos(\mathbf{n},\mathbf{h}) 换成 sin(t,h)\sin(\mathbf{t},\mathbf{h}),得 Ψspec=kssinp(t,h)\Psi_\text{spec} = k_s\,\sin^{p}(\mathbf{t},\mathbf{h})

换成点积(shader 好算):

  • 因为:单位向量下 sin2+cos2=1\sin^{2}+\cos^{2}=1,即 sin(t,h)=1cos2(t,h)\sin(\mathbf{t},\mathbf{h})=\sqrt{1-\cos^{2}(\mathbf{t},\mathbf{h})},又 cos(t,h)=th\cos(\mathbf{t},\mathbf{h})=\mathbf{t}\cdot\mathbf{h}
  • 所以:sin(t,h)=1(th)2\sin(\mathbf{t},\mathbf{h})=\sqrt{1-(\mathbf{t}\cdot\mathbf{h})^{2}}

结论:

 Ψspecular=kssinp(t,h)=ks(1(th)2)p \boxed{\ \Psi_\text{specular} = k_s\,\sin^{p}(\mathbf{t},\mathbf{h}) = k_s\left(\sqrt{1-(\mathbf{t}\cdot\mathbf{h})^2}\right)^{p}\ }

float TdotH = dot(T, H);
float sinTH = sqrt(1.0 - TdotH * TdotH);
float spec  = pow(sinTH, exponent);

几何意义:偏移高光。 把峰值条件 ht\mathbf{h}\perp\mathbf{t} 展开: ht    (l+v)t=0    cosθl+cosθv=0    θl+θv=π\mathbf{h}\perp\mathbf{t} \iff (\mathbf{l}+\mathbf{v})\cdot\mathbf{t}=0 \iff \cos\theta_l+\cos\theta_v=0 \iff \theta_l+\theta_v=\pi

高光最亮的位置不在镜像对称处,而是随光照沿纤维滑动——这正是头发那条标志性高光带的来源。

小结与用法

高光只依赖光照/视线方向对切线 t\mathbf{t} 的夹角,与绕纤维的方位无关——这是它对圆柱的核心简化(忽略方位依赖),也是便宜好用的原因。

Unity 里用 Kajiya–Kay 就用这一项做各向异性高光,漫反射另走标准 Lambert / PBR(头发上再叠 wrapped diffuse 模拟半透)。

关于符号的一个坑:不少资料把高光写成 (cosθlcosθv+sinθlsinθv)p=cosp(θlθv)(\cos\theta_l\cos\theta_v + \sin\theta_l\sin\theta_v)^p = \cos^p(\theta_l-\theta_v)(加号)。那是把切线直接当法线套进 Phong 反射公式 r=2(tl)tl\mathbf{r}=2(\mathbf{t}\cdot\mathbf{l})\mathbf{t}-\mathbf{l} 得到的结果,峰值在 θl=θv\theta_l=\theta_v,与上面半程向量写法的偏移高光(峰值在 θl+θv=π\theta_l+\theta_v=\pi)并不一致。看到 ±\pm 号打架时,先确认视线向量的朝向约定。上面用的 sinp(t,h)\sin^{p}(\mathbf{t},\mathbf{h}) 没有这个歧义,推荐用它。

局限

  • 纯经验模型,不含能量守恒、无 Fresnel、不分辨发丝内部的透射与多次散射。想要真实头发(尤其浅色发的通透感、次级高光带 R/TT/TRT),要上 Marschner 模型。
  • 需要沿发丝走向提供切线 t\mathbf{t};发根到发梢的切线要平滑,否则高光会断。
  • 常配合「切线偏移」(shift tangent)做出主/次两条高光带,见 Scheuermann 的实时头发方案。