材质与纹理系统
材质与纹理系统定义了渲染对象的表面外观属性。材质控制对象如何响应光照、透明度、混合和深度测试。纹理提供材质可以采样的图像数据。该系统通过编译过程将高级材质定义桥接到低级着色器程序,该编译过程根据材质特性生成 GLSL 代码。
关于材质如何在渲染管线中使用的详细信息,请参阅 WebGL 渲染管线。关于着色器程序编译的详细信息,请参阅 着色器程序与编译。关于应用材质的几何体数据,请参阅 几何体系统。
材质基类
Material 类 src/materials/Material.js17-660 定义了所有材质类型的抽象基类。它提供了所有渲染通用的属性:混合模式、剔除、深度测试、模板操作和裁剪平面。
核心材质属性
| 属性类别 | 属性 | 用途 |
|---|---|---|
| 混合 | blending, blendSrc, blendDst, blendEquation | 控制对象颜色如何与帧缓冲区混合 |
| 剔除 | side, shadowSide | 确定渲染哪些面 |
| 深度 | depthTest, depthWrite, depthFunc | 管理深度缓冲区操作 |
| 模板 | stencilWrite, stencilFunc, stencilRef | 控制模板缓冲区操作 |
| 透明度 | transparent, opacity, alphaTest, alphaHash | 管理透明度渲染 |
| 裁剪 | clippingPlanes, clipIntersection, clipShadows | 定义裁剪平面行为 |
材质状态标志
Material.needsUpdate // 触发重新编译
Material.version // 处置时递增
Material.isMaterial // 类型测试标志基类还定义了回调挂钩:
onBeforeCompile(shader, renderer)- 在编译之前修改着色器 src/materials/Material.js606-608customProgramCacheKey()- 扩展用于程序共享的缓存键 src/materials/Material.js622-625
来源: src/materials/Material.js1-660
纹理系统
Texture 类架构
纹理属性
Texture 类 src/textures/Texture.js32-600 封装了图像数据和采样参数:
核心纹理属性:
纹理更新机制:
纹理使用 needsUpdate 标志来触发 GPU 上传:
texture.needsUpdate = true; // 标记纹理以重新上传纹理矩阵将 offset、repeat、rotation 和 center 组合成单个 Matrix3 变换 src/textures/Texture.js458-482
内置材质类型
Three.js 在 ShaderLib src/renderers/shaders/ShaderLib.js9-355 中提供材质类型。每种材质类型映射到编译期间使用的着色器 ID。
材质类型映射
材质特性支持
| Material Type | Lighting | PBR | Textures | Special Features |
|---|---|---|---|---|
| MeshBasicMaterial | No | No | map, alphaMap, envMap, aoMap, lightMap, specularMap | Simple unlit rendering |
| MeshLambertMaterial | Yes (Lambertian) | No | + emissiveMap, bumpMap, normalMap, displacementMap | Diffuse lighting |
| MeshPhongMaterial | Yes (Blinn-Phong) | No | + specularMap | Specular highlights, shininess |
| MeshStandardMaterial | Yes | Yes | + roughnessMap, metalnessMap | Metallic-roughness PBR |
| MeshPhysicalMaterial | Yes | Yes | All standard + clearcoat, transmission, sheen, iridescence maps | Extended PBR features |
| MeshToonMaterial | Yes | No | + gradientMap | Cel-shaded rendering |
| MeshMatcapMaterial | No | No | matcap | Material capture lookup |
材质到着色器的编译管线
编译管线将高级材质定义转换为缓存的着色器程序。WebGLPrograms 类编排此过程。
编译流程图
参数对象结构
getParameters() 函数 src/renderers/webgl/WebGLPrograms.js50-380 生成一个驱动着色器生成的全面参数对象:
纹理特性检测:
const HAS_MAP = !! material.map;
const HAS_MATCAP = !! material.matcap;
const HAS_ENVMAP = !! envMap;
const HAS_AOMAP = !! material.aoMap;
const HAS_LIGHTMAP = !! material.lightMap;
const HAS_BUMPMAP = !! material.bumpMap;
const HAS_NORMALMAP = !! material.normalMap;
const HAS_DISPLACEMENTMAP = !! material.displacementMap;
const HAS_EMISSIVEMAP = !! material.emissiveMap;
const HAS_METALNESSMAP = !! material.metalnessMap;
const HAS_ROUGHNESSMAP = !! material.roughnessMap;UV 通道分配:
对于每个纹理,系统通过 getChannel() src/renderers/webgl/WebGLPrograms.js40-48 确定使用哪个 UV 属性:
parameters.mapUv = HAS_MAP && getChannel( material.map.channel );
parameters.alphaMapUv = HAS_ALPHAMAP && getChannel( material.alphaMap.channel );
parameters.normalMapUv = HAS_NORMALMAP && getChannel( material.normalMap.channel );
// ... etc for all texture types通道值 (0-3) 映射到 UV 属性(uv、uv1、uv2、uv3)。
材质属性标志:
parameters.flatShading = material.flatShading;
parameters.vertexColors = material.vertexColors;
parameters.vertexTangents = !! geometry.attributes.tangent && ( HAS_NORMALMAP || HAS_ANISOTROPY );
parameters.fog = !! fog;
parameters.useFog = material.fog === true;着色器前缀生成
WebGLProgram 构造函数 src/renderers/webgl/WebGLProgram.js412-1025 根据参数生成包含 #define 语句的着色器前缀:
顶点着色器前缀示例:
precision highp float;
#define SHADER_TYPE MeshStandardMaterial
#define SHADER_NAME MyMaterial
#define USE_MAP
#define MAP_UV uv
#define USE_NORMALMAP
#define NORMALMAP_UV uv
#define USE_TANGENT
#define USE_SKINNING
#define MORPHTARGETS_COUNT 4
// ... uniforms declarations生成过程在顶点着色器的 src/renderers/webgl/WebGLProgram.js473-669 和片元着色器的 src/renderers/webgl/WebGLProgram.js671-782 中发生。
Include 解析:
着色器代码使用 #include <chunk_name> 指令 src/renderers/webgl/WebGLProgram.js243-276,这些指令从 ShaderChunk 递归解析:
function resolveIncludes( string ) {
return string.replace( includePattern, includeReplacer );
}
function includeReplacer( match, include ) {
let string = ShaderChunk[ include ];
return resolveIncludes( string ); // Recursive
}材质 Uniform 和更新
Uniform 结构
材质通过 UniformsLib src/renderers/shaders/UniformsLib.js1-256 集合定义 uniform,每种材质类型单独合并:
Uniform 更新过程
WebGLMaterials.refreshUniforms() 函数 src/renderers/webgl/WebGLMaterials.js9-458 在渲染之前更新材质 uniform:
纹理 Uniform 更新:
function refreshTransformUniform( map, uniform ) {
if ( map.matrixAutoUpdate === true ) {
map.updateMatrix();
}
uniform.value.copy( map.matrix );
}每种纹理类型都有更新路径 src/renderers/webgl/WebGLMaterials.js45-328:
if ( material.map ) {
uniforms.map.value = material.map;
refreshTransformUniform( material.map, uniforms.mapTransform );
}
if ( material.normalMap ) {
uniforms.normalMap.value = material.normalMap;
uniforms.normalScale.value.copy( material.normalScale );
refreshTransformUniform( material.normalMap, uniforms.normalMapTransform );
}材质属性更新:
标准材质属性 src/renderers/webgl/WebGLMaterials.js134-284:
uniforms.opacity.value = material.opacity;
if ( material.color ) {
uniforms.diffuse.value.copy( material.color );
}
if ( material.emissive ) {
uniforms.emissive.value.copy( material.emissive )
.multiplyScalar( material.emissiveIntensity );
}
if ( material.roughness !== undefined ) {
uniforms.roughness.value = material.roughness;
}
if ( material.metalness !== undefined ) {
uniforms.metalness.value = material.metalness;
}物理材质扩展:
MeshPhysicalMaterial 添加额外的 uniform src/renderers/webgl/WebGLMaterials.js286-414:
uniforms.clearcoat.value = material.clearcoat;
uniforms.clearcoatRoughness.value = material.clearcoatRoughness;
if ( material.iridescence > 0 ) {
uniforms.iridescence.value = material.iridescence;
uniforms.iridescenceIOR.value = material.iridescenceIOR;
// ... thickness min/max
}
if ( material.sheen > 0 ) {
uniforms.sheenColor.value.copy( material.sheenColor )
.multiplyScalar( material.sheen );
uniforms.sheenRoughness.value = material.sheenRoughness;
}
if ( material.transmission > 0 ) {
uniforms.transmission.value = material.transmission;
uniforms.thickness.value = material.thickness;
uniforms.attenuationDistance.value = material.attenuationDistance;
uniforms.attenuationColor.value.copy( material.attenuationColor );
}材质序列化
JSON 导出格式
Material.toJSON() 方法 src/materials/Material.js627-658 将材质序列化为 JSON,存储纹理的 UUID:
{
"uuid": "F3D1E2A4-B5C6-7D8E-9F0A-1B2C3D4E5F6A",
"type": "MeshStandardMaterial",
"name": "MyMaterial",
"color": 16777215, // 0xffffff
"roughness": 0.5,
"metalness": 0.8,
"map": "texture-uuid-1",
"normalMap": "texture-uuid-2",
"normalScale": [1, 1],
"emissive": 0,
"emissiveIntensity": 1.0,
"envMapIntensity": 1.0,
"side": 0, // FrontSide
"transparent": false,
"opacity": 1.0,
"depthTest": true,
"depthWrite": true
}MaterialLoader 解析
MaterialLoader 类 src/loaders/MaterialLoader.js43-380 从 JSON 重建材质:
类型实例化:
createMaterialFromType( type ) {
const materialClass = {
'MeshBasicMaterial': MeshBasicMaterial,
'MeshLambertMaterial': MeshLambertMaterial,
'MeshPhongMaterial': MeshPhongMaterial,
'MeshStandardMaterial': MeshStandardMaterial,
'MeshPhysicalMaterial': MeshPhysicalMaterial,
// ... etc
}[ type ];
return new materialClass();
}属性分配:
加载器 src/loaders/MaterialLoader.js111-380 分配属性并进行类型特定处理:
if ( json.color !== undefined ) {
material.color.setHex( json.color );
}
if ( json.roughness !== undefined ) {
material.roughness = json.roughness;
}
if ( json.map !== undefined ) {
material.map = getTexture( json.map ); // Looks up by UUID
}
if ( json.normalScale !== undefined ) {
material.normalScale.fromArray( json.normalScale );
}纹理引用解析:
纹理从预加载的纹理字典 src/loaders/MaterialLoader.js113-125 解析:
const textures = this.textures;
function getTexture( name ) {
if ( textures[ name ] === undefined ) {
warn( 'MaterialLoader: Undefined texture', name );
}
return textures[ name ];
}ObjectLoader 通过首先解析纹理 src/loaders/ObjectLoader.js207-208 然后解析材质 src/loaders/ObjectLoader.js208 来协调此过程。
材质更新生命周期
触发条件
当设置 needsUpdate 时,材质触发重新编译:
material.needsUpdate = true; // Forces shader recompilation这会在以下情况自动发生:
- 阴影贴图类型更改 src/renderers/webgl/WebGLShadowMap.js134-154
- 材质类型需要不同的着色器特性
- 通过
onBeforeCompile修改自定义着色器代码
版本跟踪
材质使用版本号进行缓存失效 src/materials/Material.js69-75:
this.version = 0;
dispose() {
this.dispatchEvent( { type: 'dispose' } );
this.version++; // Invalidates cached programs
}渲染器跟踪材质版本以检测何时需要 uniform 更新。