每个色彩空间都是多个设计决策的集合,这些设计决策一起选择以支持多种颜色,同时满足与精度和显示技术相关的技术限制。创建 3D 资源或将 3D 资源组装到场景中时,了解这些属性是什么以及一种颜色空间的属性如何与场景中的其他颜色空间相关非常重要。
考虑两种非常常见的颜色空间: [page:SRGBColorSpace] ("sRGB") 和 [page:LinearSRGBColorSpace] ("Linear-sRGB")。两者都使用相同的原色和白点,因此具有相同的色域。两者都使用 RGB 颜色模型。它们仅在传递函数上有所不同 - Linear-sRGB 相对于物理光强度是线性的。sRGB 使用非线性 sRGB 传输函数,更接近于人眼感知光的方式以及常见显示设备的响应能力。
这种差异很重要。照明计算和其他渲染操作通常必须在线性色彩空间中进行。然而,线性颜色在图像或帧缓冲区中存储的效率较低,并且在人类观察者观看时看起来不正确。因此,输入纹理和最终渲染图像通常将使用非线性 sRGB 颜色空间。
ℹ️ 注意: 虽然一些现代显示器支持更宽的色域(例如 Display-P3),但 Web 平台的图形 API 在很大程度上依赖于 sRGB。如今使用 Three.js 的应用程序通常仅使用 sRGB 和 Linear-sRGB 颜色空间。
现代渲染方法所需的线性工作流程通常涉及多个颜色空间,每个颜色空间分配给一个特定的角色。线性和非线性颜色空间适合不同的角色,如下所述。
提供给 Three.js 的颜色(来自颜色选择器、纹理、3D 模型和其他来源)每种颜色都有一个关联的颜色空间。那些尚未在 Linear-sRGB 工作色彩空间中的纹理必须进行转换,并为纹理指定正确的 texture.colorSpace 分配。如果在初始化颜色之前启用了 THREE.ColorManagement API,则可以自动进行某些转换(对于 sRGB 中的十六进制和 CSS 颜色):
THREE.ColorManagement.enabled = true;
⚠️ 警告: 许多 3D 模型格式无法正确或一致地定义色彩空间信息。虽然 Three.js 尝试处理大多数情况,但较旧的文件格式很常见问题。为了获得最佳结果,请使用 glTF 2.0 ([page:GLTFLoader]) 并尽早在在线查看器中测试 3D 模型,以确认资产本身是正确的。
渲染、插值和许多其他操作必须在开放域线性工作色彩空间中执行,其中 RGB 分量与物理照明成正比。在 Three.js 中,工作色彩空间是 Linear-sRGB。
输出到显示设备、图像或视频可能涉及从开放域 Linear-sRGB 工作色彩空间到另一个色彩空间的转换。此转换可以在主渲染通道 ([page:WebGLRenderer.outputColorSpace]) 中或在后处理期间执行。
renderer.outputColorSpace = THREE.SRGBColorSpace; // optional with post-processing
⚠️ 警告: 渲染目标可以使用 sRGB 或 Linear-sRGB。sRGB 更好地利用了有限的精度。在封闭域中,8 位通常足以满足 sRGB,而 Linear-sRGB 可能需要 ≥12 位(半浮点)。如果后面的管道阶段需要 Linear-sRGB 输入,则额外的转换可能会产生较小的性能成本。
基于 [page:ShaderMaterial] 和 [page:RawShaderMaterial] 的自定义材质必须实现自己的输出颜色空间转换。对于 的实例 `ShaderMaterial`,将 `colorspace_fragment` 着色器块添加到片段着色器的函数 `main()` 应该就足够了。
读取或修改 [page:Color] 实例的方法假定数据已位于 Three.js 工作色彩空间 Linear-sRGB 中。RGB 和 HSL 分量是 Color 实例存储的数据的直接表示,并且永远不会隐式转换。可以使用 .convertLinearToSRGB() 或 .convertSRGBToLinear() 显式转换颜色数据。
// RGB components (no change).
color.r = color.g = color.b = 0.5;
console.log( color.r ); // → 0.5
// Manual conversion.
color.r = 0.5;
color.convertSRGBToLinear();
console.log( color.r ); // → 0.214041140
设置 ColorManagement.enabled = true (推荐) 时,会自动进行某些转换。由于十六进制和 CSS 颜色通常是 sRGB,因此 [page:Color] 方法会在 setter 中自动将这些输入从 sRGB 转换为 Linear-sRGB,或者在从 getter 返回十六进制或 CSS 输出时从 Linear-sRGB 转换为 sRGB。
// Hexadecimal conversion.
color.setHex( 0x808080 );
console.log( color.r ); // → 0.214041140
console.log( color.getHex() ); // → 0x808080
// CSS conversion.
color.setStyle( 'rgb( 0.5, 0.5, 0.5 )' );
console.log( color.r ); // → 0.214041140
// Override conversion with 'colorSpace' argument.
color.setHex( 0x808080, LinearSRGBColorSpace );
console.log( color.r ); // → 0.5
console.log( color.getHex( LinearSRGBColorSpace ) ); // → 0x808080
console.log( color.getHex( SRGBColorSpace ) ); // → 0xBCBCBC
当单个颜色或纹理配置错误时,它会显得比预期更暗或更亮。当渲染器的输出色彩空间配置错误时,整个场景可能会显得更暗(例如,缺少到 sRGB 的转换)或更亮(例如,通过后处理双重转换到 sRGB)。在每种情况下,问题可能并不统一,并且简单地增加/减少照明并不能解决问题。
当 输入色彩空间 和 输出色彩空间 都不正确时, 会出现一个更微妙的问题- 整体亮度水平可能很好,但在不同的照明下颜色可能会发生意外变化,或者阴影可能会比预期的更加过度且不那么柔和。这两个错误并不能构成正确,重要的是工作颜色空间是线性的(“场景参考”)和输出颜色空间是非线性的(“显示参考”)。