Three.js 纹理材质高级操作

56 阅读9分钟

概述

本文档将介绍Three.js中纹理材质的高级操作,包括纹理变换、过滤、加载不同格式纹理、色调映射、深度处理、混合模式、裁剪平面和模板渲染等重要概念。通过这些知识点,您将能够创建更加专业和富有表现力的3D场景。

第一部分:纹理变换 - 位移、旋转、缩放、重复

1. 纹理重复(Repeat)

纹理重复允许将纹理在UV坐标空间中重复铺设,可以创建无缝的图案效果。

// 设置纹理重复
texture.repeat.set(4, 4);  // 水平和垂直方向各重复4次

// 设置水平重复
texture.wrapS = THREE.RepeatWrapping;
// 设置垂直重复
texture.wrapT = THREE.RepeatWrapping;

// 镜像重复
texture.wrapS = THREE.MirroredRepeatWrapping;

2. 纹理偏移(Offset)

纹理偏移允许移动纹理在模型表面上的起始位置。

// 设置纹理偏移
texture.offset.set(0.5, 0.5);  // 水平和垂直方向偏移50%

3. 纹理旋转(Rotation)

纹理旋转允许围绕纹理中心点旋转纹理。

// 设置纹理旋转中心
texture.center.set(0.5, 0.5);  // 中心点
texture.rotation = Math.PI / 4; // 旋转45度

第二部分:纹理纵向翻转与预乘Alpha

1. 纹理纵向翻转

某些纹理在加载时可能会出现上下颠倒的情况,可以通过flipY属性来修正。

// 翻转纹理Y轴
texture.flipY = false;  // 默认为true,设为false可取消翻转

2. 预乘Alpha

预乘Alpha(Premultiply Alpha)处理纹理的透明通道,对透明效果的渲染质量有重要影响。

// 设置预乘Alpha
texture.premultiplyAlpha = true;  // 或false

// 通过GUI控制预乘Alpha
gui
  .add(texture, "premultiplyAlpha")
  .name("premultiplyAlpha")
  .onChange(() => {
    texture.needsUpdate = true;  // 需要更新纹理
  });

第三部分:Mipmap技术

1. Mipmap概述

Mipmap是纹理的多个分辨率版本的集合,用于提高渲染质量和性能。当物体远离相机时,使用较低分辨率的纹理版本。

// 不同的过滤方式
// 直接取映射到的最近的像素
texture.magFilter = THREE.NearestFilter;
// 取映射到的最近的四个像素的平均值
texture.magFilter = THREE.LinearFilter;

// 最小化过滤器
texture.minFilter = THREE.LinearMipMapLinearFilter;  // 推荐的高质量过滤器
texture.minFilter = THREE.LinearMipMapNearestFilter;
texture.minFilter = THREE.NearestMipMapLinearFilter;
texture.minFilter = THREE.NearestMipMapNearestFilter;

2. 生成Mipmap

// 控制是否生成Mipmap
texture.generateMipmaps = false;  // 设为false可禁用Mipmap

第四部分:各项异性过滤解决倾斜模糊问题

1. 各项异性过滤(Anisotropic Filtering)

当纹理以倾斜角度显示时,普通过滤可能导致模糊,各项异性过滤可以改善这种情况。

// 获取硬件支持的最大各项异性级别
let maxAnisotropy = renderer.capabilities.getMaxAnisotropy();
console.log(maxAnisotropy);

// 设置各项异性级别
texture.anisotropy = 4;  // 值越大质量越高,但性能开销也越大

第五部分:Three.js中使用KTX2、DDS、TGA纹理

1. KTX2格式

KTX2是一种现代纹理格式,支持多种压缩算法和高级功能。

import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader.js";

// KTX2加载器
let ktx2Loader = new KTX2Loader()
  .setTranscoderPath("basis/")  // 设置解码器路径
  .detectSupport(renderer);     // 检测支持情况

let ktx2Texture = ktx2Loader.load(
  "./texture/opt/ktx2/texture.ktx2",
  (texture) => {
    console.log("ktx2", texture);
    texture.mapping = THREE.EquirectangularReflectionMapping;
    texture.anisotropy = 16;
    texture.needsUpdate = true;
    scene.background = texture;
    scene.environment = texture;
  }
);

2. DDS格式

DDS是一种传统的压缩纹理格式,常用于游戏开发。

import { DDSLoader } from "three/examples/jsm/loaders/DDSLoader.js";

// DDS加载器
let ddsLoader = new DDSLoader();
let ddsTexture = ddsLoader.load(
  "./texture/opt/env/texture.dds",
  (texture) => {
    console.log("dds", texture);
    texture.mapping = THREE.EquirectangularReflectionMapping;
    texture.flipY = true;  // DDS纹理可能需要翻转
    texture.needsUpdate = true;
    scene.background = texture;
    scene.environment = texture;
    texture.magFilter = THREE.LinearFilter;
    texture.minFilter = THREE.LinearMipMapLinearFilter;
    texture.anisotropy = 16;
  }
);

3. TGA格式

TGA是一种较老但仍广泛使用的纹理格式。

import { TGALoader } from "three/addons/loaders/TGALoader.js";

// TGA加载纹理
let tgaLoader = new TGALoader();
tgaLoader.load(
  "./texture/opt/env/texture.tga",
  (texture) => {
    texture.mapping = THREE.EquirectangularReflectionMapping;
    console.log("tga", texture);
    scene.background = texture;
    scene.environment = texture;
  }
);

第六部分:背景色调映射

1. 色调映射概述

色调映射将高动态范围(HDR)的光照值转换为适合显示的低动态范围值。

// 设置色调映射
renderer.toneMapping = THREE.ACESFilmicToneMapping;

// 设置色调映射曝光度
renderer.toneMappingExposure = 1;

// 通过GUI控制色调映射
gui.add(renderer, "toneMapping", {
  // 无色调映射
  No: THREE.NoToneMapping,
  // 线性色调映射
  Linear: THREE.LinearToneMapping,
  // Reinhard色调映射。这是一种更复杂的色调映射方式,可以更好地处理高亮度的区域。
  Reinhard: THREE.ReinhardToneMapping,
  // Cineon色调映射。这种方法起源于电影行业,尝试模仿电影胶片的颜色响应。
  Cineon: THREE.CineonToneMapping,
  // ACES Filmic色调映射。模仿电影行业中常用的色调映射算法。
  ACESFilmic: THREE.ACESFilmicToneMapping,
});

// 控制曝光度
gui.add(renderer, "toneMappingExposure", 0, 3, 0.1);

2. 常用色调映射算法

  • NoToneMapping: 无色调映射
  • LinearToneMapping: 线性色调映射
  • ReinhardToneMapping: Reinhard色调映射
  • CineonToneMapping: Cineon色调映射
  • ACESFilmicToneMapping: ACES Filmic色调映射(推荐用于电影级渲染)

第七部分:EXR、TIF、PNG动态范围图片

1. EXR格式

EXR是一种专业的高动态范围图像格式,广泛用于电影和视觉特效行业。

import { EXRLoader } from "three/addons/loaders/EXRLoader.js";

// EXR加载器
let exrLoader = new EXRLoader();
exrLoader.load(
  "./texture/opt/env/texture.exr",
  (texture) => {
    console.log("exr", texture);
    texture.mapping = THREE.EquirectangularReflectionMapping;
    scene.background = texture;
    scene.environment = texture;
  }
);

2. 其他高动态范围格式

// TIF LogLuv格式
import { LogLuvLoader } from "three/addons/loaders/LogLuvLoader.js";

let logLuvLoader = new LogLuvLoader();
logLuvLoader.load("./texture/opt/env/texture.tif", (texture) => {
  console.log("tif", texture);
  scene.background = texture;
  scene.environment = texture;
});

// RGBM格式
import { RGBMLoader } from "three/addons/loaders/RGBMLoader.js";

let rgbmLoader = new RGBMLoader();
rgbmLoader.load("./texture/opt/env/texture.png", (texture) => {
  scene.background = texture;
  scene.environment = texture;
});

第八部分:深度模式、深度写入、深度检测

1. 深度模式

深度模式决定像素是否会被渲染,常见的深度模式包括:

// 设置深度模式
plane.material.depthFunc = THREE.LessEqualDepth;  // 小于等于时渲染

// 其他深度模式选项
THREE.NeverDepth      // 永远不渲染
THREE.AlwaysDepth     // 总是渲染
THREE.LessDepth       // 小于时渲染
THREE.LessEqualDepth  // 小于等于时渲染(常用)
THREE.GreaterEqualDepth // 大于等于时渲染
THREE.GreaterDepth    // 大于时渲染
THREE.NotEqualDepth   // 不等于时渲染

2. 深度写入和深度检测

// 深度写入 - 是否将深度信息写入深度缓冲
plane.material.depthWrite = true;

// 深度检测 - 是否进行深度测试
plane.material.depthTest = true;

// 渲染顺序 - 当深度测试相同时,决定渲染顺序
plane.renderOrder = 0;  // 数值越大越后渲染

3. 透明物体的深度处理

对于透明物体,深度处理尤为重要:

// 设置透明材质
plane.material.transparent = true;

// 为透明物体设置合适的深度模式
plane.material.depthFunc = THREE.LessEqualDepth;
plane.material.depthWrite = true;  // 通常设为false以避免深度冲突
plane.material.depthTest = true;

第九部分:混合模式

1. 混合模式概述

混合模式定义了当前像素颜色与帧缓冲中现有颜色的组合方式。

// 基本混合模式
material.blending = THREE.NormalBlending;    // 标准混合
material.blending = THREE.AdditiveBlending;  // 加法混合
material.blending = THREE.SubtractiveBlending; // 减法混合
material.blending = THREE.MultiplyBlending;  // 乘法混合
material.blending = THREE.CustomBlending;    // 自定义混合

2. 自定义混合参数

// 自定义混合
material.blending = THREE.CustomBlending;
material.blendEquation = THREE.AddEquation;           // 混合方程式
material.blendSrc = THREE.SrcAlphaFactor;            // 源因子
material.blendDst = THREE.OneMinusSrcAlphaFactor;    // 目标因子
material.blendEquationAlpha = THREE.AddEquation;     // Alpha混合方程式
material.blendSrcAlpha = THREE.OneFactor;            // 源Alpha因子
material.blendDstAlpha = THREE.OneMinusSrcAlphaFactor; // 目标Alpha因子

3. 混合方程式

  • AddEquation: 源颜色 + 目标颜色
  • SubtractEquation: 源颜色 - 目标颜色
  • ReverseSubtractEquation: 目标颜色 - 源颜色
  • MinEquation: min(源颜色, 目标颜色)
  • MaxEquation: max(源颜色, 目标颜色)

4. 混合因子

  • ZeroFactor: 0
  • OneFactor: 1
  • SrcColorFactor: 源颜色
  • OneMinusSrcColorFactor: 1 - 源颜色
  • SrcAlphaFactor: 源Alpha
  • OneMinusSrcAlphaFactor: 1 - 源Alpha
  • DstColorFactor: 目标颜色
  • OneMinusDstColorFactor: 1 - 目标颜色
  • DstAlphaFactor: 目标Alpha
  • OneMinusDstAlphaFactor: 1 - 目标Alpha

第十部分:裁剪平面

1. 裁剪平面概念

裁剪平面允许隐藏几何体的一部分,创建切片或剖面视图。

// 创建裁剪平面
const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0);  // 法向量为Y轴正方向,距离为0

// 将裁剪平面应用到材质
material.clippingPlanes = [plane];

// 启用局部裁剪
renderer.localClippingEnabled = true;

// 或者将裁剪平面应用到整个渲染器
renderer.clippingPlanes = [plane, plane2];  // 支持多个裁剪平面

2. 裁剪平面参数控制

// 控制裁剪平面的位置
plane.constant = 5;  // 距离原点的距离

// 控制裁剪平面的方向
plane.normal.set(0, 1, 0);  // 设置法向量

// 通过GUI动态控制裁剪平面
const folder = gui.addFolder("裁剪平面");
folder.add(plane, "constant", -10, 10).name("位置");
folder.add(plane.normal, "x", -1, 1).name("法向量x");
folder.add(plane.normal, "y", -1, 1).name("法向量y");
folder.add(plane.normal, "z", -1, 1).name("法向量z");

第十一部分:剪裁场景(Scissor Test)

1. Scissor Test概念

Scissor Test允许在屏幕上定义一个矩形区域,在此区域外的像素将不会被渲染。

// 启用剪裁测试
renderer.setScissorTest(true);

// 定义剪裁区域 (x, y, width, height)
renderer.setScissor(0, 0, window.innerWidth/2, window.innerHeight);

// 渲染第一部分场景
renderer.render(scene, camera);

// 定义另一个剪裁区域
renderer.setScissor(
  window.innerWidth/2,
  0,
  window.innerWidth - window.innerWidth/2,
  window.innerHeight
);

// 渲染第二部分场景
renderer.render(newScene, camera);

// 关闭剪裁测试
renderer.setScissorTest(false);

2. 动态调整剪裁区域

let params = {
  scissorWidth: window.innerWidth / 2,
};

// 在动画循环中使用动态剪裁
function animate() {
  controls.update();
  requestAnimationFrame(animate);
  
  renderer.setScissorTest(true);
  renderer.setScissor(0, 0, params.scissorWidth, window.innerHeight);
  renderer.render(scene, camera);
  
  renderer.setScissor(
    params.scissorWidth,
    0,
    window.innerWidth - params.scissorWidth,
    window.innerHeight
  );
  renderer.render(newScene, camera);
  
  renderer.setScissorTest(false);
}

// 通过GUI控制剪裁宽度
gui.add(params, "scissorWidth", 0, window.innerWidth);

第十二部分:模板渲染

1. 模板缓冲概念

模板缓冲(Stencil Buffer)允许基于模板测试的结果来决定是否绘制像素,常用于创建特殊效果。

// 模板缓冲设置参数
const material = new THREE.MeshPhysicalMaterial({
  stencilWrite: true,              // 启用模板写入
  stencilRef: 2,                   // 模板参考值
  stencilWriteMask: 0xff,          // 模板写入掩码
  stencilFunc: THREE.EqualStencilFunc, // 模板比较函数
  stencilZPass: THREE.ReplaceStencilOp, // 深度测试通过时的操作
  depthTest: false,                // 是否进行深度测试
});

2. 模板比较函数

  • THREE.NeverStencilFunc: 永远不通过
  • THREE.LessStencilFunc: 小于时通过
  • THREE.EqualStencilFunc: 等于时通过
  • THREE.LessEqualStencilFunc: 小于等于时通过
  • THREE.GreaterStencilFunc: 大于时通过
  • THREE.NotEqualStencilFunc: 不等于时通过
  • THREE.GreaterEqualStencilFunc: 大于等于时通过
  • THREE.AlwaysStencilFunc: 总是通过

3. 模板操作

  • THREE.KeepStencilOp: 保持当前值
  • THREE.ZeroStencilOp: 设置为0
  • THREE.ReplaceStencilOp: 替换为参考值
  • THREE.IncrementStencilOp: 增加1
  • THREE.DecrementStencilOp: 减少1
  • THREE.InvertStencilOp: 按位取反

4. 金属剖切面效果示例

// 前侧面材质
const frontMaterial = new THREE.MeshPhysicalMaterial({
  side: THREE.FrontSide,  // 只渲染前侧面
});

// 后侧面材质 - 使用模板缓冲
const backMaterial = new THREE.MeshBasicMaterial({
  side: THREE.BackSide,        // 只渲染后侧面
  color: 0xffcccc,
  stencilWrite: true,          // 启用模板写入
  stencilRef: 1,               // 设置参考值
  stencilWriteMask: 0xff,      // 写入掩码
  stencilZPass: THREE.ReplaceStencilOp, // 深度测试通过时替换
});

// 裁剪平面
const clipPlane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0);
frontMaterial.clippingPlanes = [clipPlane];
backMaterial.clippingPlanes = [clipPlane];

// 启用局部裁剪
renderer.localClippingEnabled = true;

// 剖切面材质 - 只在模板值匹配时渲染
const sectionMaterial = new THREE.MeshPhysicalMaterial({
  color: 0xccccff,
  metalness: 0.95,
  roughness: 0.1,
  stencilWrite: true,
  stencilRef: 1,                    // 与后侧面相同的参考值
  stencilFunc: THREE.EqualStencilFunc, // 等于参考值时通过
});

总结

通过本教程,我们学习了Three.js中纹理材质的高级操作:

  1. 纹理变换:掌握纹理的重复、偏移、旋转等变换操作
  2. 纹理格式:了解DDS、KTX2、TGA、EXR等高级纹理格式的使用
  3. Mipmap技术:理解Mipmap的工作原理和不同过滤器的选择
  4. 各项异性过滤:解决纹理倾斜时的模糊问题
  5. 色调映射:处理高动态范围图像的渲染
  6. 深度处理:控制物体的深度测试、写入和渲染顺序
  7. 混合模式:实现透明、叠加等视觉效果
  8. 裁剪平面:创建切片和剖面视图
  9. 模板渲染:利用模板缓冲实现高级渲染效果

这些高级技术可以帮助您创建更专业、更具视觉冲击力的3D场景。在实际应用中,这些技术往往需要结合使用,以达到最佳的视觉效果和性能平衡。