回答編集履歴
3
カラーピッカー側の値を矯正しようとした場合を追記
test
CHANGED
@@ -9,31 +9,31 @@
|
|
9
9
|
以前別の方の「[Unity マテリアルのEmission intensityについて](https://teratail.com/questions/291815)」とのご質問に回答しました際に調べたところによると、どうやらこのHDRカラーの分解は[ColorMutator](https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/GUI/ColorMutator.cs)が行っているようでしたが、下記引用のように...
|
10
10
|
|
11
11
|
> ```C#
|
12
|
-
>
|
12
|
+
> internal static void DecomposeHdrColor(Color linearColorHdr, out Color32 baseLinearColor, out float exposure)
|
13
|
-
>
|
13
|
+
> {
|
14
|
-
>
|
14
|
+
> baseLinearColor = linearColorHdr;
|
15
|
-
>
|
15
|
+
> var maxColorComponent = linearColorHdr.maxColorComponent;
|
16
|
-
>
|
16
|
+
> // replicate Photoshops's decomposition behaviour
|
17
|
-
>
|
17
|
+
> if (maxColorComponent == 0f || maxColorComponent <= 1f && maxColorComponent >= 1 / 255f)
|
18
|
-
>
|
18
|
+
> {
|
19
|
-
>
|
19
|
+
> exposure = 0f;
|
20
20
|
>
|
21
|
-
>
|
21
|
+
> baseLinearColor.r = (byte)Mathf.RoundToInt(linearColorHdr.r * 255f);
|
22
|
-
>
|
22
|
+
> baseLinearColor.g = (byte)Mathf.RoundToInt(linearColorHdr.g * 255f);
|
23
|
-
>
|
23
|
+
> baseLinearColor.b = (byte)Mathf.RoundToInt(linearColorHdr.b * 255f);
|
24
|
-
>
|
24
|
+
> }
|
25
|
-
>
|
25
|
+
> else
|
26
|
-
>
|
26
|
+
> {
|
27
|
-
>
|
27
|
+
> // calibrate exposure to the max float color component
|
28
|
-
>
|
28
|
+
> var scaleFactor = k_MaxByteForOverexposedColor / maxColorComponent;
|
29
|
-
>
|
29
|
+
> exposure = Mathf.Log(255f / scaleFactor) / Mathf.Log(2f);
|
30
30
|
>
|
31
|
-
>
|
31
|
+
> // maintain maximal integrity of byte values to prevent off-by-one errors when scaling up a color one component at a time
|
32
|
-
>
|
32
|
+
> baseLinearColor.r = Math.Min(k_MaxByteForOverexposedColor, (byte)Mathf.CeilToInt(scaleFactor * linearColorHdr.r));
|
33
|
-
>
|
33
|
+
> baseLinearColor.g = Math.Min(k_MaxByteForOverexposedColor, (byte)Mathf.CeilToInt(scaleFactor * linearColorHdr.g));
|
34
|
-
>
|
34
|
+
> baseLinearColor.b = Math.Min(k_MaxByteForOverexposedColor, (byte)Mathf.CeilToInt(scaleFactor * linearColorHdr.b));
|
35
|
-
> }
|
36
|
-
>
|
35
|
+
> }
|
36
|
+
> }
|
37
37
|
> ```
|
38
38
|
|
39
39
|
入力されたHDRカラーがLDRカラーで表現できない(明度が1/255を下回っているか、1を超えている)場合、元のHDRカラーの明度が191/255に(`k_MaxByteForOverexposedColor`として定義されている値に)なるようスケーリングしたものをベースカラーとして決めて、それに合わせたIntensityを計算しているらしいですね。
|
@@ -50,67 +50,67 @@
|
|
50
50
|
[RequireComponent(typeof(Renderer))]
|
51
51
|
public class EmissionController : MonoBehaviour
|
52
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
|
-
|
53
|
+
private static readonly int EmissionColorProperty = Shader.PropertyToID("_EmissionColor");
|
54
|
+
|
55
|
+
[ColorUsage(false)] public Color ColorCode = Color.black;
|
56
|
+
public float intensityValue;
|
57
|
+
|
58
|
+
private Color previousColorCode;
|
59
|
+
private float previousIntensityValue = float.NaN;
|
60
|
+
private new Renderer renderer;
|
61
|
+
|
62
|
+
private void Update()
|
63
|
+
{
|
64
|
+
if (this.renderer == null)
|
65
|
+
{
|
66
|
+
this.renderer = this.GetComponent<Renderer>();
|
67
|
+
}
|
68
|
+
|
69
|
+
// ColorCodeもintensityValueも前フレームから変わっていなければ
|
70
|
+
// Emission更新の必要はないと見なして何もしない
|
71
|
+
if ((this.ColorCode == this.previousColorCode) && (this.intensityValue == this.previousIntensityValue))
|
72
|
+
{
|
73
|
+
return;
|
74
|
+
}
|
75
|
+
|
76
|
+
// まずColorCodeをintensityValueに応じて増幅し、Emissionにセットするべき色を決める
|
77
|
+
var hdrColor = this.ColorCode * Mathf.Pow(2.0f, this.intensityValue);
|
78
|
+
hdrColor.a = this.ColorCode.a;
|
79
|
+
|
80
|
+
// EmissionにhdrColorをセットする
|
81
|
+
var material = this.renderer.material;
|
82
|
+
if (material != null)
|
83
|
+
{
|
84
|
+
material.EnableKeyword("_EMISSION");
|
85
|
+
material.SetColor(EmissionColorProperty, hdrColor);
|
86
|
+
}
|
87
|
+
|
88
|
+
// そしてhdrColorをベースカラーとIntensityに分解する
|
89
|
+
// まずhdrColorのRGBのうち最も大きい成分の値を取得する
|
90
|
+
var maxComponent = hdrColor.maxColorComponent;
|
91
|
+
|
92
|
+
// intensityValueはひとまず0にする
|
93
|
+
this.intensityValue = 0.0f;
|
94
|
+
|
95
|
+
// その最大成分が0なら完全な黒なのでLDRで表現でき、分解の必要はない
|
96
|
+
// また、1/255以上1以下ならLDRで表現でき、分解の必要はない
|
97
|
+
if ((maxComponent != 0.0f) && ((maxComponent < (1.0f / 255.0f)) || (maxComponent > 1.0f)))
|
98
|
+
{
|
99
|
+
// そうでないならば、まずmaxComponentに対する191/255の倍率を求める
|
100
|
+
var scale = 191.0f / (maxComponent * 255.0f);
|
101
|
+
|
102
|
+
// そしてそのスケールを使ってColorCodeのRGBを矯正する
|
103
|
+
this.ColorCode = hdrColor * scale;
|
104
|
+
this.ColorCode.a = hdrColor.a;
|
105
|
+
|
106
|
+
// また、scaleの逆数の2を底とする対数を求め、intensityValueとする
|
107
|
+
this.intensityValue = Mathf.Log(1.0f / scale, 2.0f);
|
108
|
+
}
|
109
|
+
|
110
|
+
// 最後に、次のフレームに備えて現在のColorCodeとintensityValueを保存しておく
|
111
|
+
this.previousColorCode = this.ColorCode;
|
112
|
+
this.previousIntensityValue = this.intensityValue;
|
113
|
+
}
|
114
114
|
}
|
115
115
|
```
|
116
116
|
|
@@ -118,3 +118,140 @@
|
|
118
118
|
そして、マテリアル側のEmissionもスクリプト側と同じ設定値になりました。
|
119
119
|
|
120
120
|

|
121
|
+
|
122
|
+
## カラーピッカー側の値を矯正しようとするとどうなるか
|
123
|
+
|
124
|
+
たとえばスクリプトを下記のようなものに変更した場合(なんだかマルウェア的な変なことをしていて気持ち悪いかもしれませんが、あくまでも実験ということでご容赦ください)...
|
125
|
+
|
126
|
+
```C#
|
127
|
+
using System;
|
128
|
+
using System.Reflection;
|
129
|
+
using System.Runtime.CompilerServices;
|
130
|
+
using System.Runtime.InteropServices;
|
131
|
+
using UnityEngine;
|
132
|
+
#if UNITY_EDITOR
|
133
|
+
using UnityEditor;
|
134
|
+
#endif
|
135
|
+
|
136
|
+
[RequireComponent(typeof(Renderer))]
|
137
|
+
public class EmissionController2 : MonoBehaviour
|
138
|
+
{
|
139
|
+
private static readonly int EmissionColorProperty = Shader.PropertyToID("_EmissionColor");
|
140
|
+
|
141
|
+
[ColorUsage(false)] public Color ColorCode = Color.black;
|
142
|
+
public float intensityValue;
|
143
|
+
|
144
|
+
private readonly byte[] decomposeHdrColorBackup = new byte[5];
|
145
|
+
private IntPtr decomposeHdrColorPointer;
|
146
|
+
private Color previousColorCode;
|
147
|
+
private float previousIntensityValue = float.NaN;
|
148
|
+
private new Renderer renderer;
|
149
|
+
|
150
|
+
private void Update()
|
151
|
+
{
|
152
|
+
if (this.renderer == null)
|
153
|
+
{
|
154
|
+
this.renderer = this.GetComponent<Renderer>();
|
155
|
+
}
|
156
|
+
|
157
|
+
// ColorCodeもintensityValueも前フレームから変わっていなければ
|
158
|
+
// Emission更新の必要はないと見なして何もしない
|
159
|
+
if ((this.ColorCode == this.previousColorCode) && (this.intensityValue == this.previousIntensityValue))
|
160
|
+
{
|
161
|
+
return;
|
162
|
+
}
|
163
|
+
|
164
|
+
// まずColorCodeをintensityValueに応じて増幅し、Emissionにセットするべき色を決める
|
165
|
+
var hdrColor = this.ColorCode * Mathf.Pow(2.0f, this.intensityValue);
|
166
|
+
hdrColor.a = this.ColorCode.a;
|
167
|
+
|
168
|
+
// EmissionにhdrColorをセットする
|
169
|
+
var material = this.renderer.material;
|
170
|
+
if (material != null)
|
171
|
+
{
|
172
|
+
material.EnableKeyword("_EMISSION");
|
173
|
+
material.SetColor(EmissionColorProperty, hdrColor);
|
174
|
+
}
|
175
|
+
|
176
|
+
// ColorMutator.DecomposeHdrColorを乗っ取り、代わりにDecomposeHdrColor255を実行させるようにする
|
177
|
+
this.HijackDecomposeHdrColor();
|
178
|
+
|
179
|
+
// 最後に、次のフレームに備えて現在のColorCodeとintensityValueを保存しておく
|
180
|
+
this.previousColorCode = this.ColorCode;
|
181
|
+
this.previousIntensityValue = this.intensityValue;
|
182
|
+
}
|
183
|
+
|
184
|
+
// コンポーネント破壊時にColorMutator.DecomposeHdrColorを元の状態に戻す
|
185
|
+
private void OnDestroy()
|
186
|
+
{
|
187
|
+
if (this.decomposeHdrColorPointer == IntPtr.Zero)
|
188
|
+
{
|
189
|
+
return;
|
190
|
+
}
|
191
|
+
Marshal.Copy(this.decomposeHdrColorBackup, 0, this.decomposeHdrColorPointer, this.decomposeHdrColorBackup.Length);
|
192
|
+
this.decomposeHdrColorPointer = IntPtr.Zero;
|
193
|
+
}
|
194
|
+
|
195
|
+
// ColorMutator.DecomposeHdrColorを乗っ取り、DecomposeHdrColor255へ制御を移すよう改変を加える
|
196
|
+
private void HijackDecomposeHdrColor()
|
197
|
+
{
|
198
|
+
#if UNITY_EDITOR
|
199
|
+
if (this.decomposeHdrColorPointer != IntPtr.Zero)
|
200
|
+
{
|
201
|
+
return;
|
202
|
+
}
|
203
|
+
|
204
|
+
// ColorMutator.DecomposeHdrColorのメモリ上の位置を取得する
|
205
|
+
const BindingFlags bindingFlags = BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Static;
|
206
|
+
var decomposeHdrColorMethodHandle = typeof(EditorWindow).Assembly.GetType("UnityEditor.ColorMutator").GetMethod("DecomposeHdrColor", bindingFlags).MethodHandle;
|
207
|
+
RuntimeHelpers.PrepareMethod(decomposeHdrColorMethodHandle);
|
208
|
+
this.decomposeHdrColorPointer = decomposeHdrColorMethodHandle.GetFunctionPointer();
|
209
|
+
|
210
|
+
// DecomposeHdrColor255のメモリ上の位置を取得する
|
211
|
+
var decomposeHdrColor255MethodHandle = typeof(EmissionController2).GetMethod(nameof(DecomposeHdrColor255), bindingFlags).MethodHandle;
|
212
|
+
RuntimeHelpers.PrepareMethod(decomposeHdrColor255MethodHandle);
|
213
|
+
var decomposeHdrColor255Pointer = decomposeHdrColor255MethodHandle.GetFunctionPointer();
|
214
|
+
|
215
|
+
// コンポーネント破壊時にColorMutator.DecomposeHdrColorを復元するため、先頭5バイトをバックアップしておく
|
216
|
+
Marshal.Copy(this.decomposeHdrColorPointer, this.decomposeHdrColorBackup, 0, this.decomposeHdrColorBackup.Length);
|
217
|
+
|
218
|
+
// ColorMutator.DecomposeHdrColorの先頭5バイトにDecomposeHdrColor255へジャンプするコードを埋め込む
|
219
|
+
Marshal.WriteByte(this.decomposeHdrColorPointer, 0xE9);
|
220
|
+
Marshal.WriteInt32(this.decomposeHdrColorPointer, 1, (int)decomposeHdrColor255Pointer - (int)this.decomposeHdrColorPointer - this.decomposeHdrColorBackup.Length);
|
221
|
+
#endif
|
222
|
+
}
|
223
|
+
|
224
|
+
// オリジナルのColorMutator.DecomposeHdrColorはベースカラーの基準値が191だが、
|
225
|
+
// こちらは255を基準にベースカラーを決める
|
226
|
+
private static void DecomposeHdrColor255(Color linearColorHdr, out Color32 baseLinearColor, out float exposure)
|
227
|
+
{
|
228
|
+
baseLinearColor = linearColorHdr;
|
229
|
+
var maxColorComponent = linearColorHdr.maxColorComponent;
|
230
|
+
if (maxColorComponent is 0.0f or <= 1.0f and >= 1.0f / 255.0f)
|
231
|
+
{
|
232
|
+
exposure = 0.0f;
|
233
|
+
baseLinearColor.r = (byte)Mathf.RoundToInt(linearColorHdr.r * 255.0f);
|
234
|
+
baseLinearColor.g = (byte)Mathf.RoundToInt(linearColorHdr.g * 255.0f);
|
235
|
+
baseLinearColor.b = (byte)Mathf.RoundToInt(linearColorHdr.b * 255.0f);
|
236
|
+
}
|
237
|
+
else
|
238
|
+
{
|
239
|
+
var scaleFactor = 255.0f / maxColorComponent;
|
240
|
+
exposure = Mathf.Log(maxColorComponent, 2.0f);
|
241
|
+
baseLinearColor.r = (byte)Math.Min(255, Mathf.CeilToInt(scaleFactor * linearColorHdr.r));
|
242
|
+
baseLinearColor.g = (byte)Math.Min(255, Mathf.CeilToInt(scaleFactor * linearColorHdr.g));
|
243
|
+
baseLinearColor.b = (byte)Math.Min(255, Mathf.CeilToInt(scaleFactor * linearColorHdr.b));
|
244
|
+
}
|
245
|
+
}
|
246
|
+
}
|
247
|
+
```
|
248
|
+
|
249
|
+
マテリアル上でもIntensityが2になりました。
|
250
|
+
|
251
|
+

|
252
|
+
|
253
|
+
一見うまくいったように見えるかもしれませんが、逆にスクリプト側のベースカラー輝度が191でも、マテリアル側のベースカラー輝度は255になってしまいます。
|
254
|
+
|
255
|
+

|
256
|
+
|
257
|
+
(255, 128, 64)、Intensity 2の色と(191, 96, 48)、Intensity 2.416924の色は計算誤差を無視すれば同等で区別できず、カラーピッカー側で本来のベースカラー・Intensityを復元するのは無理っぽいです...
|
2
スクリプト側の値を矯正する例を追記
test
CHANGED
@@ -39,3 +39,82 @@
|
|
39
39
|
入力されたHDRカラーがLDRカラーで表現できない(明度が1/255を下回っているか、1を超えている)場合、元のHDRカラーの明度が191/255に(`k_MaxByteForOverexposedColor`として定義されている値に)なるようスケーリングしたものをベースカラーとして決めて、それに合わせたIntensityを計算しているらしいですね。
|
40
40
|
|
41
41
|
カラーピッカー上の値とスクリプト上の値の不整合を解消するとなると、おそらくスクリプト側の値の方をカラーピッカー側の値に合わせる...つまり`ColorMutator`と同じ計算式で色を矯正して、`ColorCode`と`intensityValue`の方をカラーピッカーと一致するよう書き換えることになるんじゃないかと思います。
|
42
|
+
|
43
|
+
## スクリプト側の値を矯正する例
|
44
|
+
|
45
|
+
下記のようなスクリプトをオブジェクトにアタッチして...
|
46
|
+
|
47
|
+
```C#
|
48
|
+
using UnityEngine;
|
49
|
+
|
50
|
+
[RequireComponent(typeof(Renderer))]
|
51
|
+
public class EmissionController : MonoBehaviour
|
52
|
+
{
|
53
|
+
private static readonly int EmissionColorProperty = Shader.PropertyToID("_EmissionColor");
|
54
|
+
|
55
|
+
[ColorUsage(false)] public Color ColorCode = Color.black;
|
56
|
+
public float intensityValue;
|
57
|
+
|
58
|
+
private Color previousColorCode;
|
59
|
+
private float previousIntensityValue = float.NaN;
|
60
|
+
private new Renderer renderer;
|
61
|
+
|
62
|
+
private void Update()
|
63
|
+
{
|
64
|
+
if (this.renderer == null)
|
65
|
+
{
|
66
|
+
this.renderer = this.GetComponent<Renderer>();
|
67
|
+
}
|
68
|
+
|
69
|
+
// ColorCodeもintensityValueも前フレームから変わっていなければ
|
70
|
+
// Emission更新の必要はないと見なして何もしない
|
71
|
+
if ((this.ColorCode == this.previousColorCode) && (this.intensityValue == this.previousIntensityValue))
|
72
|
+
{
|
73
|
+
return;
|
74
|
+
}
|
75
|
+
|
76
|
+
// まずColorCodeをintensityValueに応じて増幅し、Emissionにセットするべき色を決める
|
77
|
+
var hdrColor = this.ColorCode * Mathf.Pow(2.0f, this.intensityValue);
|
78
|
+
hdrColor.a = this.ColorCode.a;
|
79
|
+
|
80
|
+
// EmissionにhdrColorをセットする
|
81
|
+
var material = this.renderer.material;
|
82
|
+
if (material != null)
|
83
|
+
{
|
84
|
+
material.EnableKeyword("_EMISSION");
|
85
|
+
material.SetColor(EmissionColorProperty, hdrColor);
|
86
|
+
}
|
87
|
+
|
88
|
+
// そしてhdrColorをベースカラーとIntensityに分解する
|
89
|
+
// まずhdrColorのRGBのうち最も大きい成分の値を取得する
|
90
|
+
var maxComponent = hdrColor.maxColorComponent;
|
91
|
+
|
92
|
+
// intensityValueはひとまず0にする
|
93
|
+
this.intensityValue = 0.0f;
|
94
|
+
|
95
|
+
// その最大成分が0なら完全な黒なのでLDRで表現でき、分解の必要はない
|
96
|
+
// また、1/255以上1以下ならLDRで表現でき、分解の必要はない
|
97
|
+
if ((maxComponent != 0.0f) && ((maxComponent < (1.0f / 255.0f)) || (maxComponent > 1.0f)))
|
98
|
+
{
|
99
|
+
// そうでないならば、まずmaxComponentに対する191/255の倍率を求める
|
100
|
+
var scale = 191.0f / (maxComponent * 255.0f);
|
101
|
+
|
102
|
+
// そしてそのスケールを使ってColorCodeのRGBを矯正する
|
103
|
+
this.ColorCode = hdrColor * scale;
|
104
|
+
this.ColorCode.a = hdrColor.a;
|
105
|
+
|
106
|
+
// また、scaleの逆数の2を底とする対数を求め、intensityValueとする
|
107
|
+
this.intensityValue = Mathf.Log(1.0f / scale, 2.0f);
|
108
|
+
}
|
109
|
+
|
110
|
+
// 最後に、次のフレームに備えて現在のColorCodeとintensityValueを保存しておく
|
111
|
+
this.previousColorCode = this.ColorCode;
|
112
|
+
this.previousIntensityValue = this.intensityValue;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
```
|
116
|
+
|
117
|
+
ゲーム実行時にスクリプト側の`ColorCode`と`intensityValue`を設定したところ、`ColorCode`は少し暗く、`intensityValue`は少し大きく変化しました。
|
118
|
+
そして、マテリアル側のEmissionもスクリプト側と同じ設定値になりました。
|
119
|
+
|
120
|
+

|
1
「輝度」は「明度」の方が妥当なように思い直して修正
test
CHANGED
@@ -36,6 +36,6 @@
|
|
36
36
|
> }
|
37
37
|
> ```
|
38
38
|
|
39
|
-
入力されたHDRカラーがLDRカラーで表現できない(
|
39
|
+
入力されたHDRカラーがLDRカラーで表現できない(明度が1/255を下回っているか、1を超えている)場合、元のHDRカラーの明度が191/255に(`k_MaxByteForOverexposedColor`として定義されている値に)なるようスケーリングしたものをベースカラーとして決めて、それに合わせたIntensityを計算しているらしいですね。
|
40
40
|
|
41
41
|
カラーピッカー上の値とスクリプト上の値の不整合を解消するとなると、おそらくスクリプト側の値の方をカラーピッカー側の値に合わせる...つまり`ColorMutator`と同じ計算式で色を矯正して、`ColorCode`と`intensityValue`の方をカラーピッカーと一致するよう書き換えることになるんじゃないかと思います。
|