我々が見ている「色」は、実際は異なる波長の光が混ざりあったものですが、CGレンダリングでは通常波長は考慮しません。(一部のレンダラーは波長まで考慮するものがあるそうですが、少なくともBlender cyclesレンダラーは波長を考慮しません)そこで、Blenderで波長を扱いたい場合、 波長をRGB値に変換する必要があります。
実はBlenderにはWavelengthノードと呼ばれる、波長をRGBに変換するノードが存在するのですが、実際にどういう計算をしているのかは知っておいたほうが良いので調べてみることにしました。 同じことを考えている人はいたみたいですが、このフォーラムでは満足な答えが得られていなかったので、独自に調べました。
波長RGB変換機
Blender Wavelength nodeのアルゴリズムを調べて、Webで同じ結果が出るようにした計算機を作りました。380 nm ~ 780 nm の波長に対応しています(wavelengthノードも同様)。好きな波長を入力すると、それに対応したRGBの値が、0~1の浮動小数点(float)、8bitの10進数表記、8bitの16進数カラーコード表記で表示されます。
またプリセットとして、CIE(国際照明委員会 Commission internationale de l’eclairage)で定められた、赤(R):700.0 [nm]、緑(G):546.1[nm]、青(B):435.8[nm]と、等色関数(後述)から得られる、純粋なRGB成分が出る波長を用意しています。
※700nmは、この変換式ではほぼ黒(に寄った赤)になります。
Color(float) : R,G,B = 0.00000,0.00000,0.00000
Color (8bit decimal) : R,G,B = 0, 0, 0
Color (8bit hexadecimal) : RGB = #000000
解説
CIE測色標準観察者等色関数(XYZ色空間)
Blenderのソースコードをwavelengthで検索すると、変換の仕組みが見えてきました。このWavelengthノードは CIE 1931 色空間をベースにしている様子です。 CIEのCIE測色標準観察者等色関数(The CIE XYZ standard observer color matching functions)がソースコードに組み込まれていました。
2024/02追記:Blenderソースコードの構成がいつの間にか変わってたので再リンクしました。また、CIE関数が埋め込まれているソースも別ファイルに切り出されてました。
(/intern/cycles/kernel/svm/svm_wavelength.h → /intern/cycles/kernel/svm/wavelength.h)
Figure 1. CIE測色標準観察者等色関数
グラフの元になるのは、以下のデータです。
Table 1. CIE測色標準観察者等色関数
ID | Increment | Wavelength (nm) | xBar | yBar | zBar |
0 | 0 | 380 | 0.0014 | 0.0000 | 0.0065 |
1 | 5 | 385 | 0.0022 | 0.0001 | 0.0105 |
2 | 10 | 390 | 0.0042 | 0.0001 | 0.0201 |
3 | 15 | 395 | 0.0076 | 0.0002 | 0.0362 |
4 | 20 | 400 | 0.0143 | 0.0004 | 0.0679 |
5 | 25 | 405 | 0.0232 | 0.0006 | 0.1102 |
6 | 30 | 410 | 0.0435 | 0.0012 | 0.2074 |
7 | 35 | 415 | 0.0776 | 0.0022 | 0.3713 |
8 | 40 | 420 | 0.1344 | 0.0040 | 0.6456 |
9 | 45 | 425 | 0.2148 | 0.0073 | 1.0391 |
10 | 50 | 430 | 0.2839 | 0.0116 | 1.3856 |
11 | 55 | 435 | 0.3285 | 0.0168 | 1.6230 |
12 | 60 | 440 | 0.3483 | 0.0230 | 1.7471 |
13 | 65 | 445 | 0.3481 | 0.0298 | 1.7826 |
14 | 70 | 450 | 0.3362 | 0.0380 | 1.7721 |
15 | 75 | 455 | 0.3187 | 0.0480 | 1.7441 |
16 | 80 | 460 | 0.2908 | 0.0600 | 1.6692 |
17 | 85 | 465 | 0.2511 | 0.0739 | 1.5281 |
18 | 90 | 470 | 0.1954 | 0.0910 | 1.2876 |
19 | 95 | 475 | 0.1421 | 0.1126 | 1.0419 |
20 | 100 | 480 | 0.0956 | 0.1390 | 0.8130 |
21 | 105 | 485 | 0.0580 | 0.1693 | 0.6162 |
22 | 110 | 490 | 0.0320 | 0.2080 | 0.4652 |
23 | 115 | 495 | 0.0147 | 0.2586 | 0.3533 |
24 | 120 | 500 | 0.0049 | 0.3230 | 0.2720 |
25 | 125 | 505 | 0.0024 | 0.4073 | 0.2123 |
26 | 130 | 510 | 0.0093 | 0.5030 | 0.1582 |
27 | 135 | 515 | 0.0291 | 0.6082 | 0.1117 |
28 | 140 | 520 | 0.0633 | 0.7100 | 0.0782 |
29 | 145 | 525 | 0.1096 | 0.7932 | 0.0573 |
30 | 150 | 530 | 0.1655 | 0.8620 | 0.0422 |
31 | 155 | 535 | 0.2257 | 0.9149 | 0.0298 |
32 | 160 | 540 | 0.2904 | 0.9540 | 0.0203 |
33 | 165 | 545 | 0.3597 | 0.9803 | 0.0134 |
34 | 170 | 550 | 0.4334 | 0.9950 | 0.0087 |
35 | 175 | 555 | 0.5121 | 1.0000 | 0.0057 |
36 | 180 | 560 | 0.5945 | 0.9950 | 0.0039 |
37 | 185 | 565 | 0.6784 | 0.9786 | 0.0027 |
38 | 190 | 570 | 0.7621 | 0.9520 | 0.0021 |
39 | 195 | 575 | 0.8425 | 0.9154 | 0.0018 |
40 | 200 | 580 | 0.9163 | 0.8700 | 0.0017 |
41 | 205 | 585 | 0.9786 | 0.8163 | 0.0014 |
42 | 210 | 590 | 1.0263 | 0.7570 | 0.0011 |
43 | 215 | 595 | 1.0567 | 0.6949 | 0.0010 |
44 | 220 | 600 | 1.0622 | 0.6310 | 0.0008 |
45 | 225 | 605 | 1.0456 | 0.5668 | 0.0006 |
46 | 230 | 610 | 1.0026 | 0.5030 | 0.0003 |
47 | 235 | 615 | 0.9384 | 0.4412 | 0.0002 |
48 | 240 | 620 | 0.8544 | 0.3810 | 0.0002 |
49 | 245 | 625 | 0.7514 | 0.3210 | 0.0001 |
50 | 250 | 630 | 0.6424 | 0.2650 | 0.0000 |
51 | 255 | 635 | 0.5419 | 0.2170 | 0.0000 |
52 | 260 | 640 | 0.4479 | 0.1750 | 0.0000 |
53 | 265 | 645 | 0.3608 | 0.1382 | 0.0000 |
54 | 270 | 650 | 0.2835 | 0.1070 | 0.0000 |
55 | 275 | 655 | 0.2187 | 0.0816 | 0.0000 |
56 | 280 | 660 | 0.1649 | 0.0610 | 0.0000 |
57 | 285 | 665 | 0.1212 | 0.0446 | 0.0000 |
58 | 290 | 670 | 0.0874 | 0.0320 | 0.0000 |
59 | 295 | 675 | 0.0636 | 0.0232 | 0.0000 |
60 | 300 | 680 | 0.0468 | 0.0170 | 0.0000 |
61 | 305 | 685 | 0.0329 | 0.0119 | 0.0000 |
62 | 310 | 690 | 0.0227 | 0.0082 | 0.0000 |
63 | 315 | 695 | 0.0158 | 0.0057 | 0.0000 |
64 | 320 | 700 | 0.0114 | 0.0041 | 0.0000 |
65 | 325 | 705 | 0.0081 | 0.0029 | 0.0000 |
66 | 330 | 710 | 0.0058 | 0.0021 | 0.0000 |
67 | 335 | 715 | 0.0041 | 0.0015 | 0.0000 |
68 | 340 | 720 | 0.0029 | 0.0010 | 0.0000 |
69 | 345 | 725 | 0.0020 | 0.0007 | 0.0000 |
70 | 350 | 730 | 0.0014 | 0.0005 | 0.0000 |
71 | 355 | 735 | 0.0010 | 0.0004 | 0.0000 |
72 | 360 | 740 | 0.0007 | 0.0002 | 0.0000 |
73 | 365 | 745 | 0.0005 | 0.0002 | 0.0000 |
74 | 370 | 750 | 0.0003 | 0.0001 | 0.0000 |
75 | 375 | 755 | 0.0002 | 0.0001 | 0.0000 |
76 | 380 | 760 | 0.0002 | 0.0001 | 0.0000 |
77 | 385 | 765 | 0.0001 | 0.0000 | 0.0000 |
78 | 390 | 770 | 0.0001 | 0.0000 | 0.0000 |
79 | 395 | 775 | 0.0001 | 0.0000 | 0.0000 |
80 | 400 | 780 | 0.0000 | 0.0000 | 0.0000 |
これは波長をCIE XYZ 色空間の値に変換するもので、これを基底変換してRGBに変換することができるそう。(歴史的には、RGBが先でXYZが後にできた色空間だそうです)変換については、詳細なまとめ記事があったのでそちらを参照してください。
XYZ色空間からRGB色空間へ
CIEの定義
CIE RGB色空間からCIE XYZ色空間への変換は、線形変換として定義できます。CIE特別委員会で定義された変換式は以下のような形です。
$$\begin{pmatrix} X \\ Y \\ Z \end{pmatrix}=\begin{pmatrix} 2.7688 & 1.7517 & 1.1301 \\ 1.0000 & 4.5906 & 0.060067 \\ 0.0000 & 0.056507 & 5.5942 \end{pmatrix}\begin{pmatrix} R_{\mathrm{CIE}} \\ G_{\mathrm{CIE}} \\ B_{\mathrm{CIE}} \end{pmatrix}\tag{1}$$
$$\begin{pmatrix} R_{\mathrm{CIE}} \\ G_{\mathrm{CIE}} \\ B _{\mathrm{CIE}}\end{pmatrix}=\begin{pmatrix} 0.41847 & -0.15866 & -0.082835 \\ -0.091169 & 0.25243 & 0.015708 \\ 0.00092090 & -0.0025498 & 0.17860 \end{pmatrix}\begin{pmatrix} X \\ Y \\ Z \end{pmatrix}\tag{2}$$
この変換式は、WikipediaのCIE1931色空間のページにあるものと同等のものです。(有効数字5桁)
上記のCIE測色標準観察者等色関数とCIE RGB色空間からCIE XYZ色空間への変換式を組み合わせることで、波長をRGBに変換することができます。(ページ上部変換器1)
しかし、
ITU Rec.709の定義
Blenderソースコード中で見つかった変換式は、上記のものとは違っていました。
調べたところ、Blenderのノード中では、ITU Recommendation BT.709 (Rec.709) 準拠の変換式が組み込まれているようです。これは白色点(Whitepoint) D65での変換式とのこと。Blenderはグラフィックソフトなので、現代の映像で一番用いられている色空間を採用しているということですかね。
$$\begin{pmatrix} X \\ Y \\ Z \end{pmatrix}=\begin{pmatrix} 0.412453 & 0.357580 & 0.180423 \\ 0.212671 & 0.715160 & 0.072169 \\ 0.019334 & 0.119193 & 0.950277 \end{pmatrix}\begin{pmatrix} R_{709} \\ G_{709} \\ B_{709} \end{pmatrix}\tag{3}$$
$$\begin{pmatrix} R_{709} \\ G_{709} \\ B_{709} \end{pmatrix}=\begin{pmatrix} 3.240479 & -1.537150 & -0.498535 \\ -0.969256 & 1.875992 & 0.041556 \\ 0.055648 & -0.204043 & 1.057311 \end{pmatrix}\begin{pmatrix} X \\ Y \\ Z \end{pmatrix}\tag{4}$$
2024/02追記:Blenderソースコードの構成がいつの間にか変わってたので再リンクしました
(/intern/cycles/kernel/shaders/node_color.h#L76 → /intern/cycles/kernel/osl/shaders/node_color.h#L75
細かい処理
また、ソースコードには、
color *= 1.0f / 2.52f; // Empirical scale from lg to make all comps <= 1
という部分があり、得られたRGBの値を2.52で割っていることがわかりました。調べたところ、ソニー・ピクチャーズ傘下のImageworks社が開発する、OSL(Open Shading Language)のソースコードから転用されていることがわかりました。すべての値が1を下回るようにするための経験的な値ということですね。
さらに、最後に0を下回った(負の)値を0にクランプする処理が行われて完成です。
/* Clamp to zero if values are smaller */
color = max(color, make_float3(0.0f, 0.0f, 0.0f));
ということで、BlenderのWavelength node(波長ノード)の仕組みを解き明かしました。
この計算式をWeb上で計算できるようにした計算機がこのページ上部にありますので、活用してください。Wavelengthノードと全く同じ色になるはずです。
コメント
式(4)の行列の2行1列成分は−0.969256では?
コメントありがとうございます。おっしゃる通り間違っていたので修正しました。