1. Three.js 基于物理的渲染(PBR)材质详解

1.1. PBR 概述

1.1.1. 什么是PBR

基于物理的渲染(Physically Based Rendering)是一种遵循真实世界物理规律的光照模型,通过模拟光线与物体表面的物理交互来生成逼真的渲染效果。

1.1.2. PBR核心原理

1.2. Three.js 中的PBR材质

1.2.1. MeshStandardMaterial(标准材质)

javascript

// 基本使用
const material = new THREE.MeshStandardMaterial({
    color: 0xffffff,
    roughness: 0.5,      // 粗糙度:0(镜面)~1(完全粗糙)
    metalness: 0.5,      // 金属度:0(非金属)~1(金属)
    map: texture,        // 漫反射贴图
    normalMap: normalTexture,    // 法线贴图
    roughnessMap: roughnessTexture,  // 粗糙度贴图
    metalnessMap: metalnessTexture,  // 金属度贴图
    aoMap: aoTexture,    // 环境光遮蔽贴图
    emissive: 0x000000,  // 自发光颜色
    emissiveIntensity: 1 // 自发光强度
});

1.2.2. MeshPhysicalMaterial(物理材质)

在标准材质基础上增加了更多物理属性:

javascript

const material = new THREE.MeshPhysicalMaterial({
    // 继承所有MeshStandardMaterial属性
    clearcoat: 0.5,           // 清漆层强度:0~1
    clearcoatRoughness: 0.1,  // 清漆层粗糙度
    sheen: 0.5,               // 光泽层(织物材质)
    sheenRoughness: 0.5,
    transmission: 0.5,        // 透射度(透明材质)
    thickness: 0.1,          // 厚度(用于透射计算)
    ior: 1.5,                // 折射率
    specularIntensity: 1,     // 镜面反射强度
    specularColor: 0xffffff,  // 镜面反射颜色
});

1.3. PBR材质贴图详解

1.3.1. 贴图配置示例

javascript

const textureLoader = new THREE.TextureLoader();

const material = new THREE.MeshStandardMaterial({
    color: 0xffffff,

    // 基础贴图
    map: textureLoader.load('albedo.jpg'),          // 反照率/漫反射

    // 物理属性贴图
    roughnessMap: textureLoader.load('roughness.jpg'),
    metalnessMap: textureLoader.load('metalness.jpg'),

    // 细节增强贴图
    normalMap: textureLoader.load('normal.jpg'),    // 法线贴图
    normalScale: new THREE.Vector2(1, 1),          // 法线强度

    aoMap: textureLoader.load('ao.jpg'),           // 环境光遮蔽
    aoMapIntensity: 1,                             // AO强度

    displacementMap: textureLoader.load('height.png'), // 置换贴图
    displacementScale: 0.1,
    displacementBias: -0.05,

    // 自发光
    emissiveMap: textureLoader.load('emissive.jpg'),
    emissive: 0xffffff,
    emissiveIntensity: 1
});

1.3.2. 贴图打包技术

将多个参数打包到一张贴图的不同通道:

javascript

// RGBA通道分别存储不同信息
// R: 粗糙度, G: 金属度, B: AO, A: 高度
const packedMap = textureLoader.load('packed.jpg');
material.roughnessMap = packedMap;
material.metalnessMap = packedMap;
material.aoMap = packedMap;

// 设置不同的通道
material.roughnessMap.channel = 0; // R通道
material.metalnessMap.channel = 1; // G通道
material.aoMap.channel = 2;       // B通道

1.4. PBR光照环境配置

1.4.1. 环境贴图设置

javascript

// 1. 加载HDR环境贴图
new THREE.RGBELoader()
    .load('environment.hdr', function(texture) {
        texture.mapping = THREE.EquirectangularReflectionMapping;

        // 设置为场景环境
        scene.environment = texture;

        // 设置为材质的背景
        scene.background = texture;
    });

// 2. 创建环境贴图
const pmremGenerator = new THREE.PMREMGenerator(renderer);
const envTexture = pmremGenerator.fromScene(environmentScene).texture;
material.envMap = envTexture;
material.envMapIntensity = 1.0;

1.4.2. 光源设置

javascript

// HDRI环境光照 + 方向光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
scene.add(ambientLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 5, 5);
directionalLight.castShadow = true;
scene.add(directionalLight);

// 或者使用光探针
const lightProbe = new THREE.LightProbe();
scene.add(lightProbe);

// 从环境贴图生成光探针
pmremGenerator.fromScene(environmentScene).then((cubeRenderTarget) => {
    lightProbe.copy(LightProbeGenerator.fromCubeRenderTarget(renderer, cubeRenderTarget));
});

1.5. 性能优化技巧

1.5.1. 材质优化

javascript

// 1. 使用压缩纹理格式
const compressedTexture = new THREE.CompressedTextureLoader()
    .setPath('textures/')
    .load('diffuse.dds');

// 2. 调整精度
material.precision = 'mediump'; // 'highp', 'mediump', 'lowp'

// 3. 合并材质参数
material.combine = THREE.MultiplyOperation; // 合并方式

1.5.2. 渲染优化

javascript

// 1. 使用PBR优化渲染器
renderer.physicallyCorrectLights = true;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1;

// 2. 调整渲染质量
renderer.outputEncoding = THREE.sRGBEncoding;

1.6. 完整示例

javascript

// 创建PBR材质的完整示例
function createPBRMaterial() {
    const textureLoader = new THREE.TextureLoader();

    // 加载所有贴图
    const albedo = textureLoader.load('textures/albedo.jpg');
    const normal = textureLoader.load('textures/normal.jpg');
    const roughness = textureLoader.load('textures/roughness.jpg');
    const metalness = textureLoader.load('textures/metalness.jpg');
    const ao = textureLoader.load('textures/ao.jpg');

    // 创建PBR材质
    const material = new THREE.MeshPhysicalMaterial({
        color: 0xffffff,
        map: albedo,
        normalMap: normal,
        normalScale: new THREE.Vector2(1, 1),
        roughnessMap: roughness,
        roughness: 0.5,
        metalnessMap: metalness,
        metalness: 0.8,
        aoMap: ao,
        aoMapIntensity: 1,

        // 物理特性
        clearcoat: 0.2,
        clearcoatRoughness: 0.1,
        ior: 1.5,
        transmission: 0,

        // 性能设置
        side: THREE.FrontSide,
        transparent: false,
        alphaTest: 0.5
    });

    // 设置贴图重复和包装
    [albedo, normal, roughness, metalness, ao].forEach(texture => {
        texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
        texture.repeat.set(2, 2);
    });

    return material;
}

// 创建物体
const geometry = new THREE.SphereGeometry(1, 32, 32);
const material = createPBRMaterial();
const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);

// 设置环境
new THREE.RGBELoader()
    .load('environment.hdr', function(texture) {
        texture.mapping = THREE.EquirectangularReflectionMapping;
        scene.environment = texture;
        material.envMap = texture;
        material.envMapIntensity = 1.0;
    });

1.7. 常见问题解决

1.7.1. 材质过暗

javascript

// 增加环境光强度
material.envMapIntensity = 2.0;

// 或调整曝光
renderer.toneMappingExposure = 1.5;

// 或添加补光
const fillLight = new THREE.DirectionalLight(0xffffff, 0.3);

1.7.2. 反射过强

javascript

// 调整粗糙度
material.roughness = 0.8;

// 降低环境贴图强度
material.envMapIntensity = 0.5;

// 使用菲涅尔衰减
material.specularIntensity = 0.5;

1.7.3. 性能问题

javascript

// 减少贴图分辨率
textureLoader.load('texture.jpg', texture => {
    texture.minFilter = THREE.LinearMipmapLinearFilter;
    texture.magFilter = THREE.LinearFilter;
    texture.generateMipmaps = true;
});

// 使用更简单的材质
material.precision = 'mediump';

1.8. 最佳实践

  1. 使用正确的贴图格式:HDR用于环境,PNG/JPG用于材质

  2. 保持能量守恒:高金属度配合低粗糙度,非金属反之

  3. 合理使用法线贴图:避免过度使用导致视觉失真

  4. 优化贴图分辨率:根据物体在画面中的大小决定

  5. 统一光照单位:使用物理正确的光照强度和单位

通过合理配置PBR材质参数和贴图,配合适当的光照环境,可以在Three.js中实现高度逼真的渲染效果。