news 2026/9/3 15:32:45

Three.js 颜色管理:别让你的 3D 场景忽明忽暗、颜色发灰

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Three.js 颜色管理:别让你的 3D 场景忽明忽暗、颜色发灰

Three.js 颜色管理:别让你的 3D 场景忽明忽暗、颜色发灰

原文出处:Three.js Manual – Color Management

本文基于官方手册「Color Management」章节整理,用通俗方式带你看懂:为什么同样是红色,贴进场景里就变暗了?为什么开了后期处理画面反而发白?

写在前面

如果你写过几个 Three.js 场景,大概率撞见过下面这些诡异现象:

  • 用取色器挑了个漂亮的红,贴上去却发暗、发灰;
  • 加了EffectComposer后期处理之后,整体画面突然「过曝」变白;
  • 法线贴图、粗糙度贴图看着一切正常,但颜色贴图一换就出问题。

这些都不是你眼睛的问题,也不是材质写错了——根子都在颜色管理(Color Management)。这一篇我们就把这件事聊透:颜色空间到底是什么、Three.js 里每一类数据该用什么空间、以及在代码里怎么配置才不踩坑。

一、先搞懂:什么是「颜色空间」

说白了,一个颜色空间就是一组「设计决策」的组合,目的是在有限精度下表示尽量多的颜色,同时适配显示设备的物理限制。它通常由三块拼起来:

  • 原色(Color primaries):红绿蓝并不是绝对的,而是从可见光谱里挑出来的。颜色就是这三个原色的比例。
  • 白点(White point)R=G=B时人眼应该看到「没有颜色」的白或灰。sRGB 的白点定在D65(标准日光)。
  • 传递函数(Transfer functions):数值怎么映射到实际亮度?r=0.5是物理亮度少一半,还是人眼觉得「暗一半」?这两者不一样,需要用数学函数描述。sRGB 用的是非线性传递函数(常被近似成 gamma,但官方建议别用「gamma」这个词,容易歧义)。

加上两个常用术语:

  • 颜色模型(Color model):怎么用数字表示颜色,比如 RGB 模型用r,g,b ∈ [0,1](闭区间)或[0,∞](开区间,能表示超亮的高动态范围)。
  • 色域(Color gamut):选定原色和白点后,能表示的颜色范围就是一个「体积」。超出这个体积的叫「超出色域」,闭区间 RGB 表示不了。

重点来了:Three.js 里最常打交道的SRGBColorSpace(sRGB)LinearSRGBColorSpace(Linear-sRGB),它们的原色和白点完全一样,色域也相同,唯一的区别就在传递函数

  • LinearSRGBColorSpace:相对物理光强是线性的;
  • SRGBColorSpace:非线性,更接近人眼感知和常见显示器的表现。

为什么要区分?因为光照计算等渲染操作必须在线性空间里做。但线性颜色存进图片/帧缓冲很浪费精度,人眼直接看也不对劲。所以结论很清晰:输入贴图和最终渲染图一般用非线性 sRGB,而渲染计算过程用线性 Linear-sRGB。

ℹ️ 提示:虽然现在有 Display-P3 等更广色域的屏幕,但 Web 图形 API 基本还是基于 sRGB。用 Three.js 一般只需关心 sRGB 和 Linear-sRGB 这两个。

二、颜色空间的三种「角色」

线性工作流(现代渲染必备)通常会涉及不止一个颜色空间,各自有固定职责。

1. 输入颜色空间(Input)

给 Three.js 的颜色——取色器、贴图、3D 模型——都有自己的颜色空间。不在 Linear-sRGB 工作空间的,都得先转换,贴图还要标上正确的texture.colorSpace。开启下面这行后,十六进制和 CSS 颜色会自动转:

THREE.ColorManagement.enabled=true;

注意:这个开关默认就是true,但显式写出来是个好习惯。

不同数据的归属:

数据类型所在颜色空间怎么写
材质/灯光/着色器里的颜色Linear-sRGB自动(工作空间)
顶点颜色BufferAttributeLinear-sRGB自动
颜色贴图(PNG/JPEG 的.map.emissiveMapsRGB(闭区间)texture.colorSpace = SRGBColorSpace
浮点数据贴图(OpenEXR 的.envMap.lightMapLinear-sRGB(开区间,可超 1)texture.colorSpace = LinearSRGBColorSpace
非颜色贴图(.normalMap.roughnessMap无颜色空间texture.colorSpace = NoColorSpace

⚠️ 警告:很多 3D 模型格式没有正确、一致地标注颜色空间,老格式尤其容易出问题。建议优先用glTF 2.0(GLTFLoader,并尽早用在线查看器验证资源本身是否正确。

2. 工作颜色空间(Working)

渲染、插值等计算,必须在开区间、线性的 Linear-sRGB工作空间里进行——这里的 RGB 分量正比于物理光照。Three.js 的工作空间固定是LinearSRGBColorSpace

3. 输出颜色空间(Output)

最终输出到屏幕、图片或视频时,要从 Linear-sRGB 工作空间转换出去,由WebGLRenderer.outputColorSpace决定:

  • 显示:写到 WebGL canvas 的颜色应该是 sRGB;
  • 图片:PNG/JPEG 成品一般用 sRGB;含发光、光照图等超出[0,1]的数据则用开区间 Linear-sRGB(如 OpenEXR)。
renderer.outputColorSpace=THREE.SRGBColorSpace;// 默认值,通常不用改

用了后期处理(post-processing)就必须加OutputPass,否则输出转换不会自动发生。另外,渲染到 RenderTarget 时,sRGB 更省精度(8 位常够用),Linear-sRGB 往往需要 ≥12 位(half float)。

如果你自己写ShaderMaterial/RawShaderMaterial,输出颜色空间的转换得自己实现。对ShaderMaterial,在片元着色器main()里加上colorspace_fragment着色器块通常就够了。

三、操作 THREE.Color 实例

Color实例的方法默认假设数据已经在 Three.js 的工作空间(Linear-sRGB)里,读写都不会隐式转换。需要手动转就用:

// RGB 分量直接读写,不转换color.r=color.g=color.b=0.5;console.log(color.r);// → 0.5// 手动转换color.r=0.5;color.convertSRGBToLinear();console.log(color.r);// → 0.214041140

开启ColorManagement(推荐)后,因为十六进制和 CSS 颜色本质是 sRGB,setter 会自动把输入从 sRGB 转成 Linear-sRGB,getter 返回十六进制/CSS 时又会转回去:

// 十六进制:内部自动存成线性值color.setHex(0x808080);console.log(color.r);// → 0.214041140console.log(color.getHex());// → 0x808080// CSScolor.setStyle('rgb( 0.5, 0.5, 0.5 )');console.log(color.r);// → 0.214041140// 用 colorSpace 参数覆盖默认转换color.setHex(0x808080,THREE.LinearSRGBColorSpace);console.log(color.r);// → 0.5console.log(color.getHex(THREE.LinearSRGBColorSpace));// → 0x808080console.log(color.getHex(THREE.SRGBColorSpace));// → 0xBCBCBC

小坑提醒:上面代码里getHex(SRGBColorSpace)返回0xBCBCBC而不是0x808080,因为「线性 0.5」的 sRGB 编码值本来就是约 0.737,即0xBC。这正说明内部存的是线性值。

四、最常见的几个坑

颜色配置错了,表现很典型:

  • 单个颜色或贴图配错→ 它比预期更暗或更亮(比如颜色贴图忘了设SRGBColorSpace,就会被当成线性值读进来,整体发暗)。
  • 渲染器输出空间配错→ 整个场景出问题:
    • 缺了到 sRGB 的转换 →整屏偏暗
    • 后期处理里又多转了一次 sRGB(没加OutputPass却手动转了)→整屏偏亮/过曝
  • 这类问题不均匀,单纯调亮/调暗灯光是解决不了的。

还有一种更隐蔽的:输入空间和输出空间同时配错。这时整体亮度可能「看起来正常」,但颜色会在不同光照下莫名变化,阴影也显得更「硬」、不够柔和。两错并不能抵一对——工作空间必须线性(scene-referred),输出空间必须非线性(display-referred),这是铁律。

总结

一句话记住颜色管理的核心分工:

角色颜色空间关键点
输入(颜色贴图)sRGBtexture.colorSpace = SRGBColorSpace
输入(法线/粗糙度等)texture.colorSpace = NoColorSpace
工作(计算)Linear-sRGBThree.js 固定,别手动改
输出(屏幕)sRGBrenderer.outputColorSpace,后期处理要OutputPass
颜色实例Linear-sRGB开启ColorManagement后 hex/CSS 自动转

实用 checklist:颜色发暗→检查颜色贴图的colorSpace;整屏过曝→检查后期处理有没有OutputPass;模型颜色怪→优先用 glTF 2.0 并早验证。

延伸阅读

  • three.js docs – ColorManagement
  • three.js docs – WebGLRenderer.outputColorSpace
  • three.js docs – Texture.colorSpace
  • GPU Gems 3: The Importance of Being Linear
  • What every coder should know about gamma
  • The Hitchhiker’s Guide to Digital Color
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 15:32:35

从一张商品图到全套电商视觉内容:AI生成工作流实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 15:31:36

MinimaxH3音频驱动数字人:口型同步原理与ComfyUI本地部署实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 15:30:18

AI短剧自动化:从故事到成片的工程流水线

AI短剧自动化最容易被低估的地方,是它本质上不是一个大模型在“生成剧”,而是一条由文本理解、语音合成、视觉素材生成和视频剪辑串联起来的工程流水线。所谓“从故事到成片一键搞定”,在当前可实现方案里往往不是一条提示词就出片&#xff0…

作者头像 李华
网站建设 2026/9/3 15:27:33

从零构建AI Agent平台:核心架构、技术选型与工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/3 15:26:39

Python爬虫与数据分析实战:从豆瓣电影到可视化仪表盘

简介:本资源是一套完整可用的毕业设计项目源码,面向计算机及相关专业本科生,专为毕业设计、课程设计或期末大作业打造,解决从数据采集、清洗、分析到可视化呈现的全流程实践需求。压缩包共111个文件,含5个核心Python爬…

作者头像 李华