☰
Three.js金属与玻璃材质渲染实战:光影调优与避坑指南
2026/11/4 5:55:02 网站建设 项目流程

金属和玻璃这两种材质,在实时渲染里属于那种“调好了惊艳全场,调不好一眼假”的典型。我见过太多项目,模型精度拉满、灯光布置也下了功夫,结果金属件反射一片死黑,玻璃杯像块磨砂塑料,整个画面质感直接塌掉。这篇内容就围绕 sketch-threejs 场景下的光影实现展开,把金属与玻璃材质的渲染逻辑、参数取舍、踩坑经验一次讲透。不管你是刚接触 Three.js 材质系统的新手,还是已经能跑通基础场景、想让画面再上一个台阶的开发者,下面这些内容都能直接拿去用。核心关键词就三个:金属材质、玻璃材质、光影渲染,全文围绕它们展开,不跑偏。

1. 先搞清楚渲染器与光照底座,材质才有意义

很多人一上来就调材质参数,结果怎么调都不对,问题往往不在材质本身,而在渲染器和光照的底座没搭对。金属和玻璃对环境的敏感度极高,底座差一点,材质表现就差一大截。这一章先把地基打牢。

1.1 渲染器的色彩与色调映射配置

Three.js 默认的渲染输出并不适合做写实材质。你要做的第一件事,是把渲染器的色彩管理打开,并选一个合适的色调映射。我通常的做法是:

const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.outputColorSpace = THREE.SRGBColorSpace; renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.0;

为什么是 ACESFilmic?因为金属的高光很容易过曝,线性映射下高光区域会直接死白,丢失所有细节。ACES 的曲线在高光段有柔和的压缩,能把金属反射的亮部“拉”回来,保留层次。toneMappingExposure这个参数别小看,它相当于相机的曝光补偿,金属场景通常给 0.8 到 1.2 之间,玻璃场景可以稍高一点到 1.3,因为玻璃本身透光,整体偏暗。

这里有个容易忽略的点:outputColorSpace必须设成SRGBColorSpace,否则你贴图里的颜色和最终输出会对不上,金属的反射色会偏灰。我早期就吃过这个亏,贴图明明调好了,渲染出来总感觉蒙了一层灰,查了半天才发现是色彩空间没设。

1.2 环境光照:金属和玻璃的“命根子”

金属材质几乎不产生漫反射,它的外观几乎完全来自对环境的反射。玻璃虽然透射为主,但表面反射同样依赖环境。所以环境贴图(Environment Map)是这两类材质的命根子,没有合格的环境贴图,调参数就是瞎调。

在 sketch-threejs 这类场景里,我推荐用 HDR 环境贴图配合PMREMGenerator做预过滤:

const pmremGenerator = new THREE.PMREMGenerator(renderer); pmremGenerator.compileEquirectangularShader(); const hdrTexture = await new THREE.TextureLoader().loadAsync('env.hdr'); hdrTexture.mapping = THREE.EquirectangularReflectionMapping; const envMap = pmremGenerator.fromEquirectangular(hdrTexture).texture; scene.environment = envMap;

为什么要用 PMREM?因为金属表面的粗糙度不同,反射的模糊程度也不同。PMREM 会预生成一系列不同粗糙度对应的 mipmap,让材质在采样时能根据 roughness 自动选到合适的模糊层级。如果你直接把 HDR 贴图塞给scene.environment,低粗糙度的金属还能看,一旦 roughness 上去,反射就会显得脏、有噪点。

环境贴图的选择也有讲究。室内场景用带窗户的室内 HDR,金属反射里能看到窗框和光斑,真实感立刻上来;如果随便拿一张户外天空图,金属件反射出一片蓝天白云,放在室内场景里就非常违和。这个细节很多人不注意,但它是“像不像”的关键。

1.3 直接光源的补充与阴影设置

环境贴图负责整体氛围和反射,但直接光源负责塑造形体和投影。金属的高光点、玻璃的焦散感,都需要直接光源来点。

我一般会布一盏主光(DirectionalLight 或 SpotLight)加一盏补光。主光负责高光,补光负责暗部细节。阴影方面,金属和玻璃的阴影都比较“硬”,shadow.mapSize建议给到 2048 以上,shadow.bias调到 -0.0005 左右防止阴影痤疮。

const keyLight = new THREE.DirectionalLight(0xffffff, 3); keyLight.position.set(5, 8, 5); keyLight.castShadow = true; keyLight.shadow.mapSize.set(2048, 2048); keyLight.shadow.bias = -0.0005; scene.add(keyLight);

注意DirectionalLight的强度在开启了物理正确光照(useLegacyLights = false,新版默认)之后,单位变了,3 左右是比较合理的值,别再用老教程里的 1。这个坑我踩过,按老参数布光,整个场景暗得没法看。

2. 金属材质:从“塑料感”到“真金属”的关键参数

金属材质调不好,最常见的结果就是“塑料感”——表面有高光,但反射是死的,看起来像涂了金属漆的塑料。问题出在几个核心参数上,这一章逐个拆。

2.1 metalness 与 roughness 的黄金组合

MeshStandardMaterial或MeshPhysicalMaterial里,金属的两个命门参数是metalness和roughness。

  • metalness:0 是绝缘体(塑料、木头),1 是纯金属。做金属件,这个值基本给 1.0,或者 0.9 到 1.0 之间。给 0.5 是最糟糕的,会得到一种“半金属半塑料”的诡异质感,现实里不存在这种材质。
  • roughness:控制表面微观粗糙度。0 是镜面,1 是完全漫反射。真实金属很少是绝对镜面,抛光金属给 0.05 到 0.15,拉丝金属给 0.3 到 0.5,铸造金属给 0.6 以上。
const metalMaterial = new THREE.MeshPhysicalMaterial({ color: 0xffffff, metalness: 1.0, roughness: 0.12, envMapIntensity: 1.2, });

这里color对金属来说不是“固有色”,而是反射的染色。纯金给0xffd700这类暖黄,不锈钢给接近白色。envMapIntensity控制环境反射的强度,金属场景我一般给 1.0 到 1.5,太高会显得反射过曝。

一个实操心得:调金属的时候,先把 roughness 拉到 0,看反射是否清晰、环境是否正确映射。如果反射是糊的或者黑的,说明环境贴图有问题,先解决环境再调粗糙度。这个顺序能帮你快速定位问题。

2.2 各向异性:拉丝金属的灵魂

普通金属用 roughness 就够了,但拉丝金属(brushed metal)有个方向性的高光,这时候要用anisotropy。MeshPhysicalMaterial支持各向异性:

const brushedMetal = new THREE.MeshPhysicalMaterial({ color: 0xcccccc, metalness: 1.0, roughness: 0.35, anisotropy: 0.8, anisotropyRotation: Math.PI / 2, });

anisotropy控制各向异性的强度,anisotropyRotation控制拉丝方向。拉丝金属的高光会沿着垂直于拉丝方向拉伸,形成那种细长的光带。这个参数在 sketch-threejs 里做产品展示特别有用,比如金属旋钮、拉丝面板,加上之后质感立刻不一样。

要注意的是,各向异性对性能有一定开销,而且需要环境贴图有足够的细节才能体现出来。如果环境贴图本身很糊,各向异性也救不了。

2.3 清漆层:给金属加一层“保护漆”

有些金属件表面有一层清漆或氧化层,比如汽车漆面、上漆的金属。这时候用clearcoat:

const coatedMetal = new THREE.MeshPhysicalMaterial({ color: 0x223344, metalness: 0.9, roughness: 0.4, clearcoat: 1.0, clearcoatRoughness: 0.1, });

clearcoat相当于在材质表面再叠一层透明的镜面反射层,clearcoatRoughness控制这层的粗糙度。汽车漆就是典型的“金属底漆 + 清漆层”结构,用这个组合能做出很真实的漆面效果。清漆层会让高光更“锐”,同时保留底层的金属反射,层次感很强。

2.4 金属材质的常见翻车点

说几个我实际踩过的坑。第一,金属反射里出现明显的“接缝”或“色块”,通常是环境贴图分辨率不够或者 PMREM 没做对,换高分辨率 HDR 重新生成。第二,金属在暗部完全死黑,是因为环境贴图暗部信息不足,可以适当提高envMapIntensity或者补一盏低强度的环境光。第三,金属高光出现“锯齿”或“闪烁”,多半是roughness给得太低加上抗锯齿不足,适当提高 roughness 或开启更高倍数的 MSAA。

还有一个隐蔽的坑:如果你用了后处理(比如 Bloom),金属的高光会被 Bloom 放大,导致过曝。这时候要么降低 Bloom 强度,要么把toneMappingExposure调低,两者要配合着调。

3. 玻璃材质:透射、折射与厚度的三重博弈

玻璃比金属难调,因为它同时涉及反射、折射、透射和吸收。很多人做玻璃,要么做成“透明塑料”,要么做成“毛玻璃”,很难做出那种通透又有体积感的真实玻璃。这一章把玻璃的几个核心参数拆开讲。

3.1 transmission 与 thickness:通透感的来源

做玻璃,核心是MeshPhysicalMaterial的transmission参数:

const glassMaterial = new THREE.MeshPhysicalMaterial({ color: 0xffffff, metalness: 0.0, roughness: 0.05, transmission: 1.0, thickness: 0.5, ior: 1.5, transparent: true, });

transmission设为 1.0 表示完全透射,这是玻璃的基础。thickness是玻璃的“厚度”,它影响折射的偏移量和吸收。厚度越大,透过玻璃看到的背景偏移越明显,同时颜色吸收越强(如果设了attenuationColor)。

这里有个关键点:transmission和opacity是两套机制,不要混用。用了transmission就不需要再设opacity,否则会双重衰减,玻璃会变得很暗。我见过有人两个都设,结果玻璃像蒙了层灰。

ior(折射率)玻璃给 1.5 左右,水给 1.33,钻石给 2.4。这个值影响折射的弯曲程度,给对了玻璃才有“厚度感”。

3.2 attenuationColor 与 attenuationDistance:有色玻璃的做法

做有色玻璃,不要直接改color,那样只是给透射光染色,效果很平。正确做法是用attenuationColor和attenuationDistance:

const coloredGlass = new THREE.MeshPhysicalMaterial({ transmission: 1.0, thickness: 1.0, ior: 1.5, attenuationColor: new THREE.Color(0x88ccaa), attenuationDistance: 0.8, });

attenuationColor是光穿过玻璃后被“吸收”后剩下的颜色,attenuationDistance是吸收达到该颜色所需的距离。厚度越大、距离越小,颜色越浓。这个机制模拟的是真实的吸收现象——光在玻璃里走得越远,被吸收得越多,颜色越深。所以玻璃厚的地方颜色深、薄的地方颜色浅,这种体积感是直接改color做不出来的。

3.3 玻璃的反射与粗糙度控制

玻璃表面是有反射的,roughness控制反射的清晰度。清玻璃给 0.0 到 0.1,磨砂玻璃给 0.3 到 0.6。注意玻璃的roughness同时影响透射的模糊程度,磨砂玻璃透过看东西是糊的,就是这个参数在起作用。

const frostedGlass = new THREE.MeshPhysicalMaterial({ transmission: 1.0, roughness: 0.4, thickness: 0.6, ior: 1.5, });

磨砂玻璃的难点在于,roughness高了之后,透射采样会有噪点。解决办法是提高渲染的采样数,或者用后处理降噪。在 sketch-threejs 里如果开了实时渲染,磨砂玻璃的噪点会比较明显,可以适当降低 roughness 到 0.3 左右,兼顾质感和性能。

3.4 玻璃渲染的性能与排序问题

玻璃是透明材质,透明物体的渲染顺序很重要。Three.js 默认按距离排序,但多个玻璃物体互相重叠时,排序可能出错,导致某些面“消失”或“穿透”。解决办法是手动设置renderOrder,或者把玻璃物体拆分成单独的渲染层。

性能方面,transmission是开销较大的特性,因为它需要额外的渲染 pass 来采样背景。场景里玻璃物体多的时候,帧率会明显下降。我的经验是:能用一个玻璃材质合并的,就别拆成多个;远处的玻璃可以降级成普通透明材质,近处才用完整 transmission。

还有一个坑:玻璃材质在阴影里的表现。玻璃本身投射的阴影应该是带透光的,但 Three.js 默认阴影不支持透射,玻璃会投出实心黑影。要做出玻璃的透光阴影,需要自定义阴影贴图或者用假阴影(比如在玻璃下方放一个半透明的贴片模拟焦散)。这个在 sketch-threejs 的产品展示里很常见,处理好了真实感提升明显。

4. 光影协同:让金属和玻璃在同一场景里“对话”

单独调好金属和玻璃还不够,同一个场景里两者要互相配合。金属反射玻璃、玻璃折射金属,这种互动才是真实感的关键。这一章讲协同。

4.1 环境贴图要同时满足两种材质

金属需要高动态范围的环境来体现反射细节,玻璃需要环境有足够的明暗对比来体现折射的通透感。一张好的环境贴图应该同时具备:明亮的窗户或光源(给金属高光、给玻璃亮部)、暗部区域(给金属暗部层次、给玻璃对比)、以及一些有辨识度的物体(让反射和折射有内容可看)。

我一般会选带室内家具或建筑结构的 HDR,而不是纯天空图。纯天空图反射在金属上是一片均匀的蓝,没有细节;玻璃折射出来也是均匀色块,缺乏层次。带结构的环境贴图能让金属反射出窗框、让玻璃折射出家具轮廓,画面立刻丰富。

4.2 光源位置与材质高光的配合

主光的位置决定了金属高光点和玻璃亮斑的位置。金属的高光要落在物体的“转折面”上,才能体现形体;玻璃的亮斑要落在边缘或曲面上,才能体现通透。布光的时候,我会先摆好相机,然后移动主光,观察金属高光在物体表面的移动,找到最能体现形体的位置。

一个技巧:金属和玻璃的高光最好错开,不要重叠在同一个区域。金属高光偏锐、玻璃亮斑偏柔,错开之后画面有节奏感。如果两者高光叠在一起,会显得过曝且杂乱。

4.3 反射与折射的相互影响

金属会反射玻璃,玻璃会折射金属,这种相互影响在 Three.js 里是自动计算的(通过环境贴图和屏幕空间反射),但前提是环境贴图里有这些物体的“影子”。如果环境贴图是静态的,金属反射里的玻璃就不是场景里真实的玻璃,而是环境贴图里的内容。

要做到真实的相互反射,需要屏幕空间反射(SSR)或者实时环境捕捉(CubeCamera)。SSR 性能开销大,CubeCamera 需要每帧更新。在 sketch-threejs 这类偏展示的场景里,我的建议是:静态展示用高质量环境贴图“假装”相互反射,动态场景再上 CubeCamera,并且降低更新频率(比如每 3 帧更新一次),平衡真实感和性能。

4.4 后处理对材质表现的影响

后处理是双刃剑。Bloom 能强化金属高光和玻璃亮斑,让画面更有“光泽感”,但过度会让高光糊成一片。SSAO 能增强暗部层次,让金属和玻璃的接触阴影更实,但参数不当会让画面发灰。

我的配置习惯是:Bloom 强度给 0.3 到 0.5,阈值设高一点(0.8 以上),只让真正的高光溢出;SSAO 半径给小一点,强度中等,主要压暗接触区域。色调映射用 ACES,配合适中的曝光,整体画面就不会过。

5. 实操排查链路:材质不对时怎么一步步定位

调材质最怕的是“不知道哪里错了”。这一章给一条完整的排查链路,从现象反推原因,照着走能快速定位问题。

5.1 现象一:金属反射是黑的或糊的

排查顺序:先看scene.environment是否设置,再看环境贴图是否经过 PMREM 处理,最后看envMapIntensity是否过低。如果环境贴图是 HDR 但没做 PMREM,低粗糙度金属可能还能看,高粗糙度就会糊。如果环境贴图是普通 LDR 图片,动态范围不够,金属反射会发灰发黑。解决方法是换 HDR 并重新生成 PMREM。

5.2 现象二:玻璃像塑料或像毛玻璃

玻璃像塑料,通常是transmission没设或设得太低,或者ior给成了 1.0。玻璃像毛玻璃,是roughness给高了。还有一种情况是thickness给得太小,折射偏移不明显,玻璃看起来“薄如纸”。把thickness调到 0.5 以上,ior给 1.5,roughness给 0.05,基本就能回到玻璃质感。

5.3 现象三:透明物体排序错乱

多个玻璃物体重叠时出现穿透或消失,是渲染排序问题。先尝试给每个玻璃物体设不同的renderOrder,按从远到近的顺序。如果还不行,把玻璃物体放到单独的Group里,用layers分层渲染。极端情况下,把玻璃拆成前后两个面分别渲染,手动控制顺序。

5.4 现象四:整体画面发灰或过曝

发灰通常是色调映射或色彩空间没设对,检查outputColorSpace和toneMapping。过曝是toneMappingExposure太高或者光源强度太高,金属场景把曝光降到 0.8 左右试试。还有一种可能是后处理的 Bloom 太强,把 Bloom 强度降下来。

下面这张表把常见现象和对应原因、解决方向整理在一起,方便对照:

现象最可能原因解决方向
金属反射死黑环境贴图缺失或动态范围不足换 HDR,做 PMREM
金属反射模糊有噪点未做 PMREM 或分辨率不足重新生成 PMREM,提高分辨率
金属塑料感metalness 给成中间值metalness 调到 0.9 以上
玻璃像塑料transmission 低或 ior 为 1transmission 设 1,ior 设 1.5
玻璃像毛玻璃roughness 过高roughness 降到 0.1 以下
玻璃无厚度感thickness 过小thickness 调到 0.5 以上
透明物体穿透渲染排序错误设 renderOrder 或分层
画面发灰色彩空间或色调映射错误检查 sRGB 与 ACES 配置
高光过曝曝光或 Bloom 过强降曝光,调 Bloom 阈值

6. 性能与画质的平衡:几个实战取舍

最后聊几个实战里的取舍,这些是文档里不会写、但实际项目里天天要面对的问题。

6.1 transmission 的开销与降级策略

transmission每个玻璃物体都会触发一次背景采样渲染,物体多了帧率掉得厉害。我的策略是分级:主视觉的玻璃用完整 transmission,次要的、远处的玻璃降级成transparent: true加低opacity的普通材质,视觉上差别不大,性能省很多。如果场景里玻璃特别多,考虑把多个玻璃合并成一个几何体,减少 draw call。

6.2 环境贴图的分辨率取舍

环境贴图分辨率越高,金属反射越清晰,但显存和采样开销也越大。2048 的分辨率对大多数展示场景够用,4096 适合特写镜头。如果金属物体在画面里占比很小,1024 也够。别盲目上 8K,收益递减明显。

6.3 实时反射 vs 静态环境贴图

CubeCamera 实时捕捉能做出真实的相互反射,但每帧更新开销大。我的做法是:相机不动的时候用静态环境贴图,相机移动时降低 CubeCamera 更新频率(每 3 到 5 帧一次),并且降低捕捉分辨率(256 或 512)。这样既有动态反射,又不至于卡顿。

6.4 移动端的材质降级

移动端 GPU 对 transmission 和各向异性的支持有限,性能也吃紧。移动端我会关掉 transmission,用普通透明材质加环境贴图模拟玻璃;金属的 anisotropy 关掉,用 roughness 贴图模拟拉丝方向。画质降一档,但帧率能稳住。

我个人在实际操作中的体会是,金属和玻璃的调参没有“万能数值”,环境贴图、光源、曝光三者一变,材质表现就全变。所以别死记参数,要理解每个参数背后的物理含义,然后根据场景去调。调的时候养成“先环境、再光源、后材质”的顺序,能少走很多弯路。另外,多拿真实物体的照片做对照,眼睛盯着参考图调,比凭感觉调靠谱得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询