本文为这篇文章的翻译与整理

各向异性(Anisotropy)

引入了两个新的材质属性:强度参数和镜面反射相对于表面切线延伸的方向

  • 强度参数是范围在[0, 1]内的无量纲数字,它增加了沿选定方向的粗糙度。
  • 默认方向与网格的切线对齐,如glTF 2.0规范中的网格部分所述。

切线空间要求

使用各向异性材质的网格基元必须具有定义的切线空间:

  • 必须具有NORMALTANGENT属性,或者
  • 其基础材质必须具有法线纹理。

如果网格基元没有NORMALTANGENT向量,它们将按照glTF 2.0规范的定义进行计算。

由于glTF 2.0不强制要求任何特定的切线空间推导算法,网格基元应该始终提供它们的NORMALTANGENT向量。

当材质同时具有normalTextureanisotropyTexture时,这些纹理应该使用相同的纹理坐标,因为它们在同一切线空间中操作,且它们的纹素值通常相关。

各向异性纹理

当提供时,各向异性纹理编码:

  • 红色和绿色通道: 切线空间中各向异性方向向量的XY分量。
  • 蓝色通道: 各向异性强度。

所有值都使用线性传输函数存储。

反量化

  • 红色 [0, 1] → X [-1, 1]
  • 绿色 [0, 1] → Y [-1, 1]
  • 蓝色:无需重新映射

当未提供各向异性纹理时的默认反量化纹素值:

(1.0, 0.5, 1.0)
  • 对应于方向向量(1, 0)(+X轴)和全强度。

XY向量方向指定在由anisotropyRotation旋转之前,切线/副切线空间中增加各向异性粗糙度的每个纹素方向

  • 蓝色通道包含强度[0, 1],将与anisotropyStrength相乘以确定每个纹素的各向异性强度。

注意: 各向异性的方向向量是高光将被拉伸的方向。导致各向异性的微凹槽垂直于这个方向。

粗糙度计算

沿增加各向异性方向(默认切线方向)的粗糙度为:

directionAlphaRoughness = mix(materialAlphaRoughness, 1.0, strength^2)

其中:

materialAlphaRoughness = materialRoughness^2
mix(x, y, m) = x * (1.0 - m) + y * m
  • 垂直方向(默认副切线方向)的粗糙度等于材质指定的粗糙度。

这两个alphaRoughness值,αtαb,对Burley的扩展BRDF分布项有贡献:

D(h) = 1 / (π αt αb) * ( (h ⋅ t)^2 / αt^2 + (h ⋅ b)^2 / αb^2 + (h ⋅ n)^2 )^(-2)

掩蔽/阴影函数近似可以类似地推导出单α分布函数。


实现

anisotropyRotation的默认值为零:

u_AnisotropyRotation = [1.0, 0.0]

单独光源

uniform float u_AnisotropyStrength;
uniform vec2 u_AnisotropyRotation;

float anisotropy = u_AnisotropyStrength;
vec2 direction = u_AnisotropyRotation;

#if HAS_ANISOTROPY_MAP
vec3 anisotropyTex = texture(u_AnisotropyTextureSampler, uv).rgb;
direction = anisotropyTex.rg * 2.0 - vec2(1.0);
direction = mat2(u_AnisotropyRotation.x, u_AnisotropyRotation.y, -u_AnisotropyRotation.y, u_AnisotropyRotation.x) * normalize(direction);
anisotropy *= anisotropyTex.b;
#endif

vec3 anisotropicT = normalize(TBN * vec3(direction, 0.0));
vec3 anisotropicB = normalize(cross(normal_geometric, anisotropicT));

float TdotL = dot(anisotropicT, l);
float BdotL = dot(anisotropicB, l);
float TdotH = dot(anisotropicT, h);
float BdotH = dot(anisotropicB, h);

f_specular += intensity * NdotL * BRDF_specularAnisotropicGGX(f0, f90, alphaRoughness,
VdotH, NdotL, NdotV, NdotH,
BdotV, TdotV, TdotL, BdotL, TdotH, BdotH, anisotropy);

各向异性GGX

vec3 BRDF_specularAnisotropicGGX(vec3 f0, vec3 f90, float alphaRoughness,
float VdotH, float NdotL, float NdotV, float NdotH, float BdotV, float TdotV,
float TdotL, float BdotL, float TdotH, float BdotH, float anisotropy)
{
float at = mix(alphaRoughness, 1.0, anisotropy * anisotropy);
float ab = alphaRoughness;

vec3 F = F_Schlick(f0, f90, VdotH);
float V = V_GGX_anisotropic(NdotL, NdotV, BdotV, TdotV, TdotL, BdotL, at, ab);
float D = D_GGX_anisotropic(NdotH, TdotH, BdotH, at, ab);

return F * V * D;
}

float D_GGX_anisotropic(float NdotH, float TdotH, float BdotH, float at, float ab)
{
float a2 = at * ab;
vec3 f = vec3(ab * TdotH, at * BdotH, a2 * NdotH);
float w2 = a2 / dot(f, f);
return a2 * w2 * w2 / M_PI;
}

float V_GGX_anisotropic(float NdotL, float NdotV, float BdotV, float TdotV, float TdotL, float BdotL,
float at, float ab)
{
float GGXV = NdotL * length(vec3(at * TdotV, ab * BdotV, NdotV));
float GGXL = NdotV * length(vec3(at * TdotL, ab * BdotL, NdotL));
float v = 0.5 / (GGXV + GGXL);
return clamp(v, 0.0, 1.0);
}
  • atab表示沿两个各向异性方向的线性粗糙度值

基于图像的照明(IBL)

  • 通常使用PMREM(预过滤多级辐射环境贴图)。
  • 由于PMREM是为单一粗糙度/方向采样的,一个样本可能不准确。鼓励在速度和准确性之间进行权衡。
  • 使用材质的基础粗糙度(两个方向的最小值)作为良好的PMREM采样值。
vec3 bentNormal = cross(anisotropicB, viewDir);
bentNormal = normalize(cross(bentNormal, anisotropicB));
float a = pow2(pow2(1.0 - anisotropy * (1.0 - roughness)));
bentNormal = normalize(mix(bentNormal, normal, a));
vec3 reflectVec = reflect(-viewDir, bentNormal);
reflectVec = normalize(mix(reflectVec, bentNormal, roughness * roughness));
f_specular += SamplePMREM(envMap, reflectVec, roughness);
  • 进一步的样本应该沿着anisotropicT方向放置,根据αt进行间距。
  • αb很小时(闪亮的基础材质)特别重要。

参考文献

  • Google Filament - 各向异性模型
  • Google Filament 材质指南 - 各向异性模型
  • Blender 原理化BSDF
  • Christopher Kulla and Alejandro Conty, 2017. 在Imageworks重新审视基于物理的着色