剖析UE4着色模型源码
前面讲过Marschner着色模型使用了一个基于Bidirectional Scattering Distribution Function(BSDF)的方法来计算细长物体的光学性质。这个BSDF可以分解为M项和N项,其中M项表示沿着纤维方向的反射和透射,N项表示垂直于纤维方向的反射和透射。即对光进行了拆解,但是他的计算仍非常复杂应用在实时渲染不现实,因此我们有必要进行近似逼近化简从而降低开销这也是UE4的做法,其做法就是不断地近似调参直至得到和Marschner着色模型大致的效果,在不保证丢失画面质量的情况下大幅度降低计算开销从而能够使得该基于物理的着色模型 应用于实时渲染中,我们会简单讲解一下对BSDF中不同光路R、TT、TRT的M项和N项逼近方法再从UE4源码出发学习一下他的实现代码。
M项逼近
具体的推导原理过于复杂这里就给出各个项的逼近计算公式,首先不论是R,TT还是TRT他们的M项都是类似的,可以使用高斯分布来进行替代。本身Mp的计算应如下计算这也是我们最后推导出来的改进版:
M p ( θ i , θ r ) = ( 1 v e 2 v − v ) e 1 − sin θ i sin θ r v I 0 ( cos θ i cos θ r v ) v = β p 2 \begin{aligned}
& M_p\left(\theta_i, \theta_r\right)=\left(\frac{1}{v e^{\frac{2}{v}}-v}\right) e^{\frac{1-\sin \theta_i \sin \theta_r}{v}} I_0\left(\frac{\cos \theta_i \cos \theta_r}{v}\right) \\
& \boldsymbol{v}=\beta_p^2
\end{aligned}
M p ( θ i , θ r ) = ( v e v 2 − v 1 ) e v 1 − s i n θ i s i n θ r I 0 ( v cos θ i cos θ r ) v = β p 2
但是这样的话计算开销过大,所以简化用高斯替代:
M p ( θ i , θ r ) ≈ 1 β n 2 π e − ( sin θ i + sin θ r − α p ) 2 2 β p 2 M_p\left(\theta_i, \theta_r\right) \approx \frac{1}{\boldsymbol{\beta}_n \sqrt{2 \pi}} e^{-\frac{\left(\sin \theta_i+\sin \theta_r-\alpha_p\right)^2}{2 \beta_p{ }^2}}
M p ( θ i , θ r ) ≈ β n 2 π 1 e − 2 β p 2 ( s i n θ i + s i n θ r − α p ) 2
其中上式有一个alpha值,R,TT和TRT分别用不同的关于shift函数值,具体格式为
1 2 3 4 5 6 7 float Shift = 0.035 ;float Alpha[] = { -Shift * 2 , Shift, Shift * 4 , };
而其中的β会与粗糙度有关,因此最终Mp的计算code为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 float Shift = 0.035 ;float Alpha[] = { -Shift * 2 , Shift, Shift * 4 , };float B[] = { Area + Pow2(ClampedRoughness), Area + Pow2(ClampedRoughness) / 2 , Area + Pow2(ClampedRoughness) * 2 , };float Hair_g (float B, float Theta) { return exp (-0.5 * Pow2(Theta) / (B * B)) / (sqrt (2 * PI) * B); }float Mp = Hair_g( B[0 ] , SinThetaL + SinThetaV - Alpha[0 ]);float Mp = Hair_g( B[1 ], SinThetaL + SinThetaV - Alpha[1 ] );float Mp = Hair_g( B[2 ], SinThetaL + SinThetaV - Alpha[2 ] );
当然为了得到更真实的效果,我们可以将占贡献主体的R的M项使用原公式,只对TT,TRT进行M项逼近。
N项的逼近
M项逼近很容易,但是N项却不同,不同光的N项公式并不相同有的简单有的复杂。这里我们首先看一下思路:
R的N项逼近:当光线与细长物体垂直相交时,即光线方向与纤维方向相垂直时,可以使用R逼近方法计算N项。R逼近基于Fresnel反射定律,计算垂直方向的反射光线强度。由于这种情况下没有透射光线,因此只需要计算反射光线的强度。
TT的N项逼近:当光线与细长物体平行相交时,即光线方向与纤维方向平行时,可以使用TT逼近方法计算N项。TT逼近基于透射光线的衰减模型,计算光线从一个纤维进入另一个纤维的透射光线的强度。由于这种情况下没有反射光线,因此只需要计算透射光线的强度。
TRT的N项逼近:当光线与细长物体斜向相交时,即光线方向既不与纤维方向平行也不与纤维方向垂直时,可以使用TRT逼近方法计算N项。TRT逼近基于透射-反射-透射(transmission-reflection-transmission)的光线路径,计算光线在细长物体中传播的强度。这种情况下既有反射光线又有透射光线,因此需要同时计算反射和透射光线的强度。
R Path的N项逼近
如果直接计算R的N项,那么计算公式为:
N r ( θ i , θ r , ϕ r ) = A r ( θ i , ϕ r ) 4 cos θ i ( 1 cos θ i + 1 cos θ r ) N_r(\theta_i,\theta_r,\phi_r) = \frac{A_r(\theta_i,\phi_r)}{4\cos{\theta_i}}\left(\frac{1}{\cos{\theta_i}} + \frac{1}{\cos{\theta_r}}\right)
N r ( θ i , θ r , ϕ r ) = 4 cos θ i A r ( θ i , ϕ r ) ( cos θ i 1 + cos θ r 1 )
因为计算过于复杂所以我们可以使用R逼近方法计算N项。R逼近基于Fresnel反射定律,计算垂直方向的反射光线强度。具体来说,R的N项逼近公式为:
N R ( θ i , θ r , ϕ ) = ( 1 4 cos ϕ 2 ) A ( 0 , h ) N_R\left(\theta_i, \theta_r, \phi\right)=\left(\frac{1}{4} \cos \frac{\phi}{2}\right) A(0, h)
N R ( θ i , θ r , ϕ ) = ( 4 1 cos 2 ϕ ) A ( 0 , h )
对于R项的N项逼近公式N R ( θ i , θ r , ϕ ) = ( 1 4 cos ϕ 2 ) A ( 0 , h ) N_R\left(\theta_i, \theta_r, \phi\right)=\left(\frac{1}{4} \cos \frac{\phi}{2}\right) A(0, h) N R ( θ i , θ r , ϕ ) = ( 4 1 cos 2 ϕ ) A ( 0 , h ) ,其中,θ i \theta_i θ i 和θ r \theta_r θ r 分别为入射光和反射光的夹角,ϕ \phi ϕ 表示入射光和反射光的平面角度差即方位角度差,h h h 表示视线方向和入射光/反射光的平均方向的中间向量,A ( 0 , h ) A(0,h) A ( 0 , h ) 表示由中间向量h h h 计算出的散射光强度。
公式中的( 1 4 cos ϕ 2 ) \left(\frac{1}{4} \cos \frac{\phi}{2}\right) ( 4 1 cos 2 ϕ ) 是一个常数项,表示在入射角和反射角一定的情况下,散射光的强度随着平面角度差的增大而逐渐减小:
cos ϕ 2 = 1 2 + 1 2 cos ϕ \cos \frac{\phi}{2}=\sqrt{\frac{1}{2}+\frac{1}{2} \cos \phi}
cos 2 ϕ = 2 1 + 2 1 cos ϕ
A ( 0 , h ) A(0, h) A ( 0 , h ) 则是中间向量h h h 的函数,表示散射光随着中间向量的不同而发生变化。在实际应用中,A ( 0 , h ) A(0, h) A ( 0 , h ) 通常会采用一些高效的近似算法来进行计算。这里我们采用如下公式进行近似:
A ( 0 , h ) = F ( η , 1 2 + 1 2 ( ω i ⋅ ω r ) ) F ( η , x ) = F 0 + ( 1 − F 0 ) ( 1 − x ) 5 F 0 = ( 1 − η ) 2 ( 1 + η ) 2 A(0, h)=F\left(\eta, \sqrt{\frac{1}{2}+\frac{1}{2}\left(\omega_i \cdot \omega_r\right)}\right)
\begin{aligned}
& F(\eta, x)=F_0+\left(1-F_0\right)(1-x)^5 \\
& F_0=\frac{(1-\eta)^2}{(1+\eta)^2}
\end{aligned}
A ( 0 , h ) = F ( η , 2 1 + 2 1 ( ω i ⋅ ω r ) ) F ( η , x ) = F 0 + ( 1 − F 0 ) ( 1 − x ) 5 F 0 = ( 1 + η ) 2 ( 1 − η ) 2
因此结合R的N项逼近和M项计算记得R的BSDF代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 float F (n, x) { F0=pow ((1 -n)/1 +n),2 ); return F0 + (1 - F0) * pow ((1 - x), 5 ); } vec3 wi, wr;float Mp = Hair_g(B[0 ] * BScale, SinThetaL + SinThetaV - Alpha[0 ]);float CosHalfPhi = sqrt (0.5 + 0.5 * cos (phi));float A = F(n, sqrt (0.5 + 0.5 * (dotProduct(wi, wr))));float Np = 0.25 * CosHalfPhi * A; s+=Mp*Np;
TT Path的N项逼近
我们在前篇文章给出过N项的公式:
A ( 0 , h ) = F ( η , 1 2 arccos ( ω j ⋅ ω r ) ) A ( p , h ) = ( 1 − f ) 2 f p − 1 T ( μ a ′ , h ) p N p ( ϕ ) = 1 2 ∫ − 1 1 A ( p , h ) D p ( ϕ − Φ ( p , h ) ) d h \begin{aligned}
&A(0, h)=F(η, \frac{1}{2} \arccos (\omega_j \cdot\omega_r))\\
&A(p, h)=(1-f)^2 f^{p-1} T\left(\mu_a{ }^{\prime}, h\right)^p
\end{aligned}\\
N_p(\phi)=\frac{1}{2} \int_{-1}^1 A(p, h) D_p(\phi-\Phi(p, h)) d h
A ( 0 , h ) = F ( η , 2 1 arccos ( ω j ⋅ ω r ) ) A ( p , h ) = ( 1 − f ) 2 f p − 1 T ( μ a ′ , h ) p N p ( ϕ ) = 2 1 ∫ − 1 1 A ( p , h ) D p ( ϕ − Φ ( p , h ) ) d h
为了能够实现实时计算,我们需要进行简化来减少计算开销,因此我们主要是针对h,η \eta η ,D,A计算公式进行拟合。
首先h的初始原计算公式如下:
a = 1 η ′ h T T = sign ( ϕ ) cos ϕ 2 1 + a 2 − 2 a sign ( ϕ ) sin ϕ 2 \begin{aligned}
& a=\frac{1}{\eta^{\prime}} \\
& h_{T T}=\frac{\operatorname{sign}(\phi) \cos \frac{\phi}{2}}{\sqrt{1+a^2-2 a \operatorname{sign}(\phi) \sin \frac{\phi}{2}}} \\
&
\end{aligned}
a = η ′ 1 h T T = 1 + a 2 − 2 a s i g n ( ϕ ) sin 2 ϕ s i g n ( ϕ ) cos 2 ϕ
h T T 2 = 1 2 + 1 2 cos ϕ 1 + a 2 − 2 a 1 2 − 1 2 cos ϕ h_{T T}{ }^2=\frac{\frac{1}{2}+\frac{1}{2} \cos \phi}{1+a^2-2 a \sqrt{\frac{1}{2}-\frac{1}{2} \cos \phi}}
h T T 2 = 1 + a 2 − 2 a 2 1 − 2 1 cos ϕ 2 1 + 2 1 cos ϕ
但是还是过于复杂所以使用如下函数近似逼近:
h T T ≈ ( 1 + a ( 0.6 − 0.8 cos ϕ ) cos ϕ 2 ) . h_{T T} \approx(1+a(0.6-0.8 \cos \phi) \cos \frac{\phi}{2}).
h T T ≈ ( 1 + a ( 0 . 6 − 0 . 8 cos ϕ ) cos 2 ϕ ) .
同时对于η ′ \eta' η ′ 修正折射率是基于原始材质折射率η \eta η 计算得出,因此为了简化我们这里规定将η \eta η 使用固定的1.55计算,同时使用线性拟合代替元计算方程:
原始计算公式
η ′ = η 2 − sin 2 θ d cos θ d \eta^{\prime}=\frac{\sqrt{\eta^2-\sin ^2 \theta_d}}{\cos \theta_d}
η ′ = cos θ d η 2 − sin 2 θ d
近似计算公式
η = 1.55 η ′ ≈ 1.19 cos θ d + 0.36 cos θ d 误差: error < 0.68 % \begin{aligned}
& \eta=1.55 \\
& \eta^{\prime} \approx \frac{1.19}{\cos \theta_d}+0.36 \cos \theta_d \\
& 误差:\text { error }<0.68 \%
\end{aligned}
η = 1 . 5 5 η ′ ≈ cos θ d 1 . 1 9 + 0 . 3 6 cos θ d 误 差 : error < 0 . 6 8 %
而对于A的优化我们主要是需要优化A(p,h)中的吸收因子计算公式,其原始公式为:
T ( θ , ϕ ) = exp ( − 2 μ a 1 + cos ( 2 γ t ) cos θ t ) γ t = sin − 1 ( h η ′ ) \begin{aligned}
& T(\theta, \phi)=\exp \left(-2 \mu_a \frac{1+\cos \left(2 \gamma_t\right)}{\cos \theta_t}\right) \\
& \gamma_t=\sin ^{-1}\left(\frac{h}{\eta^{\prime}}\right)
\end{aligned}
T ( θ , ϕ ) = exp ( − 2 μ a cos θ t 1 + cos ( 2 γ t ) ) γ t = sin − 1 ( η ′ h )
其中,μ a \mu_a μ a 表示头发的吸收系数,θ t \theta_t θ t 表示入射光线与头发法线的夹角,γ t \gamma_t γ t 表示头发内部的光线传播角度。
这里选择使用Pixar’s absorption尝试简化:
T ( θ , ϕ ) = exp ( − ζ ( C ) cos γ t cos θ d ) T(\theta, \phi)=\exp \left(-\zeta(C) \frac{\cos \gamma_t}{\cos \theta_d}\right)
T ( θ , ϕ ) = exp ( − ζ ( C ) cos θ d cos γ t )
简化后这里的ζ ( C ) \zeta(C) ζ ( C ) 是一个根据吸收系数μ a \mu_a μ a 和头发曲率半径C C C 计算得到的常数,一般在渲染前已经确定。但是计算公式仍较为复杂,所以在UE4中进一步优化近似如下:
T ( θ , ϕ ) = C 1 − h 2 a 2 2 cos θ d T(\theta, \phi)=C^{\frac{\sqrt{1-h^2 a^2}}{2 \cos \theta_d}}
T ( θ , ϕ ) = C 2 c o s θ d 1 − h 2 a 2
其中a和C仍是根据μ a \mu_a μ a 计算得到的常数,在渲染前确定好,自此T不再是一个复杂的指数计算公式同时减少了复杂的三角运算,从而整体上降低了A的计算开销。
最后我们同样优化一下D,实际上D在原论文中应该是接收三个参数的
D ( ϕ , s , μ ) = e ϕ − μ s s ( 1 + e ϕ − μ s ) 2 D(\phi, s, \mu)=\frac{e^{\frac{\phi-\mu}{s}}}{s\left(1+e^{\frac{\phi-\mu}{s}}\right)^2}
D ( ϕ , s , μ ) = s ( 1 + e s ϕ − μ ) 2 e s ϕ − μ
ϕ \phi ϕ :单位弧度长度上的横截面积分(cross-sectional area per unit length),表示在弧度长度上纤维横截面积的总和。
s s s :控制密度分布的参数,被称为“形状参数”(shape parameter),它越小,密度分布越集中,越大,密度分布越均匀。
μ \mu μ :密度分布的平均值,表示纤维横截面积的平均大小。
但是计算开销太大,因此我们这里考虑到有些因素影响较小因此我们把一些参数设置为常用的常量:
D T T ( ϕ ) = D ( ϕ , 0.35 , π ) D T T ( ϕ ) ≈ e − 3.65 cos ϕ − 3.98 \begin{aligned}
& D_{T T}(\phi)=D(\phi, 0.35, \pi) \\
& D_{T T}(\phi) \approx e^{-3.65 \cos \phi-3.98}
\end{aligned}
D T T ( ϕ ) = D ( ϕ , 0 . 3 5 , π ) D T T ( ϕ ) ≈ e − 3 . 6 5 c o s ϕ − 3 . 9 8
自此TT的N项逼近就大致完成了。
TRT Path的N项逼近
对于TRT我们将h设置为了常数且不包含η \eta η 了,因此它的吸收项T也可以大幅简化,对于D我们使用高斯分布近似:
h T R T = 3 2 h_{T R T}=\frac{\sqrt{3}}{2}
h T R T = 2 3
T T R T ( θ , ϕ ) = C 0.8 cos θ d T_{T R T}(\theta, \phi)=C^{\frac{0.8}{\cos \theta_d}}
T T R T ( θ , ϕ ) = C c o s θ d 0 . 8
D T R T ( ϕ ) = 3 4 D ( ϕ , 0.15 , 0 ) D T R T ( ϕ ) ≈ e 17 cos ϕ − 16.78 \begin{aligned}
D_{T R T}(\phi) & =\frac{3}{4} D(\phi, 0.15,0) \\
D_{T R T}(\phi) & \approx e^{17 \cos \phi-16.78}
\end{aligned}
D T R T ( ϕ ) D T R T ( ϕ ) = 4 3 D ( ϕ , 0 . 1 5 , 0 ) ≈ e 1 7 c o s ϕ − 1 6 . 7 8
自此我们就完成了所有光路的M项和N项逼近,实际上就是基于大量经验调整和函数逼近尽可能在降低开销的情况下得到和原公式差不多的结果,并且仍符合基于物理的特性,以下是最终的对比图我们可以看到效果不错:
虽然近似效果已经很好,但是UE4还又在最后引入了一个多散射近似,用来模拟头发散乱、多时对光线造成的多散射情况,可以看到透光性更强烈,如下所示,我们会在代码中看到其实现:
UE4 HairShading代码剖析
首先思路也是从GBuffer中获取头发的一些属性参数,然后使用封装好的BSDF结合渲染方程进行渲染,这里我们重点分析一下BSDF的封装,即如何实现对BSDF的M项和N项拆解以及近似的。
注意,虽然近似思路大致相同,但是具体的代码实现可能略有出入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 float Hair_g (float B, float Theta) { return exp (-0.5 * Pow2(Theta) / (B * B)) / (sqrt (2 * PI) * B); }float GaussianDetector (float Bp, float Phi) { float Dp = 0 ; for (int k = -4 ; k <= 4 ; k++) { Dp += Hair_g(Bp, Phi - (2 * PI) * k); } return Dp; }float Hair_F (float CosTheta) { const float n = 1.55 ; const float F0 = Pow2((1 - n) / (1 + n)); return F0 + (1 - F0) * Pow5(1 - CosTheta); } float3 KajiyaKayDiffuseAttenuation (FGBufferData GBuffer, float3 L, float3 V, half3 N, float Shadow) { float KajiyaDiffuse = 1 - abs (dot(N, L)); float3 FakeNormal = normalize(V - N * dot(V, N)); N = FakeNormal; float Wrap = 1 ; float NoL = saturate((dot(N, L) + Wrap) / Square(1 + Wrap)); float DiffuseScatter = (1 / PI) * lerp(NoL, KajiyaDiffuse, 0.33 ) * GBuffer.Metallic; float Luma = Luminance(GBuffer.BaseColor); float3 ScatterTint = pow (GBuffer.BaseColor / Luma, 1 - Shadow); return sqrt (GBuffer.BaseColor) * DiffuseScatter * ScatterTint; } float3 EvaluateHairMultipleScattering ( const FHairTransmittanceData TransmittanceData, const float Roughness, const float3 Fs) { return TransmittanceData.GlobalScattering * (Fs + TransmittanceData.LocalScattering) * TransmittanceData.OpaqueVisibility; }#define HAIR_REFERENCE 0 #if HAIR_REFERENCE struct FHairTemp { float SinThetaL; float SinThetaV; float CosThetaD; float CosThetaT; float CosPhi; float CosHalfPhi; float VoL; float n_prime; }; float I0 (float x) { x = abs (x); float a; if (x < 3.75 ) { float t = x / 3.75 ; float t2 = t * t; a = +0.0045813 ; a = a * t2 + 0.0360768 ; a = a * t2 + 0.2659732 ; a = a * t2 + 1.2067492 ; a = a * t2 + 3.0899424 ; a = a * t2 + 3.5156229 ; a = a * t2 + 1.0 ; } else { float t = 3.75 / x; a = +0.00392377 ; a = a * t - 0.01647633 ; a = a * t + 0.02635537 ; a = a * t - 0.02057706 ; a = a * t + 0.00916281 ; a = a * t - 0.00157565 ; a = a * t + 0.00225319 ; a = a * t + 0.01328592 ; a = a * t + 0.39894228 ; a *= exp (x) * rsqrt(x); } return a; } float LongitudinalScattering (float B, float SinThetaL, float SinThetaV) { float v = B * B; float CosThetaL2 = 1 - SinThetaL * SinThetaL; float CosThetaV2 = 1 - SinThetaV * SinThetaV; float Mp = 0 ; if (v < 0.1 ) { float a = sqrt (CosThetaL2 * CosThetaV2) / v; float b = -SinThetaL * SinThetaV / v; float logI0a = a > 12 ? a + 0.5 * (-log (2 * PI) + log (1 / a) + 0.125 / a) : log (I0(a)); Mp = exp (logI0a + b - rcp(v) + 0.6931 + log (0.5 / v)); } else { Mp = rcp(exp (2 / v) * v - v) * exp ((1 - SinThetaL * SinThetaV) / v) * I0(sqrt (CosThetaL2 * CosThetaV2) / v); } return Mp; } float GaussianDetector (float Bp, float Phi) { float Dp = 0 ; for (int k = -4 ; k <= 4 ; k++) { Dp += Hair_g(Bp, Phi - (2 * PI) * k); } return Dp; } float3 Attenuation (uint p, float h, float3 Color, FHairTemp HairTemp) { float3 A; if (p == 0 ) { A = Hair_F(sqrt (0.5 + 0.5 * HairTemp.VoL)); } else { float3 Sigma_ae = { 0.419 , 0.697 , 1.37 }; float3 Sigma_ap = { 0.187 , 0.4 , 1.05 }; float3 ua = -0.25 * log (Color); float3 ua_prime = ua / HairTemp.CosThetaT; float yi = asin (h); float yt = asin (h / HairTemp.n_prime); float f = Hair_F(HairTemp.CosThetaD * sqrt (1 - h * h)); float3 T = exp (-2 * ua_prime * cos (yt)); if (p == 1 ) A = Pow2(1 - f) * T; else A = Pow2(1 - f) * f * T * T; } return A; } float Omega (uint p, float h, FHairTemp HairTemp) { float yi = asin (h); float yt = asin (h / HairTemp.n_prime); return 2 * p * yt - 2 * yi + p * PI; } float3 AzimuthalScattering (uint p, float Bp, float3 Color, FHairTemp HairTemp, uint2 Random) { float Phi = acos (HairTemp.CosPhi); float Offset = float (Random.x & 0xffff ) / (1 << 16 ); uint Num = 16 ; float3 Np = 0 ; for (uint i = 0 ; i < Num; i++) { float h = ((float )(i + Offset) / Num) * 2 - 1 ; Np += Attenuation(p, h, Color, HairTemp) * GaussianDetector(Bp, Phi - Omega(p, h, HairTemp)); } Np *= 2.0 / Num; return 0.5 * Np; } float3 HairShadingRef (FGBufferData GBuffer, float3 L, float3 V, half3 N, uint2 Random, uint HairComponents) { float ClampedRoughness = clamp(GBuffer.Roughness, 1 / 255.0f , 1.0f ); float n = 1.55 ; FHairTemp HairTemp; HairTemp.VoL = dot(V, L); HairTemp.SinThetaL = dot(N, L); HairTemp.SinThetaV = dot(N, V); HairTemp.CosThetaT = sqrt (1 - Pow2((1 / n) * HairTemp.SinThetaL)); HairTemp.CosThetaD = cos (0.5 * abs (asin (HairTemp.SinThetaV) - asin (HairTemp.SinThetaL))); float3 Lp = L - HairTemp.SinThetaL * N; float3 Vp = V - HairTemp.SinThetaV * N; HairTemp.CosPhi = dot(Lp, Vp) * rsqrt(dot(Lp, Lp) * dot(Vp, Vp)); HairTemp.CosHalfPhi = sqrt (0.5 + 0.5 * HairTemp.CosPhi); HairTemp.n_prime = sqrt (n * n - 1 + Pow2(HairTemp.CosThetaD)) / HairTemp.CosThetaD; float Shift = 0.035 ; float Alpha[] = { -Shift * 2 , Shift, Shift * 4 , }; float B[] = { Pow2(ClampedRoughness), Pow2(ClampedRoughness) / 2 , Pow2(ClampedRoughness) * 2 , }; float3 S = 0 ; UNROLL for (uint p = 0 ; p < 3 ; p++) { if (p == 0 && (HairComponents & HAIR_COMPONENT_R) == 0 ) continue ; if (p == 1 && (HairComponents & HAIR_COMPONENT_TT) == 0 ) continue ; if (p == 2 && (HairComponents & HAIR_COMPONENT_TRT) == 0 ) continue ; float SinThetaV = HairTemp.SinThetaV; float Bp = B[p]; if (p == 0 ) { Bp *= sqrt (2.0 ) * HairTemp.CosHalfPhi; float sa, ca; sincos(Alpha[p], sa, ca); SinThetaV -= 2 * sa * (HairTemp.CosHalfPhi * ca * sqrt (1 - SinThetaV * SinThetaV) + sa * SinThetaV); } else { SinThetaV = sin (asin (SinThetaV) - Alpha[p]); } float Mp = LongitudinalScattering(Bp, HairTemp.SinThetaL, SinThetaV); float3 Np = AzimuthalScattering(p, B[p], GBuffer.BaseColor, HairTemp, Random); float3 Sp = Mp * Np; S += Sp; } return S; }#endif float3 HairShading ( FGBufferData GBuffer, float3 L, float3 V, half3 N, float Shadow, FHairTransmittanceData HairTransmittance, float InBacklit, float Area, uint2 Random ) { float ClampedRoughness = clamp(GBuffer.Roughness, 1 /255.0f , 1.0f ); const float Backlit = min(InBacklit, HairTransmittance.bUseBacklit ? GBuffer.CustomData.z : 1 ); #if HAIR_REFERENCE float3 S = HairShadingRef( GBuffer, L, V, N, Random ); #else const float VoL = dot(V,L); const float SinThetaL = clamp(dot(N,L), -1.f , 1.f ); const float SinThetaV = clamp(dot(N,V), -1.f , 1.f ); float CosThetaD = cos ( 0.5 * abs ( asinFast( SinThetaV ) - asinFast( SinThetaL ) ) ); const float3 Lp = L - SinThetaL * N; const float3 Vp = V - SinThetaV * N; const float CosPhi = dot(Lp,Vp) * rsqrt( dot(Lp,Lp) * dot(Vp,Vp) + 1e-4 ); const float CosHalfPhi = sqrt ( saturate( 0.5 + 0.5 * CosPhi ) ); float n = 1.55 ; float n_prime = 1.19 / CosThetaD + 0.36 * CosThetaD; float Shift = 0.035 ; float Alpha[] = { -Shift * 2 , Shift, Shift * 4 , }; float B[] = { Area + Pow2(ClampedRoughness), Area + Pow2(ClampedRoughness) / 2 , Area + Pow2(ClampedRoughness) * 2 , }; float3 S = 0 ; if (HairTransmittance.ScatteringComponent & HAIR_COMPONENT_R) { const float sa = sin (Alpha[0 ]); const float ca = cos (Alpha[0 ]); float Shift = 2 * sa * (ca * CosHalfPhi * sqrt (1 - SinThetaV * SinThetaV) + sa * SinThetaV); float BScale = HairTransmittance.bUseSeparableR ? sqrt (2.0 ) * CosHalfPhi : 1 ; float Mp = Hair_g(B[0 ] * BScale, SinThetaL + SinThetaV - Shift); float Np = 0.25 * CosHalfPhi; float Fp = Hair_F(sqrt (saturate(0.5 + 0.5 * VoL))); S += Mp * Np * Fp * (GBuffer.Specular * 2 ) * lerp(1 , Backlit, saturate(-VoL)); } if (HairTransmittance.ScatteringComponent & HAIR_COMPONENT_TT) { float Mp = Hair_g( B[1 ], SinThetaL + SinThetaV - Alpha[1 ] ); float a = 1 / n_prime; float h = CosHalfPhi * ( 1 + a * ( 0.6 - 0.8 * CosPhi ) ); float f = Hair_F( CosThetaD * sqrt ( saturate( 1 - h*h ) ) ); float Fp = Pow2(1 - f); float3 Tp = 0 ; if (HairTransmittance.bUseLegacyAbsorption) { Tp = pow (GBuffer.BaseColor, 0.5 * sqrt (1 - Pow2(h * a)) / CosThetaD); } else { const float3 AbsorptionColor = HairColorToAbsorption(GBuffer.BaseColor); Tp = exp (-AbsorptionColor * 2 * abs (1 - Pow2(h * a) / CosThetaD)); } float Np = exp ( -3.65 * CosPhi - 3.98 ); S += Mp * Np * Fp * Tp * Backlit; } if (HairTransmittance.ScatteringComponent & HAIR_COMPONENT_TRT) { float Mp = Hair_g( B[2 ], SinThetaL + SinThetaV - Alpha[2 ] ); float f = Hair_F( CosThetaD * 0.5 ); float Fp = Pow2(1 - f) * f; float3 Tp = pow ( GBuffer.BaseColor, 0.8 / CosThetaD ); float Np = exp ( 17 * CosPhi - 16.78 ); S += Mp * Np * Fp * Tp; } #endif if (HairTransmittance.ScatteringComponent & HAIR_COMPONENT_MULTISCATTER) { S = EvaluateHairMultipleScattering(HairTransmittance, ClampedRoughness, S); S += KajiyaKayDiffuseAttenuation(GBuffer, L, V, N, Shadow); } S = -min(-S, 0.0 ); return S; }