最近研究头发实时渲染,发现一篇比较好的文章,因此翻译出来,一方面增强自己对原论文的理解,另一方面希望能对需要的人有所帮助。原论文传送门。我翻译水平一般,尽可能认真严肃的翻译,如有错误,还望提醒。
PartI
我(原作者)决定分三部分尽可能详细讲解Marschner论文《Light Scattering from Human Hair Fibers》中研究的内容。
首先,我得提醒你为了理解论文,你必须具备一定的物理学和数学背景而不是shader语法本身。比如原论文经常提到 Snell’s law (我理解是菲涅尔定律)或者probability density functions(概率分布函数)。
Marschner提出的模型主要优点是基于光线通过头发纤维时发生的真实的物理现象。所以我们先学习一下头发丝在电镜下的样子。
将每一根头发丝当成半透明的圆柱,得到以下模型:
模型显示头发上独特且非常明显的反射是R,TT和TRT。
R——光线从头发丝表面反弹给观察者
TT——光线折射进头发然后折射给观察者
TRT——光线折射进头发丝,再内部反射后最终折射给观察者
在整个论文中使用的符号是基于切线空间的(我以前就在坐标系上面吃过亏)。
这里是Marschner头发渲染模型所需的所有变量:
u—头发的切线方向,方向是从头发根部到顶部。
w—头发的法线方向,面向观察者。
v—头发的次法线方向,基于v和w计算出来的右手坐标系方向(这点我弄错了)。v-w是法平面。
wi—光照方向。
wr—摄像机(观察者)方向(有待验证,原论文是散射方向)。
θi,θr—与法平面的倾角(0°垂直于头发,u为Pi,-u为-Pi(这里的Pi应该指90°))。
φi,φr—围绕头发的方位角(measured so that v is 0 and w is +PI)。
还有几个被用到的参数:
θd = (θr-θi)/2 —差分方位角
φd = (φr-φi)/2—相对方位
θh = (θi+θr)/2—半角
φh = (φi+φr)/2—半角
当然,还有一些头发丝的常量参数,这些参数可以在原论文的Table1(第8页)找到。
心中有了这些后,我们能近似得到以下头发光照模型:
S = SR + STT + STRT,
Sp = Mp (q i , q r ) x Np (q d , f d ) for P = R, TT, TRT
接下来我们只要找到谁是M和N。
PartII
在上节中我提到在Marschner的论文中被提到的两个函数
S = SR + STT + STRT,
Sp = Mp (q i , q r ) x Np (q d , f d ) for P = R, TT, TRT
M部分
MR (θh) = g( Beta R , θh – Alpha R).
MTT (θh) = g( Beta TT , θh – Alpha TT).
MTRT (θh ) = g( Beta TRT , θh – Alpha TRT).
N部分
转化为Miller-Bravais index。
解决cubic equation(立方体函数)
解决菲涅尔方程
吸收因子
衰减因子
N部分(最终版)
NR (q d , f d ) = NP (0, q d , f d ).
NTT (q d , f d ) = NP (1, q d , f d ).
NTRT (q d , f d ) = NP (2, q d , f d ).
在最后部分,Marschner为了避免奇异情况,建议了一个更复杂的计算模型。但我实现下来看,我不能说这有多大的帮助。所以我坚持原始的NTRT算法。
整个模型
PartIII
这是Marschner Shader的最后一部分。我将介绍如何有效的处理这个光照模型,怎么增加环境光和漫反射光,并且在最后一部分我也将提供生成Marschner查找贴图的代码资源和展示一个我在CS中的效果。
查找贴图
由于在shader中要进行大量的运算,最佳的运算就是使用查找贴图,进而尽可能的减少刷新。
我们很容易注意到Marschner的论文中,除了定义的常量,M函数值取决于θi和θr,N函数取决于θd和φd。尽管这或许在开始是一个好的优化,考虑到所有角度必须是从反三角函数中运算得到,比如acos和asin,这些都不是一开始就能得到的,因此直接通过cos和sin建立索引贴图听起来是个不错的想法。
所有角度上的正弦值和余弦值获取方法都能在GPU Gems2上被找到,第23章:
- sin q i = (light · Tangent),
- sin q o = (eye · Tangent).
- lightPerp = light – (light · tangent) x tangent,
- eyePerp = eye – (eye · tangent) x tangent.
- cos f d = (eyePerp · lightPerp) x ((eyePerp · eyePerp) x (lightPerp · lightPerp))-0.5
创建这两张图的最简单方法是生成一张M函数贴图和N函数贴图(包含Mr,Mtt,Mtrt和θd)。然而在原始论文中Ntt和Ntrt都只有三个通道,但我们考虑到吸收率也有一个通道,我们就能简化到只有一个通道( but they can be reduced to only one channel if we consider the absorption to have one channel as well.)。
环境光和漫反射光
Marschner光照模型只是指定了灯光的反射部分,所以为了获得好的视觉效果,我们为这模型增加环境光和漫反射光。
我使用的灯光来自于Nalu Demo,更详细的介绍:传送门
/* Compute diffuse lighting with phi-dependent component */ float diffuse = sqrt(max(0.0001, 1 - uv1.x * uv1.x)); /* Pass colors */ float4 diffuseColor; diffuseColor.rgb = diffuse * objColor.rgb * DiffuseCol; diffuseColor.a = objColor.a; float3 ambientColor; ambientColor = objColor.rgb * AmbientCol; float3 lighting = (( M.r * N.r + M.g * N.g + M.b * N.b ) / (cos_qd * cos_qd)); lighting += diffuseColor.rgb; OUT.xyz = lighting + diffuseColor.rgb * 0.2 + IN.AmbientColor;