color-management.html 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. <!DOCTYPE html><html lang="zh"><head>
  2. <meta charset="utf-8">
  3. <title>颜色管理</title>
  4. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  5. <meta name="twitter:card" content="summary_large_image">
  6. <meta name="twitter:site" content="@threejs">
  7. <meta name="twitter:title" content="Three.js - 颜色管理">
  8. <meta property="og:image" content="https://threejs.org/files/share.png">
  9. <link rel="shortcut icon" href="../../files/favicon_white.ico" media="(prefers-color-scheme: dark)">
  10. <link rel="shortcut icon" href="../../files/favicon.ico" media="(prefers-color-scheme: light)">
  11. <link rel="stylesheet" href="../resources/lesson.css">
  12. <link rel="stylesheet" href="../resources/lang.css">
  13. <link rel="stylesheet" href="/manual/zh/lang.css">
  14. <script type="importmap">
  15. {
  16. "imports": {
  17. "three": "../../build/three.module.js"
  18. }
  19. }
  20. </script>
  21. <style>
  22. blockquote {
  23. font-size: 0.8em;
  24. line-height: 1.5em;
  25. margin-left: 0;
  26. border-left: 4px solid #cccccc;
  27. padding: 1em 2em 1em 2em;
  28. }
  29. blockquote p:first-child {
  30. margin-top: 0;
  31. }
  32. blockquote p:last-child {
  33. margin-bottom: 0;
  34. }
  35. figure {
  36. width: 100%;
  37. margin: 1em 0;
  38. font-style: italic;
  39. }
  40. figure img {
  41. width: 100%;
  42. }
  43. figure.float {
  44. float: right;
  45. max-width: 30%;
  46. margin: 1em;
  47. }
  48. @media all and ( max-width: 640px ) {
  49. figure.float {
  50. float: none;
  51. max-width: 100%;
  52. }
  53. }
  54. </style>
  55. </head>
  56. <body>
  57. <div class="container">
  58. <div class="lesson-title">
  59. <h1>颜色管理</h1>
  60. </div>
  61. <div class="lesson">
  62. <div class="lesson-main">
  63. <h2>什么是色彩空间?</h2>
  64. <p>
  65. 每一种色彩空间,都是一组经过权衡的设计选择。它们共同目标是:
  66. 在满足精度和显示技术限制的前提下,覆盖尽可能大的颜色范围。
  67. 在创建 3D 资源或把多个 3D 资源组装进同一场景时,
  68. 理解这些属性及其在不同色彩空间之间的关系非常重要。
  69. </p>
  70. <figure class="float">
  71. <img src="../resources/srgb_gamut.png" alt="">
  72. <figcaption>
  73. 参考 CIE 1931 色度图中的 sRGB 颜色与白点(D65)。
  74. 彩色区域是 sRGB 色域(三维体积)在二维中的投影。
  75. 来源:<a href="https://en.wikipedia.org/wiki/SRGB" target="_blank" rel="noopener">Wikipedia</a>
  76. </figcaption>
  77. </figure>
  78. <ul>
  79. <li>
  80. <b>色彩原色(Color primaries):</b>原色(如红、绿、蓝)并非绝对值;
  81. 它们是在有限精度与显示设备能力约束下,从可见光谱中选定的。
  82. 颜色由各原色的比例来表达。
  83. </li>
  84. <li>
  85. <b>白点(White point):</b>大多数色彩空间都会定义:
  86. 当原色满足 <i>R = G = B</i> 时呈现“无色(中性)”。
  87. 白、灰等中性色的视觉效果依赖人眼感知,而感知又与观察环境相关。
  88. 因此色彩空间会指定一个“白点”以统一基准。
  89. sRGB 的白点是 [link:https://en.wikipedia.org/wiki/Illuminant_D65 D65]。
  90. </li>
  91. <li>
  92. <b>传递函数(Transfer functions):</b>在确定色域和颜色模型后,
  93. 还要定义数值与颜色空间之间的映射(传递函数)。
  94. <i>r = 0.5</i> 是表示物理光照比 <i>r = 1.0</i> 少 50%,
  95. 还是表示人眼感知亮度少 50%?两者并不等价,
  96. 差异由数学函数来描述。根据目标不同,传递函数可以是
  97. <i>线性</i>或<i>非线性</i>。sRGB 使用非线性传递函数。
  98. 这些函数有时会近似为<i>伽马函数</i>,但“gamma”一词在这里含义模糊,
  99. 应尽量避免混用。
  100. </li>
  101. </ul>
  102. 以上三个参数(原色、白点、传递函数)共同定义了一个色彩空间。
  103. 在此基础上,再补充几个术语会更清晰:
  104. <ul>
  105. <li>
  106. <b>颜色模型(Color model):</b>在既定色域中用数字描述颜色的方式,
  107. 可理解为颜色坐标系。在 three.js 里我们主要使用 RGB 模型,
  108. 坐标为 <i>r, g, b ∈ [0,1]</i>(闭区间)或
  109. <i>r, g, b ∈ [0,+∞)</i>(开域),每一项都表示某个原色所占比例。
  110. 其他模型(HSL、Lab、LCH)常用于美术调色。
  111. </li>
  112. <li>
  113. <b>色域(Color gamut):</b>当原色与白点确定后,就确定了可见光谱中的一个体积范围,
  114. 这就是“色域”。不在这个体积内的颜色(超出色域)无法用闭区间 [0,1] 的 RGB 表达。
  115. 在开域 [0,+∞) 中,色域在数学上可以视为无限。
  116. </li>
  117. </ul>
  118. <p>
  119. 来看两个最常见的色彩空间:`SRGBColorSpace`(sRGB)与
  120. `LinearSRGBColorSpace`(Linear-sRGB)。两者原色和白点相同,
  121. 因此色域一致,也都使用 RGB 模型。它们只在传递函数上不同:
  122. Linear-sRGB 相对于物理光强是线性的;sRGB 使用非线性传递函数,
  123. 更接近人眼感知与常见显示设备的响应特性。
  124. </p>
  125. <p>
  126. 这个差异非常关键。光照计算和大多数渲染运算通常必须在线性色彩空间中完成。
  127. 但线性颜色在图像或帧缓冲中的存储效率较低,且直接显示给人眼时观感不正确。
  128. 因此,输入纹理与最终输出图像通常会使用非线性的 sRGB 色彩空间。
  129. </p>
  130. <blockquote>
  131. <p>
  132. ℹ️ <i><b>注意:</b>虽然部分现代显示器支持 Display-P3 等更宽色域,
  133. 但 Web 平台图形 API 仍主要基于 sRGB。
  134. 当前 three.js 应用通常只会使用 sRGB 与 Linear-sRGB。</i>
  135. </p>
  136. </blockquote>
  137. <h2>色彩空间在流程中的角色</h2>
  138. <p>
  139. 现代渲染所需的线性工作流通常会涉及不止一种色彩空间,
  140. 每种色彩空间承担不同职责。线性与非线性色彩空间适用于不同环节,
  141. 如下所示。
  142. </p>
  143. <h3>输入色彩空间</h3>
  144. <p>
  145. 传入 three.js 的颜色(来自取色器、纹理、3D 模型等)都带有各自色彩空间。
  146. 凡是不在 Linear-sRGB 工作色彩空间中的输入,都需要转换;
  147. 纹理也必须正确设置 <i>texture.colorSpace</i>。
  148. 如果在初始化颜色前启用 THREE.ColorManagement,
  149. 某些转换(如十六进制颜色与 CSS sRGB 颜色)会自动处理:
  150. </p>
  151. <code>
  152. THREE.ColorManagement.enabled = true;
  153. </code>
  154. <p>
  155. THREE.ColorManagement 默认已启用。
  156. </p>
  157. <ul>
  158. <li>
  159. <b>材质、灯光与着色器:</b>其颜色数据中的 RGB 分量存储在线性的
  160. Linear-sRGB 工作空间中。
  161. </li>
  162. <li>
  163. <b>顶点颜色:</b>`BufferAttribute` 中的 RGB 分量也存储在
  164. Linear-sRGB 工作空间中。
  165. </li>
  166. <li>
  167. <b>颜色纹理:</b>包含颜色信息的 PNG/JPEG `Texture`
  168. (如 `.map`、`.emissiveMap`)应使用闭区间 sRGB,
  169. 并标注 <i>texture.colorSpace = SRGBColorSpace</i>。
  170. OpenEXR 等格式(常用于 `.envMap`、`.lightMap`)则使用 Linear-sRGB,
  171. 标注为 <i>texture.colorSpace = LinearSRGBColorSpace</i>,
  172. 并且可能包含开域 [0,+∞) 的值。
  173. </li>
  174. <li>
  175. <b>非颜色纹理:</b>不存储颜色信息的纹理(如 `.normalMap`、`.roughnessMap`)
  176. 没有对应色彩空间,一般使用默认标注
  177. <i>texture.colorSpace = NoColorSpace</i>。
  178. 在少数场景下,非颜色数据可能因技术原因采用其他非线性编码。
  179. </li>
  180. </ul>
  181. <blockquote>
  182. <p>
  183. ⚠️ <i><b>警告:</b>许多 3D 模型格式并未正确或一致地定义色彩空间信息。
  184. three.js 虽会尽量处理常见情况,但旧格式仍常出现问题。
  185. 为获得最佳结果,请优先使用 glTF 2.0(`GLTFLoader`),
  186. 并尽早在在线查看器中验证资源本身是否正确。</i>
  187. </p>
  188. </blockquote>
  189. <h3>工作色彩空间</h3>
  190. <p>
  191. 渲染、插值及许多其他计算,必须在开域的线性工作色彩空间中进行,
  192. 此时 RGB 分量与物理光照强度成比例。在 three.js 中,
  193. 工作色彩空间是 Linear-sRGB。
  194. </p>
  195. <h3>输出色彩空间</h3>
  196. <p>
  197. 输出到显示设备、图片或视频时,通常需要将开域 Linear-sRGB
  198. 工作空间转换到目标色彩空间。该转换由
  199. `WebGLRenderer.outputColorSpace` 定义。
  200. 使用后处理时,需要 `OutputPass`。
  201. </p>
  202. <ul>
  203. <li>
  204. <b>显示:</b>写入 WebGL 画布并显示的颜色应使用 sRGB。
  205. </li>
  206. <li>
  207. <b>图像:</b>写入图像时应使用与格式和用途匹配的色彩空间。
  208. 完整渲染后保存为 PNG/JPEG 的图片通常使用 sRGB。
  209. 若图像包含自发光、光照贴图或其他不受 [0,1] 限制的数据,
  210. 通常使用开域 Linear-sRGB,并配合 OpenEXR 等兼容格式。
  211. </li>
  212. </ul>
  213. <blockquote>
  214. <p>
  215. ⚠️ <i><b>警告:</b>渲染目标可使用 sRGB 或 Linear-sRGB。
  216. sRGB 在有限精度下利用率更高:在闭区间内,sRGB 常用 8-bit 即可,
  217. 而 Linear-sRGB 可能需要至少 16-bit(half float)。
  218. 若后续管线阶段还要求 Linear-sRGB 输入,额外转换会带来一定性能开销。</i>
  219. </p>
  220. </blockquote>
  221. <p>
  222. 基于 `ShaderMaterial` 和 `RawShaderMaterial` 的自定义材质需要自行实现输出色彩空间转换。
  223. 对于 `ShaderMaterial`,通常在片元着色器 `main()` 中加入
  224. `colorspace_fragment` shader chunk 即可。
  225. </p>
  226. <h2>使用 THREE.Color 实例</h2>
  227. <p>
  228. 读取或修改 `Color` 的方法默认假设数据已经在 three.js 的工作色彩空间
  229. (Linear-sRGB)中。RGB 与 HSL 分量都直接对应 `Color` 实例内部数据,
  230. 不会被隐式转换。你可以显式调用
  231. <i>.convertLinearToSRGB()</i> 或 <i>.convertSRGBToLinear()</i> 进行转换。
  232. </p>
  233. <pre class="prettyprint notranslate lang-js" translate="no">
  234. // RGB 分量(不发生转换)。
  235. color.r = color.g = color.b = 0.5;
  236. console.log( color.r ); // → 0.5
  237. // 手动转换。
  238. color.r = 0.5;
  239. color.convertSRGBToLinear();
  240. console.log( color.r ); // → 0.214041140
  241. </pre>
  242. <p>
  243. 当设置 <i>ColorManagement.enabled = true</i>(推荐,且默认开启)后,
  244. 某些转换会自动执行。由于十六进制与 CSS 颜色通常属于 sRGB,
  245. `Color` 在 setter 中会把它们从 sRGB 转为 Linear-sRGB;
  246. 在 getter 返回十六进制或 CSS 值时,则会从 Linear-sRGB 转回 sRGB。
  247. </p>
  248. <pre class="prettyprint notranslate lang-js" translate="no">
  249. // 十六进制转换。
  250. color.setHex( 0x808080 );
  251. console.log( color.r ); // → 0.214041140
  252. console.log( color.getHex() ); // → 0x808080
  253. // CSS 颜色转换。
  254. color.setStyle( 'rgb( 0.5, 0.5, 0.5 )' );
  255. console.log( color.r ); // → 0.214041140
  256. // 通过 'colorSpace' 参数覆盖默认转换。
  257. color.setHex( 0x808080, LinearSRGBColorSpace );
  258. console.log( color.r ); // → 0.5
  259. console.log( color.getHex( LinearSRGBColorSpace ) ); // → 0x808080
  260. console.log( color.getHex( SRGBColorSpace ) ); // → 0xBCBCBC
  261. </pre>
  262. <h2>常见错误</h2>
  263. <p>
  264. 当某个颜色或纹理配置错误时,它看起来会比预期更亮或更暗。
  265. 当渲染器输出色彩空间配置错误时,整张场景都可能偏暗
  266. (例如遗漏了到 sRGB 的转换)或偏亮(例如后处理中重复转换到 sRGB)。
  267. 这类问题通常并非全局线性偏差,单纯增减光照并不能真正解决。
  268. </p>
  269. <p>
  270. 更隐蔽的问题是:当输入和输出色彩空间<i>都</i>设置错误时,
  271. 整体亮度看似正常,但颜色会在不同光照下异常变化,
  272. 或明暗层次变得过曝、生硬。两个错误不会相互抵消。
  273. 务必确保工作色彩空间是线性的(scene referred),
  274. 输出色彩空间是非线性的(display referred)。
  275. </p>
  276. <h2>延伸阅读</h2>
  277. <ul>
  278. <li>
  279. <a href="https://developer.nvidia.com/gpugems/gpugems3/part-iv-image-effects/chapter-24-importance-being-linear" target="_blank" rel="noopener">GPU Gems 3: The Importance of Being Linear</a>, by Larry Gritz and Eugene d'Eon
  280. </li>
  281. <li>
  282. <a href="https://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/" target="_blank" rel="noopener">What every coder should know about gamma</a>, by John Novak
  283. </li>
  284. <li>
  285. <a href="https://hg2dc.com/" target="_blank" rel="noopener">The Hitchhiker's Guide to Digital Color</a>, by Troy Sobotka
  286. </li>
  287. <li>
  288. <a href="https://docs.blender.org/manual/en/latest/render/color_management.html" target="_blank" rel="noopener">Color Management</a>, Blender
  289. </li>
  290. </ul>
  291. </div>
  292. </div>
  293. </div>
  294. <script src="../resources/prettify.js"></script>
  295. <script src="../resources/lesson.js"></script>
  296. </body></html>
粤ICP备19079148号