“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第4篇文章,点击查看活动详情”
正文
下面的 OSL 材质模拟了 Tony Reynolds 制作的皮肤着色器,它采用了由红、绿、蓝波长调制的分层次表面散射(layered subsurface scattering)。此外,着色器实现了一个 Dual lobe Specular (two-lobe spec,双叶瓣高光)来捕捉人类皮肤上高光的微妙变化。
理论
不同波长的光穿透皮肤的深度不同。如果我们将可见光谱简化为三种颜色(红、绿、蓝),那么每一种颜色的平均自由路径(光子在被散射或吸收之前穿过特定物质的距离)的值就会不同。对于人类的皮肤来说,红色是最深的,然后是绿色,蓝色是相当浅的。
color Depth = getTextureDif(RGB_Scatter_Depth, color(0.8,0.2,0.1));
...
color SubColor = getTextureDif(Subsurface_Color, color(0.85,0.75,0.65));
...
color SubR = SubColor * color(1,0,0);
color SubG = SubColor * color(0,1,0);
color SubB = SubColor * color(0,0,1);
基于以上理论,着色器将主颜色纹理(次表面颜色)分离为红色、绿色和蓝色部分,并将这些部分输送到三个次表面着色器中,然后将它们组合在一起。这三个次表面着色器的深度(即平均自由路径)由相应的 RGB_Scatter_Depth
颜色的红、绿、蓝通道驱动。为了加快速度,将绿色和蓝色结合在一起,所以着色器将两个 SSS 闭包组合在一起(红+绿&蓝)而不是三个。
closure color SSS = vray_subsurface (
IOR, PhaseFunction,
Depth * Depth_Scale * 0.5,
SubColor * Subsurface_Amount,
"subdivs", Subdivs);
closure color SkinR = vray_subsurface (
IOR, PhaseFunction,
SubR * Depth[0] * Depth_Scale,
SubR * Subsurface_Amount,
"subdivs", Subdivs);
closure color SkinGB = vray_subsurface (
IOR, PhaseFunction,
((SubG * Depth[1]) +
(SubB * Depth[2])) *
Depth_Scale,
(SubG + SubB) * Subsurface_Amount,
"subdivs", Subdivs);
所基于的想法是,所有人类的主要散射颜色是红色(因为我们在不同的肤色下都有红色的血液)。
换句话说,RGB 散射深度本身并不是一种颜色,而是分别代表红、绿、蓝三种波长的散射系数。因此,将波长 RGB 深度颜色的红色通道设置为 1.0 意味着红色波长的深度是 100% ,而将其设置为 0.7 意味着它是 70%,以此类推,对于所有三个 RGB 通道。
请注意,在下图中,红色通道比绿色或蓝通道更柔和,因为它有更多的深度。
- RGB 散射深度值: 1.0, 0.4, 0.2
优缺点
这种方法的另一个好处是,当它设置的散射量较大时,它几乎消除了 SSS2
材质的薄部分可能出现的 “绿色误差”,因为它将多个次表面着色器线性混合在一起,而不像在 SSS2
材质将中次表面和散射颜色之间进行更复杂的相互作用,虽然更 物理正确,但对艺术家来说可能 不直观。
总的来说,这个着色器的 重点 是优先考虑 艺术控制 而不是 物理正确性 。像 VraySkinMtl
这样的分层皮肤着色器的一个 缺点 是,用户需要将不同(通常 不直观)的颜色混合在一起到达想要的皮肤颜色。autodesk 在谈到他们的分层 Miss*
着色器时表示: “每一层都有自己独特的颜色,这使得很难达到给定的“最终”颜色,并且改变图层之间的平衡会使颜色失去效果。” 同样, SSS2
材料的一个 缺点 是,由于次表面散射计算涉及复杂的数学,艺术家可能会选择一个特定的颜色,在渲染中却得到一个完全不同的颜色。
相比之下,使用本文的这个着色器,用户只需输入皮肤的单个纹理贴图到 Subsurface Color
中,就可以得到他们想要的结果。同样地,用户设置想要在渗出效果中看到的 Wavelength RGB Depth
的颜色(记住,这也会影响散射深度),他们也会得到 所见即所得 的结果。
单一散射/漫反射
该着色器包含一个设置为灰色的漫反射参数,以便在真实皮肤中模拟单一散射。换句话说,这就像一个非常粗糙的镜面反射,因为皮肤是电介质(dielectric),所以应该是无色的。由于 OSL 采用线性混合的工作方式,这比 SSS2
中的漫反射工作得更好,后者实际上抵消了 SSS
。相反,在这里,0.5 左右的值就可以起到不错的效果。如果你想要更偏向一些卡通效果,你可以将其设置为 0。
双叶瓣高光(Two-lobe Specular)
着色器将两个高光叶瓣(specular lobe)组合在一起。使用 GGX microfacet BRDF (GGX 相当于尾部衰减参数锁定在 2.0 的广义 Trowbridge-Reitz (GTR) BRDF) 作为 “尖锐”(硬) 高光叶瓣,以及使用 Blinn BRDF 作为 “软” 高光叶瓣。OSL 闭包用 SSS 平衡了这两个高光叶瓣,因此闭包总和不超过 1.0,使其 能量守恒(即反射光总量不能超过入射光总量)。如果您想要更专业一点,渲染器将闭包各组件根据权重相加,如果它们超过 1.0,则将权重除以它们的和,使它们保持相同的平衡。
/// @note 硬高光
closure color spcHard = Sharp_Amount * SpecColor * Spec_total_amount * 0.5 *
microfacet_ggx (Nb, Roughness, Reflection_IOR,
"gtr_gamma", Tail,
"subdivs", Reflection_Subdivs,
"reflection_glossiness", Spec_sharpness,
"highlight_glossiness", Spec_sharpness,
"trace_reflections", Trace_Sharp_Reflections
);
/// @note 软高光
closure color spcSoft = Bloom_Amount * Fresnel * Spec_total_amount * 2 *
vray_blinn (Nb, SoftGloss, Anisotropy, Aniso_rotation,
"subdivs", Reflection_Subdivs, "trace_reflections", Trace_Bloom_Reflections);
高光光泽度可以通过连接黑白贴图(参见下面的图1)到 Gloss Mask
参数来驱动。该贴图的 白色 部分将获得 “最大光泽度” 值,黑色 部分将获得 “最小光泽度” 值,灰色 区域将获得介于最小和最大值之间的值(见下图2)。如果没有提供贴图,着色器将 默认 使用最大光泽度值。这样一来,可以允许艺术家来确定面部更有光泽的部分,比如嘴唇和 T 字区。
color GlossMask = getTextureDif(Gloss_Mask, color(1));
float Spec_sharpness = blendGloss(Gloss_Min, Gloss_Max, GlossMask);
...
float SoftGloss = (1-Spec_bloom * 0.6) * Spec_sharpness;
注意 下面的图片(图3),嘴唇和鼻子看起来光滑和有光泽,因为遮罩是白色的,接收到最大的光泽值,而嘴唇上面的区域是粗糙的,因为遮罩是黑色的,因此接收到最小的光泽值。
- 一个黑白光泽纹理贴图和从最小/最大光泽度值产生的高光变化。
双叶瓣高光 设置进一步增加了细节,结合了一个 “锐利” 的高光(由最小/最大光泽度值控制)和一个由 spec bloom
数量控制的 “软” 高光。bloom 越高,规格就越大,所以它类似于 “粗糙度” 参数。bloom 也受到光泽遮罩的影响。通过使用它们的 “数量” 滑块,两个高光叶瓣可以被关闭。
- OSL 锐利和 bloom 高光叶瓣
凹凸 (Bump)
这个着色器 没有 凹凸,这是由于目前在 Vray 上 OSL 的限制(或者换句话来说,Vray 凹凸是相当复杂的,所以在 OSL 中实现它并不简单)。要获得 bump,只需将 OSLMtl
连接到 VrayBumpMtl
即可。
作为给用户的额外提示,拥有一个漂亮的凹凸贴图——包括皮肤毛孔之间的微观细节——对于有效捕捉人类皮肤是必不可少的。注意下面的图片,你可以在高光中看到比皮肤毛孔小得多的细节。双高光叶瓣 是捕捉这个细节所 必需 的。创建这些贴图的一种方法是在建模包中雕刻细节,并提取一个浮点 EXR 置换贴图。然而,在像 Mari 这样的程序中简单地将这些绘制成凹凸贴图通常更容易,通过为毛孔和毛孔之间的微观细节使用不同的层即可。在凹凸贴图中获得这种细节的技巧(与之相对的是置换贴图)是降低 凸起的增量比例(bump delta scale) (从 1 到 0.1),这将产生一个与置换贴图的精细细节相媲美的更清晰的结果。另一种方法是分离微观细节,并将其应用到高光图中。
- 皮肤光度扫描的微观细节
UI 界面
- 属性编辑器
一般参数
- 透明度: 控制透明度
- 深度尺度: 对次表面散射深度的全局控制
- 整体亮度: 控制颜色的亮度。可以用来禁用 sss 和漫反射。
- 漫反射颜色: 用一个无色的 Labertian 漫反射模拟单一散射。
- 漫反射量: 控制漫反射量。可以用来禁用漫反射。
次表面参数
- 底色: 材质的主色。这就是你的颜色纹理。默认 RGB 值:
0.85,0.75,0.65
- 次表面量: 控制浅散射量,而不是深散射量。可用于禁用 SSS。
- RGB 散射深度: 控制红色、绿色和蓝色波长的散射百分比,这也影响散射颜色。默认 RGB 值:
0.8,0.2,0.1
- 纹理伽马: 伽马校正次表面颜色
反射/规格参数
- 规格颜色: 高光的颜色(皮肤是电介质,所以这应该是白色的)
- 光泽度蒙版: 黑色和白色贴图之间调节最小/最大光泽度值。
- 高光总量: 控制两个高光叶瓣的总量。可用于禁用该高光。
- 锐利数量: 锐利高光的数量。可以用来禁用锋利高光。
- Bloom 数量: 软高光的数量。可以用来禁用 bloom 高光。
- 光泽度最小值: 光泽度遮罩贴图黑色部分的光泽度值
- 光泽度最大值: 光泽度遮罩贴图白色部分的光泽度值
- 高光 Bloom: 控制高光 bloom 的大小。数值越大,bloom 的尺寸越大。
高级的反射参数
- 反射 IOR 指数: 该高光的折射的反射索引
- 反射细分: 反射细分 (在 OSL for Vray 的 SSS 中目前没有细分。当渲染设置中 “
use local subdivs
” 关闭时,此选项将被忽略。 - 追踪锐利反射: 为锐利高光的 BRDF 启用光线追踪反射(禁用此功能只会产生高光组件)
- 追踪 Bloom 反射: 为 Bloom 高光 BRDF 启用光线追踪反射(禁用此功能只会产生高光组件)
- 使用单一 SSS: 禁用 RGB 波长分层 SSS,并使用单一次表面(相当于 VraySSS2Mtl)。效果虽然没有那么好,但是更快。
完整 OSL 代码
要使用以下代码,将其复制粘贴到 VRayOSLMtl 中
/*
*
* 依赖波长的次表面散射 OSL 材料由 Derek Flood (cc)2016
* 灵感来自 Tony Reynolds 的超阴影树,增加了 two-lobe spec 和可映射光泽度。
*
*
*/
color getTextureDif(string texture_name, color no_texture_default_color)
{
int channels = -1;
if (gettextureinfo(texture_name, "channels", channels))
{
return texture(texture_name, u, v);
}
return no_texture_default_color;
}
float fresnelReflectionFactor(normal Nb, float ior)
{
float c = abs(dot(I, Nb));
float g = ior * ior - 1.0 + c * c;
if (g > 0.0) {
g = sqrt (g);
float A = (g - c) / (g + c);
float B = (c * (g + c) - 1.0) / (c * (g - c) + 1.0);
return 0.5 * A * A * (1.0 + B * B);
}
return 1.0;
}
float blendGloss(float GlossMin, float GlossMax, color Blender)
{
Blender = luminance(Blender);
return GlossMin * (1.0 - Blender[0]) + GlossMax * Blender[0];
}
surface DFskinMtl
[[ string description = "blended skin material" ]]
(
/* 全局区 */
string Transparency = "trs.png",
float Depth_Scale = 1,
float Overall_Brightness = 0.8,
color Diffuse_Color = 0.5,
float Diffuse_Amount = 0.5,
/* SSS 区 */
string Subsurface_Color = "color.png",
float Subsurface_Amount = 1,
string RGB_Scatter_Depth = "RGB.png"
[[ string description =
"Proportional scatter depth for red, green, and blue wavelengths."
]],
float Texture_Gamma = 2.2,
/* 高光区 */
string Spec_color = "specular.png",
string Gloss_Mask = "mask.png"
[[ string description =
"black and white mask for glossiness min/max values."
]],
float Spec_total_amount = 0.5
[[ string description =
"Total amount of sharp and soft spec."
]],
float Sharp_Amount = 0.3
[[ string description =
"For the sharp spec lobe."
]],
float Bloom_Amount = 0.7
[[ string description =
"For the soft spec lobe."
]],
float Gloss_Min = 0.6
[[ string description =
"Glossiness value for the black parts of the Gloss Mask map."
]],
float Gloss_Max = 0.8
[[ string description =
"Glossiness value for the white parts of the Gloss Mask map."
]],
float Spec_bloom = 0.3
[[ string description =
"For the soft spec lobe (Blinn). Controls tail size of spec."
]],
float Reflection_IOR = 1.5,
int Reflection_Subdivs = 8,
int Trace_Sharp_Reflections = 1
[[ string widget = "checkBox" ]],
int Trace_Bloom_Reflections = 1
[[ string widget = "checkBox" ]],
int Use_Single_SSS = 0
[[ string widget = "checkBox" ]],
output color result = 1
)
{
/* 定义 Bump */
normal Nb = N;
/* 声明 SSS 变量并读取纹理贴图 */
color TrsColor = getTextureDif(Transparency, color(0));
color Opacity = 1-TrsColor;
color Depth = getTextureDif(RGB_Scatter_Depth, color(0.8,0.2,0.1));
Depth = clamp(Depth,0.001,1); // 防止深度值为零,这会破坏着色器 RGB 混合
color SubColor = getTextureDif(Subsurface_Color, color(0.85,0.75,0.65));
SubColor = pow(SubColor, Texture_Gamma); // gamma 校正纹理
SubColor *= Overall_Brightness;
color SubR = SubColor * color(1,0,0);
color SubG = SubColor * color(0,1,0);
color SubB = SubColor * color(0,0,1);
/* 定义高光 */
float IOR = 1.38;
float PhaseFunction = 0.8;
int Subdivs = 8;
color SpecColor = getTextureDif(Spec_color, color(1,1,1));
color GlossMask = getTextureDif(Gloss_Mask, color(1));
float Spec_sharpness = blendGloss(Gloss_Min, Gloss_Max, GlossMask);
color Fresnel = SpecColor * fresnelReflectionFactor(Nb, Reflection_IOR); // Blinn 的菲涅尔
float SoftGloss = (1-Spec_bloom * 0.6) * Spec_sharpness;
float Tail = 2.0;
float Roughness = 0;
float Anisotropy = 0;
float Aniso_rotation = 0;
/* 闭包区 */
closure color Diff = Diffuse_Color * Diffuse_Amount * Overall_Brightness *
diffuse(Nb, "roughness", 0);
closure color SSS = vray_subsurface (
IOR, PhaseFunction,
Depth * Depth_Scale * 0.5,
SubColor * Subsurface_Amount,
"subdivs", Subdivs);
closure color SkinR = vray_subsurface (
IOR, PhaseFunction,
SubR * Depth[0] * Depth_Scale,
SubR * Subsurface_Amount,
"subdivs", Subdivs);
closure color SkinGB = vray_subsurface (
IOR, PhaseFunction,
((SubG * Depth[1]) +
(SubB * Depth[2])) *
Depth_Scale,
(SubG + SubB) * Subsurface_Amount,
"subdivs", Subdivs);
closure color spcHard = Sharp_Amount * SpecColor * Spec_total_amount * 0.5 *
microfacet_ggx (Nb, Roughness, Reflection_IOR,
"gtr_gamma", Tail,
"subdivs", Reflection_Subdivs,
"reflection_glossiness", Spec_sharpness,
"highlight_glossiness", Spec_sharpness,
"trace_reflections", Trace_Sharp_Reflections
);
closure color spcSoft = Bloom_Amount * Fresnel * Spec_total_amount * 2 *
vray_blinn (Nb, SoftGloss, Anisotropy, Aniso_rotation,
"subdivs", Reflection_Subdivs, "trace_reflections", Trace_Bloom_Reflections);
if (Use_Single_SSS)
{ Ci = Opacity * Diff + SSS + spcHard + spcSoft + (1.0 - Opacity) * transparent(); }
else
{ Ci = Opacity * Diff + SkinR + SkinGB + spcHard + spcSoft + (1.0 - Opacity) * transparent(); }
}