【V-Ray 渲染器】依赖波长的次表面散射材质 OSL Shader

1,406 阅读10分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第4篇文章,点击查看活动详情

正文

下面的 OSL 材质模拟了 Tony Reynolds 制作的皮肤着色器,它采用了由红、绿、蓝波长调制的分层次表面散射(layered subsurface scattering)。此外,着色器实现了一个 Dual lobe Speculartwo-lobe spec,双叶瓣高光)来捕捉人类皮肤上高光的微妙变化。

oslSkinB3.png

理论

不同波长的光穿透皮肤的深度不同。如果我们将可见光谱简化为三种颜色(红、绿、蓝),那么每一种颜色的平均自由路径(光子在被散射或吸收之前穿过特定物质的距离)的值就会不同。对于人类的皮肤来说,红色是最深的,然后是绿色蓝色是相当浅的。

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 image.png

优缺点

这种方法的另一个好处是,当它设置的散射量较大时,它几乎消除了 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),嘴唇和鼻子看起来光滑和有光泽,因为遮罩是白色的,接收到最大的光泽值,而嘴唇上面的区域是粗糙的,因为遮罩是黑色的,因此接收到最小的光泽值。

  • 一个黑白光泽纹理贴图和从最小/最大光泽度值产生的高光变化。 image.png

双叶瓣高光 设置进一步增加了细节,结合了一个 “锐利” 的高光(由最小/最大光泽度值控制)和一个由 spec bloom 数量控制的 “软” 高光。bloom 越高,规格就越大,所以它类似于 “粗糙度” 参数。bloom 也受到光泽遮罩的影响。通过使用它们的 “数量” 滑块,两个高光叶瓣可以被关闭。

  • OSL 锐利和 bloom 高光叶瓣 image.png

凹凸 (Bump)

这个着色器 没有 凹凸,这是由于目前在 Vray 上 OSL 的限制(或者换句话来说,Vray 凹凸是相当复杂的,所以在 OSL 中实现它并不简单)。要获得 bump,只需将 OSLMtl 连接到 VrayBumpMtl 即可。

作为给用户的额外提示,拥有一个漂亮的凹凸贴图——包括皮肤毛孔之间的微观细节——对于有效捕捉人类皮肤是必不可少的。注意下面的图片,你可以在高光中看到比皮肤毛孔小得多的细节。双高光叶瓣 是捕捉这个细节所 必需 的。创建这些贴图的一种方法是在建模包中雕刻细节,并提取一个浮点 EXR 置换贴图。然而,在像 Mari 这样的程序中简单地将这些绘制成凹凸贴图通常更容易,通过为毛孔和毛孔之间的微观细节使用不同的层即可。在凹凸贴图中获得这种细节的技巧(与之相对的是置换贴图)是降低 凸起的增量比例(bump delta scale) (从 10.1),这将产生一个与置换贴图的精细细节相媲美的更清晰的结果。另一种方法是分离微观细节,并将其应用到高光图中。

  • 皮肤光度扫描的微观细节 image.png

UI 界面

  • 属性编辑器 image.png

一般参数

  • 透明度: 控制透明度
  • 深度尺度: 对次表面散射深度的全局控制
  • 整体亮度: 控制颜色的亮度。可以用来禁用 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(); }
}