【浅入浅出】Unity 布料渲染
在unity教程中对于布料这块的渲染好像并不多,网上能搜到的也是寥寥,因为目前大多数的基于物理的材质都是用BRDF进行渲染的,但是传统的BRDF是建立在微表面理论,但是对于微表面不能当成完美镜面的,传统的BRDF并不能很好的模拟,所以我们需要找到一个能“模拟”布料的BRDF公式。在下面实现的代码都是基于以下的公式:
diffuse项是DisneyDiffuse 项:
https://www.zhihu.com/equation?tex=%5Cbegin%7Bequation%7D+f_%7Bd%7D+%3D+%5Cfrac%7BbaseColor%7D%7B%5Cpi%7D%281+%2B+%28F_%7BD90%7D-1%29%28%7B1-cos%5Ctheta_l%7D%5E%7B5%7D+%29%29%281+%2B+%28F_%7BD90%7D-1%29%28%7B1-cos%5Ctheta_v%7D%5E%7B5%7D+%29%29%5Cend%7Bequation%7D
其中, https://www.zhihu.com/equation?tex=F_%7BD90%7D%3D0.5%2B2DroughnessDcos%5E2%5Ctheta_d
在UnityStandardBRDF中已经为我们实现好了,我们直接拿来用:
half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness);如果我们要为其添加次表面散射,则需要用到如下公式:
https://www.zhihu.com/equation?tex=f_+%3D+%5Cfrac%7BbaseColor%7D%7B%5Cpi%7D%281+-+F%28v%2Ch%29%29+%5Cleft%3C+++%5Cfrac%7Bn+%5Ccdot+l%2Bw%7D%7B%281+%2B+w%29%5E2%7D+%5Cright%3E+%5Cleft%3C+c_%7Bsubsurface%7D+%2B+n+%5Ccdot+l+%5Cright%3E
其中w是一个0-1之间的值,用来控制散射的柔和程度,实际上就是WrapLighting,
代码:
inline float Wrap(float nl, float w) {
return saturate((nl + w) / (1.0 + w)*(1.0 + w));
}
diffuseTerm *= Wrap(nl,_FabricScatterSale);
half3 Fd = diffColor * diffuseTerm * saturate(_FabricScatterColor + nl);这样Fd就算好了,接着就是specular项:
这里的公式和Cook-Torrance公式有点不一样,首先是D项:
在Physically Based Rendering in Filament 种他提到了两种模型,一种是Ashikhmin模型,他们认为在衣物的渲染中distribution占了很大的比重并且建立在这样的假设上:遮罩带来的影响为0。模型本身是一个逆高斯函数,并且当添加偏移后能很好的灯光对毛绒的高光的模拟,这里偏移为1:
https://www.zhihu.com/equation?tex=D_%7Bvelvet%7D%28v%2Ch%2C%5Calpha%29+%3D+%5Cfrac%7B1%7D%7B%5Cpi%281+%2B+4%5Calpha%5E2%29%7D+%281+%2B+4+%5Cfrac%7Bexp%5Cleft%28%5Cfrac%7B-%7Bcot%7D%5E2%5Ctheta_%7Bh%7D%7D%7B%5Calpha%5E2%7D%5Cright%29%7D%7B%7Bsin%7D%5E4%5Ctheta_%7Bh%7D%7D%29+
第二个模型为Charlie模型,这也是建立在遮罩影响为0的情况下,并且相对于上面的cot运算,Charlie模型的运算量更小。
https://www.zhihu.com/equation?tex=D%28m%29+%3D+%5Cfrac%7B%282+%2B+%5Cfrac%7B1%7D%7B%5Calpha%7D%29+sin%28%5Ctheta%29%5E%7B%5Cfrac%7B1%7D%7B%5Calpha%7D%7D%7D%7B2+%5Cpi%7D
两种代码:
inline float D_Ashikhmin(float roughness,float nh){
float a2 = roughness * roughness;
float cos2h = nh * nh ;
float sin2h = max(1.0 - cos2h, 0.0078125); // 2^(-14/2), so sin2h^2 > 0 in fp16
float sin4h = sin2h * sin2h;
float cot2 = -cos2h / (a2 * sin2h);
return 1.0 / (PI * (4.0 * a2 + 1.0) * sin4h) * (4.0 * exp(cot2) + sin4h);
}
inline float D_Charlie(float roughness, float nh) {
float invAlpha= 1.0 / roughness;
float cos2h = nh * nh;
float sin2h = max(1.0 - cos2h, 0.0078125); // 2^(-14/2), so sin2h^2 > 0 in fp16
return (2.0 + invAlpha) * pow(sin2h, invAlpha * 0.5) / (2.0 * PI);
}对于specular中的G项(Unity中的Visibility)
https://www.zhihu.com/equation?tex=%5Cbegin%7Bequation%7D%5C+V+%3D+%5Cfrac%7B1%7D%7B4%28nl+%2B+nv+-+%28nl%29%28nv%29%29%7D+%5Cend%7Bequation%7D
inline float VisibilityCloth(float nl, float nv, float roughness){
return1/(4 * ( nl + nv - nl*nv) + 1e-5f);
}最后整合得到Fr。
注意我们没有菲尼尔项,取而代之的是用Sheen来控制高光。在Physically Based Rendering in Filament 一文中,Sheen的缺省值为0.04 以实现天鹅绒的效果,如果考虑实现其他布料材质,我们可以考虑使用luminance来作为sheen值。
高光项的效果如下:(注意黑丝)
Charlie模型下的高光结果
加上漫反射项:
没有使用sss
加上sss:
加上sss后感觉更透了,像超能陆战队的大白一样。
不同参数下的结果:
注意:我们在使用sss时,不应该乘 https://www.zhihu.com/equation?tex=n%5Cbullet+l
最后我们的代码应该看起来像这样的:
half4 Cuntom_BRDF_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);
#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
half diffuseTerm = DisneyDiffuse(nv, nl, lh, perceptualRoughness);
//specular term
float roughness = PerceptualRoughnessToRoughness(perceptualRoughness);
roughness = max(roughness, 0.002);
half V = VisibilityCloth (nl, nv, roughness);
half D = DistributionCloth(roughness, nh);
#ifdef USE_LUMINANCE
half luminance = dot(diffColor,half3(0.299,0.587,0.114));
half3 F = luminance;
#else
half3 F = specColor;//0.04 should bedefault
#endif
half specularTerm = V * D * _SpecularScale;
specularTerm = max(0, specularTerm * nl);
half surfaceReduction;
surfaceReduction = 1.0 / (roughness*roughness + 1.0);
specularTerm *= any(specColor) ? 1.0 : 0.0;
//half grazingTerm = saturate(smoothness + (1-oneMinusReflectivity));
#ifdefSUBSURFACE_COLOR_ON
diffuseTerm *= Wrap(nl,_FabricScatterSale);
half3 Fd = diffColor * (gi.diffuse + light.color * diffuseTerm
* saturate(_FabricScatterColor + nl));
half3 Fr = specularTerm * light.color;
half3 color = Fd + Fr;
#else
half3 Fd = diffColor * (gi.diffuse + light.color * diffuseTerm
* nl);
half3 Fr = specularTerm * light.color;
half3 color = Fd + Fr;
#endif
return half4(color, 1);
}另外网上还有一种Custom Fabric的文章,并且知乎上有人实现了效果,传送门。但我觉得并不是很真实,他使用了比较强的菲尼尔系数,导致材质看起来蒙蒙的。
最后还有一篇关于快速模拟sss的文章,与本篇内容无关,但挺有意思的,传送门。
厉害 模型有木有传送门 sketchfab 搜索 “gwen” 大佬,小白前来请教!我想问问专精衣物布料的制作和物理模拟这块,怎么点亮技能树? 这个DisneyDiffuse的返回值是不是写错了,为啥返回是一个标量啊?
页:
[1]