跳到主要内容
腾讯题库 · Q09

原题 Q09 · 当前收录 8 / 49

学习状态 未开始

Roughness 高光分布

Shader PBR · roughness 对应课程:PBR 与光照

面试官问

PDF 第 19 页

在 PBR 材质中,粗糙度(Roughness)与金属度(Metallic)的贴图如何影响反射高光的分布?请结合 Shader 代码片段说明。(必问题,理清逻辑)

学习进度

0/9 已勾

只保存在这台浏览器;勾满 9 项即视为本题完成。

先把题目说成人话

手电照在抛光勺面上是一个小而集中的亮点,照到磨砂金属上则摊成一大片柔亮;再把喷漆塑料与裸金属并排看,前者保留有颜色的主体明暗,后者的颜色更多出现在反射里。现实要解决的不是两个“亮度开关”,而是一个旋钮改亮斑的形状,另一个旋钮改颜色和能量走哪条路。

控制亮斑摊宽程度的输入叫粗糙度(Roughness),控制金属与非金属路径的输入叫金属度(Metallic),材质颜色输入叫基础颜色(Base Color)。把表面看成许多细小镜面并统计其朝向的模型叫微表面模型(Microfacet Model),后文使用的一种分布叫格杰克斯分布(GGX);角度相关的反射比例叫菲涅耳项(Fresnel)。

这是虚构教学故事,不是个人项目经历。

场景:先让问题变得看得见

桌上有四个同形测试球:

  • 非金属、低 Roughness:小而清晰的无色高光,仍有明显漫反射底色。
  • 非金属、高 Roughness:高光铺宽、峰值降低,底色仍走漫反射。
  • 金属、低 Roughness:清晰且被 Base Color 染色的镜面反射,几乎没有漫反射。
  • 金属、高 Roughness:有色反射铺宽、变钝,仍不是“彩色漫反射”。

这个四格画面先建立关系,不把“粗糙度越高越暗”当作固定规律。粗糙度主要重新分布镜面能量;最终像素亮度还取决于环境、视角、遮蔽和实现。

(右脑)画面:窗边摆着三颗球:一颗光滑的黑塑料球、一颗磨砂黑塑料球和一颗光滑铜球。转动第一颗时,窗户的亮影又小又清楚;转动磨砂球,亮影摊开成柔和的一片;铜球上的亮影仍然清楚,却染上了铜的颜色。亮斑有多宽,和亮斑带什么颜色,是眼睛能分开看到的两件事。

(左脑)对表:核验对象是线性 Base Color、Metallic、工作流 Roughness、GGX 参数 α\alpha 以及同一空间内归一化的 N,V,L,HN,V,L,H。高光变宽和峰值变化是观察事实,“Roughness 直接乘亮度”是假设;证据要来自固定 HDRI、曝光与视角下的单变量扫描,金属有色反射也不能证明额外增加了一份能量。点积无量纲且需限制范围,perceptual roughness 是否平方必须服从管线约定。小镜子类比解释分布,类比不等于 D/G/F 已正确实现。

小明:先形成一个完整猜测

小明把 Roughness 当成高光强度滑杆,把 Metallic 当成镜面开关。这个直觉能描述部分画面,却解释不了两件事:粗糙高光为何面积变大,以及金属高光为何会继承 Base Color。

如果只把粗糙度乘到最终高光颜色上,高光会原地变暗,却不会正确展宽;如果只在 Metallic 为 1 时“额外加高光”,还会违反能量分配。

小红:把猜测放回现实检查

小红先固定 Metallic,只提高 Roughness。法线分布从集中变得分散,高光峰降低、波瓣变宽;几何遮蔽也随角度改变。然后固定 Roughness,只把 Metallic 从 0 移到 1:电介质的近似 F0F_0 逐渐过渡到 Base Color,漫反射同时被压到零。

她检查了变量前提:所有方向向量归一化,点积限制在 0–1;Base Color 在着色计算时是线性颜色;工作流中的 perceptual roughness 是否要平方成 GGX 的 α\alpha,必须与项目管线约定一致。

我:用检查结果修正模型

我保留两张贴图的独立职责。Roughness 进入 GGX 的法线分布 D 和几何遮蔽 G;Metallic 通过 F0F_0 进入 Fresnel F,并同时控制漫反射剩余量。贴图打包可以减少采样,但不能改变通道语义。

材质验收使用固定 HDRI、灰球和多角度旋转:不能只在一张正面截图判断。金属边缘若混入污渍,可用连续 Metallic 做纹理过滤与材质混合;纯净均质区域则通常更接近 0 或 1。

(右脑)画面修正:小明把 Roughness 当成单纯调暗高光的旋钮,又在 Metallic 打开时额外叠了一层亮光。小红固定窗光和观察位置以后,看到 Roughness 主要让同一份反射铺得更开,而 Metallic 改变了底色怎样进入反射、漫反射还剩多少。粗糙金属仍会留下铺开的有色反射,粗糙塑料也仍能看见自己的底色;不能把两只旋钮都理解成“更亮或更暗”。

(左脑)严格对表:着色数据按序列处理:把 Base Color 解码到线性空间;将 Roughness 映射为项目约定的 α\alpha;由 N,V,LN,V,L 构造并归一化 HH;计算 GGX 的 D、Smith G;以 F0=lerp(0.04,BaseColor,Metallic)F_0=lerp(0.04,BaseColor,Metallic) 计算 Fresnel F;令漫反射乘 (1−Metallic)(1−F)(1-Metallic)(1-F),再与镜面共同乘光照项。验收时分别只扫一个通道,检查波瓣宽度、反射颜色和漫反射份额,固定 HDRI 多角度观察且无 NaN/Inf 或能量重复相加。

老师解惑

下面是省略多光源、IBL、阴影和管线封装的教学片段,只展示 GGX 的 D/G/F 与能量路径:

static const float PI = 3.14159265;

float3 FresnelSchlick(float VoH, float3 F0)
{
    return F0 + (1.0 - F0) * pow(1.0 - VoH, 5.0);
}

float DistributionGGX(float NoH, float alpha)
{
    float a2 = alpha * alpha;
    float d  = NoH * NoH * (a2 - 1.0) + 1.0;
    return a2 / max(PI * d * d, 1e-5);
}

float GeometrySchlickGGX(float NoX, float roughness)
{
    float k = ((roughness + 1.0) * (roughness + 1.0)) / 8.0;
    return NoX / max(NoX * (1.0 - k) + k, 1e-5);
}

float3 EvaluateBRDF(
    float3 baseColor, float metallic, float roughness,
    float3 N, float3 V, float3 L)
{
    float3 halfVector = V + L;
    float halfLengthSq = dot(halfVector, halfVector);
    float3 H = halfLengthSq > 1e-8
        ? halfVector * rsqrt(halfLengthSq)
        : N;
    float NoL = saturate(dot(N, L));
    float NoV = saturate(dot(N, V));
    float NoH = saturate(dot(N, H));
    float VoH = saturate(dot(V, H));

    float alpha = max(roughness * roughness, 0.002);
    float3 F0 = lerp(float3(0.04, 0.04, 0.04), baseColor, metallic);
    float3 F = FresnelSchlick(VoH, F0);
    float  D = DistributionGGX(NoH, alpha);
    float  G = GeometrySchlickGGX(NoL, roughness)
             * GeometrySchlickGGX(NoV, roughness);

    float3 specular = D * G * F / max(4.0 * NoL * NoV, 1e-5);
    float3 diffuse  = (1.0 - metallic) * (1.0 - F)
                    * baseColor / PI;
    return (diffuse + specular) * NoL;
}
  • D:微面法线朝向半程向量的分布;Roughness 主要在这里改变高光宽度。
  • G:光线和视线被微面遮蔽的比例。
  • F:Fresnel;Metallic 通过 F0F_0 决定镜面反射颜色。
  • 能量分配:金属度升高时,漫反射衰减,不能在保留完整漫反射的同时再叠一份金属高光。
  • 半程向量边界:当 VV 与 LL 几乎反向时,直接归一化 V+LV+L 会退化;教学代码先检查长度,再选择不会产生无效数值的回退方向。

工具分工

  • PBR / BRDF Lab:固定灯光与视角,分别扫描 Roughness 和 Metallic。
  • Unity Material Inspector:核对 Smoothness 与 Roughness 是否取反、贴图通道和 sRGB 设置。
  • Frame Debugger / RenderDoc:检查材质关键字、采样值和最终 D/G/F 中间结果。

记忆钩子

Roughness 塑造高光波瓣,Metallic 改写颜色与能量去向:D/G 看粗糙,F 和漫反射分账看金属。

这是教学场景,不是目标项目的真实捕获或个人经历。

下一步:验证这条因果

看见高光异常时,能判断是谁改了能量。

教学边界:它只验证这一条画面因果,不替代完整答案、项目经历或目标设备数据。

进入教学 Lab:「PBR:把一束光拆成可解释的能量」