forked from serjIII/threejsSDK
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathColor-management.html
333 lines (284 loc) · 12.8 KB
/
Color-management.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<base href="../../../" />
<script src="page.js"></script>
<link type="text/css" rel="stylesheet" href="page.css" />
<style>
blockquote {
font-size: 0.8em;
line-height: 1.5em;
margin-left: 0;
border-left: 4px solid #cccccc;
padding: 1em 2em 1em 2em;
}
blockquote p:first-child {
margin-top: 0;
}
blockquote p:last-child {
margin-bottom: 0;
}
figure {
width: 100%;
margin: 1em 0;
font-style: italic;
}
figure img {
width: 100%;
}
figure.float {
float: right;
max-width: 30%;
margin: 1em;
}
@media all and ( max-width: 640px ) {
figure.float {
float: none;
max-width: 100%;
}
}
</style>
</head>
<body>
<h1>[name]</h1>
<h2>What is a color space?</h2>
<p>
Every color space is a collection of several design decisions, chosen together to support a
large range of colors while satisfying technical constraints related to precision and display
technologies. When creating a 3D asset, or assembling 3D assets together into a scene, it is
important to know what these properties are, and how the properties of one color space relate
to other color spaces in the scene.
</p>
<figure class="float">
<img src="resources/srgb_gamut.png" alt="">
<figcaption>
sRGB colors and white point (D65) displayed in the reference CIE 1931 chromaticity
diagram. Colored region represents a 2D projection of the sRGB gamut, which is a 3D
volume. Source: <a href="https://en.wikipedia.org/wiki/SRGB" target="_blank" rel="noopener">Wikipedia</a>
</figcaption>
</figure>
<ul>
<li>
<b>Color primaries:</b> Primary colors (e.g. red, green, blue) are not absolutes; they are
selected from the visible spectrum based on constraints of limited precision and
capabilities of available display devices. Colors are expressed as a ratio of the primary colors.
</li>
<li>
<b>White point:</b> Most color spaces are engineered such that an equally weighted sum of
primaries <i>R = G = B</i> will appear to be without color, or "achromatic". The appearance
of achromatic values (like white or grey) depend on human perception, which in turn depends
heavily on the context of the observer. A color space specifies its "white point" to balance
these needs. The white point defined by the sRGB color space is
[link:https://en.wikipedia.org/wiki/Illuminant_D65 D65].
</li>
<li>
<b>Transfer functions:</b> After choosing the color gamut and a color model, we still need to
define mappings ("transfer functions") of numerical values to/from the color space. Does <i>r = 0.5</i>
represent 50% less physical illumination than <i>r = 1.0</i>? Or 50% less bright, as perceived
by an average human eye? These are different things, and that difference can be represented as
a mathematical function. Transfer functions may be <i>linear</i> or <i>nonlinear</i>, depending
on the objectives of the color space. sRGB defines nonlinear transfer functions. Those
functions are sometimes approximated as <i>gamma functions</i>, but the term "gamma" is
ambiguous and should be avoided in this context.
</li>
</ul>
These three parameters — color primaries, white point, and transfer functions — define a color
space, with each chosen for particular goals. Having defined the parameters, a few additional terms
are helpful:
<ul>
<li>
<b>Color model:</b> Syntax for numerically identifying colors within chosen the color gamut —
a coordinate system for colors. In three.js we're mainly concerned with the RGB color
model, having three coordinates <i>r, g, b ∈ [0,1]</i> ("closed domain") or
<i>r, g, b ∈ [0,∞]</i> ("open domain") each representing a fraction of a primary
color. Other color models (HSL, Lab, LCH) are commonly used for artistic control.
</li>
<li>
<b>Color gamut:</b> Once color primaries and a white point have been chosen, these represent
a volume within the visible spectrum (a "gamut"). Colors not within this volume ("out of gamut")
cannot be expressed by closed domain [0,1] RGB values. In the open domain [0,∞], the gamut is
technically infinite.
</li>
</ul>
<p>
Consider two very common color spaces: [page:SRGBColorSpace] ("sRGB") and
[page:LinearSRGBColorSpace] ("Linear-sRGB"). Both use the same primaries and white point,
and therefore have the same color gamut. Both use the RGB color model. They differ only in
the transfer functions — Linear-sRGB is linear with respect to physical light intensity.
sRGB uses the nonlinear sRGB transfer functions, and more closely resembles the way that
the human eye perceives light and the responsiveness of common display devices.
</p>
<p>
That difference is important. Lighting calculations and other rendering operations must
generally occur in a linear color space. However, a linear colors are less efficient to
store in an image or framebuffer, and do not look correct when viewed by a human observer.
As a result, input textures and the final rendered image will generally use the nonlinear
sRGB color space.
</p>
<blockquote>
<p>
ℹ️ <i><b>NOTICE:</b> While some modern displays support wider gamuts like Display-P3,
the web platform's graphics APIs largely rely on sRGB. Applications using three.js
today will typically use only the sRGB and Linear-sRGB color spaces.</i>
</p>
</blockquote>
<h2>Roles of color spaces</h2>
<p>
Linear workflows — required for modern rendering methods — generally involve more than
one color space, each assigned to a particular role. Linear and nonlinear color spaces are
appropriate for different roles, explained below.
</p>
<h3>Input color space</h3>
<p>
Colors supplied to three.js — from color pickers, textures, 3D models, and other sources —
each have an associated color space. Those not already in the Linear-sRGB working color
space must be converted, and textures be given the correct <i>texture.colorSpace</i> assignment.
Certain conversions (for hexadecimal and CSS colors in sRGB) can be made automatically if
the THREE.ColorManagement API is enabled before initializing colors:
</p>
<code>
THREE.ColorManagement.enabled = true;
</code>
<ul>
<li>
<b>Materials, lights, and shaders:</b> Colors in materials, lights, and shaders store
RGB components in the Linear-sRGB working color space.
</li>
<li>
<b>Vertex colors:</b> [page:BufferAttribute BufferAttributes] store RGB components in the
Linear-sRGB working color space.
</li>
<li>
<b>Color textures:</b> PNG or JPEG [page:Texture Textures] containing color information
(like .map or .emissiveMap) use the closed domain sRGB color space, and must be annotated with
<i>texture.colorSpace = SRGBColorSpace</i>. Formats like OpenEXR (sometimes used for .envMap or
.lightMap) use the Linear-sRGB color space indicated with <i>texture.colorSpace = LinearSRGBColorSpace</i>,
and may contain values in the open domain [0,∞].
</li>
<li>
<b>Non-color textures:</b> Textures that do not store color information (like .normalMap
or .roughnessMap) do not have an associated color space, and generally use the (default) texture
annotation of <i>texture.colorSpace = NoColorSpace</i>. In rare cases, non-color data
may be represented with other nonlinear encodings for technical reasons.
</li>
</ul>
<blockquote>
<p>
⚠️ <i><b>WARNING:</b> Many formats for 3D models do not correctly or consistently
define color space information. While three.js attempts to handle most cases, problems
are common with older file formats. For best results, use glTF 2.0 ([page:GLTFLoader])
and test 3D models in online viewers early to confirm the asset itself is correct.</i>
</p>
</blockquote>
<h3>Working color space</h3>
<p>
Rendering, interpolation, and many other operations must be performed in an open domain
linear working color space, in which RGB components are proportional to physical
illumination. In three.js, the working color space is Linear-sRGB.
</p>
<h3>Output color space</h3>
<p>
Output to a display device, image, or video may involve conversion from the open domain
Linear-sRGB working color space to another color space. This conversion may be performed in
the main render pass ([page:WebGLRenderer.outputColorSpace]), or during post-processing.
</p>
<code>
renderer.outputColorSpace = THREE.SRGBColorSpace; // optional with post-processing
</code>
<ul>
<li>
<b>Display:</b> Colors written to a WebGL canvas for display should be in the sRGB
color space.
</li>
<li>
<b>Image:</b> Colors written to an image should use the color space appropriate for
the format and usage. Fully-rendered images written to PNG or JPEG textures generally
use the sRGB color space. Images containing emission, light maps, or other data not
confined to the [0,1] range will generally use the open domain Linear-sRGB color space,
and a compatible image format like OpenEXR.
</li>
</ul>
<blockquote>
<p>
⚠️ <i><b>WARNING:</b> Render targets may use either sRGB or Linear-sRGB. sRGB makes
better use of limited precision. In the closed domain, 8 bits often suffice for sRGB
whereas ≥12 bits (half float) may be required for Linear-sRGB. If later pipeline
stages require Linear-sRGB input, the additional conversions may have a small
performance cost.</i>
</p>
</blockquote>
<p>
Custom materials based on [page:ShaderMaterial] and [page:RawShaderMaterial] have to implement their own output color space conversion.
For instances of `ShaderMaterial`, adding the `colorspace_fragment` shader chunk to the fragment shader's `main()` function should be sufficient.
</p>
<h2>Working with THREE.Color instances</h2>
<p>
Methods reading or modifying [page:Color] instances assume data is already in the
three.js working color space, Linear-sRGB. RGB and HSL components are direct
representations of data stored by the Color instance, and are never converted
implicitly. Color data may be explicitly converted with <i>.convertLinearToSRGB()</i>
or <i>.convertSRGBToLinear()</i>.
</p>
<code>
// 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
</code>
<p>
With <i>ColorManagement.enabled = true</i> set (recommended), certain conversions
are made automatically. Because hexadecimal and CSS colors are generally sRGB, [page:Color]
methods will automatically convert these inputs from sRGB to Linear-sRGB in setters, or
convert from Linear-sRGB to sRGB when returning hexadecimal or CSS output from getters.
</p>
<code>
// 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
</code>
<h2>Common mistakes</h2>
<p>
When an individual color or texture is misconfigured, it will appear darker or lighter than
expected. When the renderer's output color space is misconfigured, the entire scene may appear
darker (e.g. missing conversion to sRGB) or lighter (e.g. a double conversion to sRGB with
post-processing). In each case the problem may not be uniform, and simply increasing/decreasing
lighting does not solve it.
</p>
<p>
A more subtle issue appears when <i>both</i> the input color spaces and the output color
spaces are incorrect — the overall brightness levels may be fine, but colors may change
unexpectedly under different lighting, or shading may appear more blown-out and less soft
than intended. These two wrongs do not make a right, and it's important that the working
color space be linear ("scene referred") and the output color space be nonlinear
("display referred").
</p>
<h2>Further reading</h2>
<ul>
<li>
<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
</li>
<li>
<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
</li>
<li>
<a href="https://hg2dc.com/" target="_blank" rel="noopener">The Hitchhiker's Guide to Digital Color</a>, by Troy Sobotka
</li>
<li>
<a href="https://docs.blender.org/manual/en/latest/render/color_management.html" target="_blank" rel="noopener">Color Management</a>, Blender
</li>
</ul>
</body>
</html>