首页
/ three.js MeshMatcapMaterial 深度指南:用 MatCap(Lit Sphere)纹理实现烘焙光照与风格化着色

three.js MeshMatcapMaterial 深度指南:用 MatCap(Lit Sphere)纹理实现烘焙光照与风格化着色

2026-09-07 10:44:51作者:管翌锬

MeshMatcapMaterial 是 three.js 中一类由 MatCap(Material Capture,又称 Lit Sphere/材质捕获球)纹理驱动的材质。它将"颜色+光照"一起烘焙进一张球形贴图,在渲染时按视点方向采样,从而在完全不依赖场景灯光的情况下呈现出受光质感。阅读本文后,你将掌握 MeshMatcapMaterial 的构造方式、全部公开属性的语义与默认值、纹理色彩空间约定,并能结合源码理解其着色器采样原理与阴影限制,进而在实际项目中直接落地使用。

什么是 MatCap 材质:把光照"烤"进纹理

MatCap(Material Capture)纹理是一张展开的球体贴图,其内容通常是在三维建模软件中预先渲染好的"材质球":球面上的每个像素,记录的是某个固定法线方向在某种光照环境下应呈现的颜色与明暗。因此这类贴图也被称作 Lit Sphere(光照球)

渲染时,MeshMatcapMaterial 会根据每个片元的视点相关法线(view-space normal)在 MatCap 纹理上采样,得到该方向预烘焙好的光照颜色。这意味着:

  • 材质不受场景灯光影响:因为光照信息已经在纹理里烘焙完成,场景中放置多少盏灯都不会改变它的明暗;
  • 着色呈现视点相关:转动视角时,采样点沿 MatCap 球的表面移动,观感类似于在观察一个固定的、被光照亮的球体;
  • 渲染成本极低:没有逐片元光照计算,适合在移动端或需要大量风格化物体的场景中提升性能。

需要特别说明的是其阴影行为:它可以把阴影投射到接收阴影的物体上(ShadowMaterial/接收面的 shadow clipping 也正常生效),但它自身不会自阴影(self-shadow),也不会接收阴影——这与其"光照已烘焙、无需参与阴影光照计算"的定位一致。该语义在 类文档源码注释 中均有明确声明。

构造器与继承关系

new MeshMatcapMaterial( parameters : Object )

parameters 是一个可包含一个或多个外观属性的对象。任何 Material 基类(乃至更上层继承链)公开的属性都可以传入,例如 transparentopacitysidealphaTest 等;颜色类属性可传入 Color#set 支持的任何取值形式(如 0xff0000'red''rgb(255,0,0)'new THREE.Color(...))。

继承链为:EventDispatcherMaterialMeshMatcapMaterial。从源码看,构造器在 super() 之后依次初始化 defines = { 'MATCAP': '' }type = 'MeshMatcapMaterial' 以及下文列出的全部属性,最后调用 this.setValues( parameters ) 统一应用传入的参数(构造函数)。单元测试也验证了它继承自 Materialtype 正确且携带 MATCAP 宏定义(测试用例)。

全部公开属性详解与默认值

为便于速查,先给出属性总表(默认值均可在 源码 中逐一核对):

属性 类型 默认值 作用
matcap Texture null MatCap 主贴图(核心)
color Color (1,1,1) 材质漫反射基色,与 matcap 采样色相乘
map Texture null 颜色贴图,与 color 相乘调制
bumpMap / bumpScale Texture / number null / 1 凹凸贴图及强度
normalMap / normalMapType Texture / enum null / TangentSpaceNormalMap 法线贴图与类型
normalScale Vector2 (1,1) 法线贴图影响强度
displacementMap / displacementScale / displacementBias Texture / number / number null / 1 / 0 顶点置换贴图及其缩放、偏移
alphaMap Texture null 灰度透明度贴图
wireframe / wireframeLinewidth boolean / number false / 1 线框渲染及线宽
flatShading boolean false 是否平面着色
fog boolean true 是否受雾效影响
isMeshMatcapMaterial boolean(只读) true 类型标识,用于运行时判断
defines Object { 'MATCAP': '' } 着色器预处理宏

注:displacementScale 在 API 文档正文中的默认值描述与 源码 保持一致为 1;该值仅当设置了 displacementMap 时才生效。

核心外观:matcap、color 与 map

.matcap : Texture —— MatCap 贴图,本材质区别于其他 Mesh 材质的关键。它代表的是亮度/光照数据,其色彩空间约定非常关键(详见下文"色彩空间约定"一节):HDR MatCap 纹理(如 .exr)通常设 texture.colorSpace = LinearSRGBColorSpace,而 LDR MatCap 纹理(如 .png.jpg.webp)通常设 texture.colorSpace = SRGBColorSpace

.color : Color —— 材质基色(diffuse)。着色器最终的出射光为 diffuseColor.rgb * matcapColor.rgb(见 fragment shader),即白色基色 (1,1,1) 时 matcap 颜色原样呈现,设置非白色则像给烘焙好的光照罩了一层滤色片——示例页面用 GUI 实时改 color 即为此效果。

.map : Texture —— 颜色贴图,采样结果同样与 color 相乘。它属于颜色数据纹理,需指定 SRGBColorSpace。可选带 alpha 通道,常与 Material#transparentMaterial#alphaTest 搭配使用。

法线与凹凸:改善球面采样的关键

MatCap 采样完全依赖片元法线,因此对模型细节的"光影表现"影响最大的是法线/凹凸通道:

.normalMap : Texture / .normalScale : Vector2 / .normalMapType : Texture 常量

  • 法线贴图的 RGB 逐片元改变表面法线,从而改变采样的 MatCap 颜色,但不改变几何形状
  • 若贴图按左手系(left-handed)烘焙,需把 normalScale.y 取负以抵消不同手性;
  • normalMapType 支持 TangentSpaceNormalMap(默认,切线空间)与 ObjectSpaceNormalMap(物体空间)两种取值。

.bumpMap : Texture / .bumpScale : number —— 黑白值映射"相对灯光的感知深度",同样只影响光照观感不改几何;一旦定义了 normalMap,bumpMap 会被忽略bumpScale 典型范围 [0,1],默认 1

.flatShading : boolean —— 是否平面着色,默认 false。开启后顶点法线被面法线替代,可用于刻意制造低多边形"块面感"(注意这会改变法线方向,从而直接影响 MatCap 采样结果)。

顶点置换通道(改变真实几何)

.displacementMap : Texture.displacementScale : number(默认 1)、.displacementBias : number(默认 0)—— 与其他材质不同,置换贴图真实地移动网格顶点位置(白为最高、黑为最低),被置换出的顶点可以投射阴影、遮挡其他物体,等同真实几何。bias 会直接加到"缩放后的置换采样值"上。最佳实践是搭配一张匹配的法线贴图,因为渲染器无法从置换后的顶点重新计算表面法线;顶点着色器中对应的位移逻辑见 meshmatcap 顶点着色器

透明度与线框

.alphaMap : Texture —— 灰度纹理控制表面不透明度(黑:全透明;白:全不透明)。需要注意:采样时只取纹理颜色、忽略自带 alpha 通道;对于 RGB/RGBA 纹理,渲染器会采样 绿色通道(因为 DXT 压缩与未压缩 RGB 565 格式中绿色通道拥有额外精度),Luminance-only 与 luminance/alpha 纹理不受影响、按预期工作。

.wireframe : boolean / .wireframeLinewidth : number —— 线框渲染开关默认 falsewireframeLinewidth 控制线宽,默认 1,且仅对 SVGRenderer(SVG 渲染器)有效,WebGL 渲染器中线宽通常固定为 1。

其他行为开关

  • .fog : boolean(默认 true):是否受场景雾效影响。着色器末尾的 #include <fog_fragment> 即对应此开关(fragment shader)。
  • .isMeshMatcapMaterial : boolean(只读,恒 true):用于类型测试,与源码中 this.isMeshMatcapMaterial = true;实现处)一致,运行时可用 material.isMeshMatcapMaterial 快速判断。

纹理色彩空间约定(Color Space 红线)

这是使用该材质最容易踩坑的地方,规则可归纳为三类:

纹理 数据类别 色彩空间要求
map 颜色数据 必须赋 SRGBColorSpace(绝大多数颜色贴图)
matcap(LDR,png/jpg/webp) 亮度数据 SRGBColorSpace
matcap(HDR,exr) 亮度数据 LinearSRGBColorSpace
bumpMap / normalMap / displacementMap / alphaMap 非颜色数据 保持默认 NoColorSpace

其中所有"非颜色数据"贴图若错误设置了 sRGB 色彩空间,会导致凹凸、法线、置换强度被错误解码而失真。官方示例页在拖入新 MatCap 图片后强制 texture.needsUpdate = truematerial.needsUpdate = true,正是因为"色彩空间可能变化、需要重新编译着色器"(见 webgl_materials_matcap 示例)。

源码级原理:MatCap 采样着色器剖析

要真正驾驭该材质,应理解 meshmatcap.glsl.js 中的采样数学:

  1. 构造视线正交基:由视向量 viewDir 派生两个正交切向量 xy
  2. 投影法线:片元法线(已经过 normal/bump 通道修正)分别与 xy 做点积,得到球面坐标;
  3. 映射到纹理 UVvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;——其中 0.495 是特意留出的 0.5% 收缩量,注释明确说明这是为了"消除 MatCap 圆盘贴图尺寸不足时产生的采样边缘伪影"(对应代码行);
  4. 兜底逻辑:当 USE_MATCAP 宏未定义(未设置 matcap)时,程序化生成一个从 0.20.8 的渐变球作为默认显示,避免黑屏(兜底实现)。

渲染管线侧的集成同样清晰可循:

  • 材质的 defines = { 'MATCAP': '' }material.matcap 是否设置,决定了 WebGLProgram 是否生成 #define USE_MATCAPWebGLProgram 代码)以及程序缓存 key 中的 matcap 位(WebGLPrograms 代码);
  • 每帧绘制前,WebGLMaterials 会把 material.matcap 上传到名为 matcap 的 sampler2D uniform(WebGLMaterials 代码),uniform 的默认声明见 ShaderLib 定义

因此"动态切换 matcap 纹理"是完全可行的——只需为新纹理设置正确的 colorSpace、texture.needsUpdate = true,必要时令 material.needsUpdate = true 触发着色器重编译(色彩空间改变时)。

完整实战示例:加载 LDR/HDR MatCap

下面结合 webgl_materials_matcap.htmloffscreen 场景示例 给出最小可用代码骨架。

基础用法(普通场景,配合法线贴图)

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';

// 1) 加载 LDR MatCap(jpg/png/webp),用 TextureLoader 即可
const textureLoader = new THREE.TextureLoader();
const matcapLDR = textureLoader.load( 'textures/matcaps/matcap-porcelain-white.jpg' );
matcapLDR.colorSpace = THREE.SRGBColorSpace;   // LDR → SRGBColorSpace

// 1') 若使用 HDR MatCap(.exr),需用 EXRLoader 并按线性色彩空间处理
const matcapHDR = new EXRLoader().load( 'textures/matcaps/040full.exr' );
matcapHDR.colorSpace = THREE.LinearSRGBColorSpace; // HDR → LinearSRGBColorSpace

// 2) 加载法线贴图(改善细节方向的光感)
const normalmap = textureLoader.load(
  'models/gltf/LeePerrySmith/Infinite-Level_02_Tangent_SmoothUV.jpg'
);

// 3) 构造材质并赋给网格
const material = new THREE.MeshMatcapMaterial( {
  color: new THREE.Color( 0xffffff ), // 基色
  matcap: matcapHDR,                   // MatCap 主贴图
  normalMap: normalmap                 // 法线贴图(可选但推荐)
} );

// 4) 运行时调整(示例页 GUI 同款逻辑)
material.color.set( 0xaa24df );
material.needsUpdate = true; // 换贴图且 colorSpace 变化时需要

批量风格化对象

offscreen 场景 展示了"一张 MatCap 纹理 + 多个不同 color 实例"的高效用法——多个 IcosahedronGeometry 网格各自持有不同基色的 MeshMatcapMaterial,共享同一张 matcap 贴图,即能以极低资源开销渲染出一整组具有统一风格光照的物体。该文件还演示了在 Worker/OffscreenCanvas 场景下用 ImageBitmapLoader + CanvasTexture(而非依赖 DOM 的 ImageLoader)加载 MatCap 的做法。

直接运行官方示例

仓库自带两个可直接运行的官方示例:

  • webgl_materials_matcap.html:加载 LeePerrySmith 模型,展示 EXR MatCap + 法线贴图的组合,支持拖放 JPG/PNG/WebP/AVIF/EXR 文件实时替换 MatCap,并可通过 lil-gui 调整基色与曝光(配合 ACESFilmicToneMapping)。
  • 材质总览场景 docs/scenes/material-browser.html:可视化对比包括 MeshMatcapMaterial 在内的各种材质表现,是挑选 MatCap 风格前的直观参考。

阴影与光照限制小结

基于文档与着色器实现,使用时请牢记以下边界:

  1. 不接受任何场景灯光#define MATCAP 路径下 fragment 着色器完全没有光源循环,画面明暗全部来自纹理;
  2. 可以投影,不可自阴影/接阴影:只做"被投"一侧,接收侧需要其他物体;
  3. 法线质量决定效果上限:由于采样基于法线,低模物体的棱边往往比平滑模型更容易暴露 MatCap 的"球面感",必要时用法线贴图、flatShading 开关或细分来调节观感。

相关 API 参考

登录后查看全文
热门项目推荐
相关项目推荐