Three.js,给纹理,设颜色空间

前言

在Three.js中设置材质纹理时,颜色空间的选择会显著影响最终渲染效果。

当我们在编辑器中直接设置颜色值时,可能会发现实际显示效果与预期存在偏差 - 比如设置50%灰度的颜色,在线性颜色空间下(按照物理光强均匀分布)实际显示会比预期更亮,这是因为人眼对光强的感知是非线性的(约18%物理光强就会被感知为50%亮度)。

正确处理纹理颜色空间是保证3D场景视觉效果真实性的关键因素.

上篇文章讲到纹理纹理Texture是指图像中表面特征的模式或结构,它描述了图像中的细节,如粗糙、平 滑、条纹、波纹等。),这篇文章讲一下纹理颜色空间

正文

一、颜色空间基础概念

1.1 为什么需要颜色空间

人眼对光强的感知是非线性的。实验表明,当物理光强达到约18%时,人眼已经感知为"一半亮度"。这种特性使得我们需要两种不同的颜色表示方式:

  • 线性颜色空间:按照实际光强均匀分布(物理准确)
  • sRGB颜色空间:按照人眼感知均匀分布(视觉准确)

为了解决这个问题,Three.js提供了sRGB颜色空间模式,它能按照人眼感知特性来校正颜色表现。

当我们将纹理的colorSpace设置为THREE.SRGBColorSpace后,50%灰度的颜色会正确显示为视觉上的中间灰度。

javascript 复制代码
// 三种颜色空间枚举
THREE.NoColorSpace      // 无颜色空间处理
THREE.SRGBColorSpace   // sRGB颜色空间(感知均匀)
THREE.LinearSRGBColorSpace // 线性化的sRGB(计算用)

texture.colorSpace = THREE.SRGBColorSpacesRGB颜色空间(感知均匀):

texture.colorSpace = THREE.LinearSRGBColorSpace:线性化的sRGB(计算用);

texture.colorSpace = THREE.NoColorSpace:无颜色空间处理;

1.2 常见纹理的颜色空间需求

纹理类型 推荐颜色空间 说明
漫反射贴图 SRGBColorSpace 存储视觉颜色信息
法线贴图 NoColorSpace 存储向量数据
金属/粗糙度贴图 NoColorSpace 存储物理参数
HDR环境贴图 NoColorSpace 已经包含线性数据

二、Three.js中的实现

2.1 基础设置

以下是一个配置纹理颜色空间的完整示例:

javascript 复制代码
// 初始化场景
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.outputColorSpace = THREE.SRGBColorSpace; // 设置渲染输出为sRGB

// 加载纹理并设置颜色空间
const textureLoader = new THREE.TextureLoader();
const diffuseMap = textureLoader.load('textures/diffuse.jpg');
diffuseMap.colorSpace = THREE.SRGBColorSpace; // 漫反射贴图使用sRGB

const normalMap = textureLoader.load('textures/normal.jpg');
normalMap.colorSpace = THREE.NoColorSpace; // 法线贴图不使用颜色空间

2.2 动态切换颜色空间

通过GUI控件可以实时观察不同颜色空间的效果差异:

javascript 复制代码
const gui = new GUI();
const params = {
    colorSpace: 'sRGB'
};

gui.add(params, 'colorSpace', ['sRGB', 'Linear', 'None'])
   .onChange((value) => {
       switch(value) {
           case 'sRGB': 
               texture.colorSpace = THREE.SRGBColorSpace;
               break;
           case 'Linear':
               texture.colorSpace = THREE.LinearSRGBColorSpace;
               break;
           case 'None':
               texture.colorSpace = THREE.NoColorSpace;
               break;
       }
       material.needsUpdate = true; // 必须更新材质
   });

三、关键技术细节

3.1 颜色空间转换流程

Three.js内部的颜色处理流程如下:

  1. 输入纹理根据texture.colorSpace设置被转换为线性空间
  2. 所有光照计算在线性空间中进行
  3. 最终输出根据renderer.outputColorSpace转换
javascript 复制代码
// 典型的工作流程
texture.colorSpace = THREE.SRGBColorSpace; // 输入转换
renderer.outputColorSpace = THREE.SRGBColorSpace; // 输出转换

3.2 必须注意的坑

  1. 材质更新 :更改颜色空间后必须设置material.needsUpdate = true
  2. 性能影响:颜色空间转换会增加着色器复杂度
  3. HDR处理 :HDR纹理应保持NoColorSpace
  4. 后期处理:自定义着色器中需要手动处理颜色空间转换
javascript 复制代码
// 在自定义着色器中处理sRGB纹理
uniform sampler2D sRGBTexture;

void main() {
    vec4 texColor = texture2D(sRGBTexture, vUv);
    texColor.rgb = pow(texColor.rgb, vec3(2.2)); // sRGB转线性
    // ...其他计算
}

四、实践建议

  1. 默认规则

    • 视觉颜色纹理:SRGBColorSpace
    • 数据纹理:NoColorSpace
  2. 调试技巧

    javascript 复制代码
    // 快速检查颜色空间问题
    function debugColorSpace() {
        console.log('Current color space:', texture.colorSpace);
        texture.colorSpace = texture.colorSpace === THREE.SRGBColorSpace 
            ? THREE.NoColorSpace 
            : THREE.SRGBColorSpace;
        material.needsUpdate = true;
    }
  3. 性能优化

    • 对于静态场景,可以预先把纹理转换为目标颜色空间
    • 使用NoColorSpace避免运行时转换

五、完整示例

以下是一个整合了所有概念的完整示例:

javascript 复制代码
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

// 初始化
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 设置渲染器输出
renderer.outputColorSpace = THREE.SRGBColorSpace;

// 加载纹理
const textureLoader = new THREE.TextureLoader();
const diffuseTexture = textureLoader.load('diffuse.jpg');
diffuseTexture.colorSpace = THREE.SRGBColorSpace;

// 创建材质
const material = new THREE.MeshStandardMaterial({
    map: diffuseTexture,
    roughness: 0.5,
    metalness: 0.5
});

// 创建立方体
const geometry = new THREE.BoxGeometry(1, 1, 1);
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// 添加光源
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1);
scene.add(light);

// GUI控制
const gui = new GUI();
const textureParams = {
    colorSpace: 'sRGB'
};

gui.add(textureParams, 'colorSpace', ['sRGB', 'Linear', 'None'])
   .onChange((val) => {
       diffuseTexture.colorSpace = 
           val === 'sRGB' ? THREE.SRGBColorSpace :
           val === 'Linear' ? THREE.LinearSRGBColorSpace :
           THREE.NoColorSpace;
       material.needsUpdate = true;
   });

// 动画循环
function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
}
animate();

总结

通过合理配置颜色空间,可以确保3D场景的视觉效果既符合物理规律,又能满足人眼的感知特性。

需要注意的是,在运行时切换颜色空间后,必须设置material.needsUpdate = true来触发材质重新编译,否则更改不会生效。

Three.js最新版本已统一使用SRGBColorSpace这个命名,开发者应注意API命名的更新变化。

通过正确配置颜色空间,可以确保3D场景的颜色表现更符合人眼视觉预期。

相关推荐
华仔啊2 小时前
前端必看!12个JS神级简写技巧,代码效率直接飙升80%,告别加班!
前端·javascript
excel2 小时前
dep.ts 逐行解读
前端·javascript·vue.js
爱上妖精的尾巴2 小时前
5-20 WPS JS宏 every与some数组的[与或]迭代(数组的逻辑判断)
开发语言·前端·javascript·wps·js宏·jsa
excel2 小时前
Vue3 响应式核心源码全解析:Dep、Link 与 track/trigger 完整执行机制详解
前端
前端大卫2 小时前
一个关于时区的线上问题
前端·javascript·vue.js
whltaoin3 小时前
中秋赏月互动页面:用前端技术演绎传统节日之美
前端·javascript·html·css3·中秋主题前端
IT派同学3 小时前
TableWiz诞生记:一个被表格合并逼疯的程序员如何自救
前端·vue.js
西洼工作室5 小时前
CSS高效开发三大方向
前端·css
昔人'5 小时前
css`font-variant-numeric: tabular-nums` 用来控制数字的样式。
前端·css
铅笔侠_小龙虾6 小时前
动手实现简单Vue.js ,探索Vue原理
前端·javascript·vue.js