使用バージョン Unity2019.4.13f1
EmissionのIntensityをスクリプトから調整したいのですが、調べると出てくるSet("_EmissionColor", ColorCode * intensityValue)
を試していますが、intenstiyValueに渡した数値通りになりません。
例えば4を渡してマテリアルのintensityの実数値を確認すると4ではなく2.416924となっていました。
8を渡せば4になるのかと思えば、3.416925となっていました。
調べた中では渡すintensityをMathf.Pow(2,intensity)
とすると書いてある場合もありましたが、これも試してみましたが値が近付きはするものの同じ値にはなりませんでした。
何か方法が間違っているのでしょうか?どのようにすればEmissionのIntensityを指定した値と同じ値に変更できるでしょうか。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

回答2件
0
Intensityのセットについては、Mathf.Pow(2,intensity)
の方を使うのが適切だろうと思います。
カラーピッカーで見てみたときの値の狂いについては、おそらくカラーピッカーがHDRカラーをベースカラーとIntensityに分解するときの計算方法のせいじゃないでしょうかね?
たとえば下図では、EmissionとしてRGBがすべて255の白色、Intensityが2の色をセットしているのですが、カラーピッカーをいったん閉じて開き直すとRGBが191に暗くなり、代わりにIntensityは2.416924に上昇してしまいました。
ご質問者さんの場合も、もしかしてIntensityだけではなくベースカラーの方も本来のColorCode
とは違う値になってはいませんでしょうか?
以前別の方の「Unity マテリアルのEmission intensityについて」とのご質問に回答しました際に調べたところによると、どうやらこのHDRカラーの分解はColorMutatorが行っているようでしたが、下記引用のように...
C#
1 internal static void DecomposeHdrColor(Color linearColorHdr, out Color32 baseLinearColor, out float exposure) 2 { 3 baseLinearColor = linearColorHdr; 4 var maxColorComponent = linearColorHdr.maxColorComponent; 5 // replicate Photoshops's decomposition behaviour 6 if (maxColorComponent == 0f || maxColorComponent <= 1f && maxColorComponent >= 1 / 255f) 7 { 8 exposure = 0f; 9 10 baseLinearColor.r = (byte)Mathf.RoundToInt(linearColorHdr.r * 255f); 11 baseLinearColor.g = (byte)Mathf.RoundToInt(linearColorHdr.g * 255f); 12 baseLinearColor.b = (byte)Mathf.RoundToInt(linearColorHdr.b * 255f); 13 } 14 else 15 { 16 // calibrate exposure to the max float color component 17 var scaleFactor = k_MaxByteForOverexposedColor / maxColorComponent; 18 exposure = Mathf.Log(255f / scaleFactor) / Mathf.Log(2f); 19 20 // maintain maximal integrity of byte values to prevent off-by-one errors when scaling up a color one component at a time 21 baseLinearColor.r = Math.Min(k_MaxByteForOverexposedColor, (byte)Mathf.CeilToInt(scaleFactor * linearColorHdr.r)); 22 baseLinearColor.g = Math.Min(k_MaxByteForOverexposedColor, (byte)Mathf.CeilToInt(scaleFactor * linearColorHdr.g)); 23 baseLinearColor.b = Math.Min(k_MaxByteForOverexposedColor, (byte)Mathf.CeilToInt(scaleFactor * linearColorHdr.b)); 24 } 25 }
入力されたHDRカラーがLDRカラーで表現できない(明度が1/255を下回っているか、1を超えている)場合、元のHDRカラーの明度が191/255に(k_MaxByteForOverexposedColor
として定義されている値に)なるようスケーリングしたものをベースカラーとして決めて、それに合わせたIntensityを計算しているらしいですね。
カラーピッカー上の値とスクリプト上の値の不整合を解消するとなると、おそらくスクリプト側の値の方をカラーピッカー側の値に合わせる...つまりColorMutator
と同じ計算式で色を矯正して、ColorCode
とintensityValue
の方をカラーピッカーと一致するよう書き換えることになるんじゃないかと思います。
スクリプト側の値を矯正する例
下記のようなスクリプトをオブジェクトにアタッチして...
C#
1using UnityEngine; 2 3[RequireComponent(typeof(Renderer))] 4public class EmissionController : MonoBehaviour 5{ 6 private static readonly int EmissionColorProperty = Shader.PropertyToID("_EmissionColor"); 7 8 [ColorUsage(false)] public Color ColorCode = Color.black; 9 public float intensityValue; 10 11 private Color previousColorCode; 12 private float previousIntensityValue = float.NaN; 13 private new Renderer renderer; 14 15 private void Update() 16 { 17 if (this.renderer == null) 18 { 19 this.renderer = this.GetComponent<Renderer>(); 20 } 21 22 // ColorCodeもintensityValueも前フレームから変わっていなければ 23 // Emission更新の必要はないと見なして何もしない 24 if ((this.ColorCode == this.previousColorCode) && (this.intensityValue == this.previousIntensityValue)) 25 { 26 return; 27 } 28 29 // まずColorCodeをintensityValueに応じて増幅し、Emissionにセットするべき色を決める 30 var hdrColor = this.ColorCode * Mathf.Pow(2.0f, this.intensityValue); 31 hdrColor.a = this.ColorCode.a; 32 33 // EmissionにhdrColorをセットする 34 var material = this.renderer.material; 35 if (material != null) 36 { 37 material.EnableKeyword("_EMISSION"); 38 material.SetColor(EmissionColorProperty, hdrColor); 39 } 40 41 // そしてhdrColorをベースカラーとIntensityに分解する 42 // まずhdrColorのRGBのうち最も大きい成分の値を取得する 43 var maxComponent = hdrColor.maxColorComponent; 44 45 // intensityValueはひとまず0にする 46 this.intensityValue = 0.0f; 47 48 // その最大成分が0なら完全な黒なのでLDRで表現でき、分解の必要はない 49 // また、1/255以上1以下ならLDRで表現でき、分解の必要はない 50 if ((maxComponent != 0.0f) && ((maxComponent < (1.0f / 255.0f)) || (maxComponent > 1.0f))) 51 { 52 // そうでないならば、まずmaxComponentに対する191/255の倍率を求める 53 var scale = 191.0f / (maxComponent * 255.0f); 54 55 // そしてそのスケールを使ってColorCodeのRGBを矯正する 56 this.ColorCode = hdrColor * scale; 57 this.ColorCode.a = hdrColor.a; 58 59 // また、scaleの逆数の2を底とする対数を求め、intensityValueとする 60 this.intensityValue = Mathf.Log(1.0f / scale, 2.0f); 61 } 62 63 // 最後に、次のフレームに備えて現在のColorCodeとintensityValueを保存しておく 64 this.previousColorCode = this.ColorCode; 65 this.previousIntensityValue = this.intensityValue; 66 } 67}
ゲーム実行時にスクリプト側のColorCode
とintensityValue
を設定したところ、ColorCode
は少し暗く、intensityValue
は少し大きく変化しました。
そして、マテリアル側のEmissionもスクリプト側と同じ設定値になりました。
カラーピッカー側の値を矯正しようとするとどうなるか
たとえばスクリプトを下記のようなものに変更した場合(なんだかマルウェア的な変なことをしていて気持ち悪いかもしれませんが、あくまでも実験ということでご容赦ください)...
C#
1using System; 2using System.Reflection; 3using System.Runtime.CompilerServices; 4using System.Runtime.InteropServices; 5using UnityEngine; 6#if UNITY_EDITOR 7using UnityEditor; 8#endif 9 10[RequireComponent(typeof(Renderer))] 11public class EmissionController2 : MonoBehaviour 12{ 13 private static readonly int EmissionColorProperty = Shader.PropertyToID("_EmissionColor"); 14 15 [ColorUsage(false)] public Color ColorCode = Color.black; 16 public float intensityValue; 17 18 private readonly byte[] decomposeHdrColorBackup = new byte[5]; 19 private IntPtr decomposeHdrColorPointer; 20 private Color previousColorCode; 21 private float previousIntensityValue = float.NaN; 22 private new Renderer renderer; 23 24 private void Update() 25 { 26 if (this.renderer == null) 27 { 28 this.renderer = this.GetComponent<Renderer>(); 29 } 30 31 // ColorCodeもintensityValueも前フレームから変わっていなければ 32 // Emission更新の必要はないと見なして何もしない 33 if ((this.ColorCode == this.previousColorCode) && (this.intensityValue == this.previousIntensityValue)) 34 { 35 return; 36 } 37 38 // まずColorCodeをintensityValueに応じて増幅し、Emissionにセットするべき色を決める 39 var hdrColor = this.ColorCode * Mathf.Pow(2.0f, this.intensityValue); 40 hdrColor.a = this.ColorCode.a; 41 42 // EmissionにhdrColorをセットする 43 var material = this.renderer.material; 44 if (material != null) 45 { 46 material.EnableKeyword("_EMISSION"); 47 material.SetColor(EmissionColorProperty, hdrColor); 48 } 49 50 // ColorMutator.DecomposeHdrColorを乗っ取り、代わりにDecomposeHdrColor255を実行させるようにする 51 this.HijackDecomposeHdrColor(); 52 53 // 最後に、次のフレームに備えて現在のColorCodeとintensityValueを保存しておく 54 this.previousColorCode = this.ColorCode; 55 this.previousIntensityValue = this.intensityValue; 56 } 57 58 // コンポーネント破壊時にColorMutator.DecomposeHdrColorを元の状態に戻す 59 private void OnDestroy() 60 { 61 if (this.decomposeHdrColorPointer == IntPtr.Zero) 62 { 63 return; 64 } 65 Marshal.Copy(this.decomposeHdrColorBackup, 0, this.decomposeHdrColorPointer, this.decomposeHdrColorBackup.Length); 66 this.decomposeHdrColorPointer = IntPtr.Zero; 67 } 68 69 // ColorMutator.DecomposeHdrColorを乗っ取り、DecomposeHdrColor255へ制御を移すよう改変を加える 70 private void HijackDecomposeHdrColor() 71 { 72 #if UNITY_EDITOR 73 if (this.decomposeHdrColorPointer != IntPtr.Zero) 74 { 75 return; 76 } 77 78 // ColorMutator.DecomposeHdrColorのメモリ上の位置を取得する 79 const BindingFlags bindingFlags = BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Static; 80 var decomposeHdrColorMethodHandle = typeof(EditorWindow).Assembly.GetType("UnityEditor.ColorMutator").GetMethod("DecomposeHdrColor", bindingFlags).MethodHandle; 81 RuntimeHelpers.PrepareMethod(decomposeHdrColorMethodHandle); 82 this.decomposeHdrColorPointer = decomposeHdrColorMethodHandle.GetFunctionPointer(); 83 84 // DecomposeHdrColor255のメモリ上の位置を取得する 85 var decomposeHdrColor255MethodHandle = typeof(EmissionController2).GetMethod(nameof(DecomposeHdrColor255), bindingFlags).MethodHandle; 86 RuntimeHelpers.PrepareMethod(decomposeHdrColor255MethodHandle); 87 var decomposeHdrColor255Pointer = decomposeHdrColor255MethodHandle.GetFunctionPointer(); 88 89 // コンポーネント破壊時にColorMutator.DecomposeHdrColorを復元するため、先頭5バイトをバックアップしておく 90 Marshal.Copy(this.decomposeHdrColorPointer, this.decomposeHdrColorBackup, 0, this.decomposeHdrColorBackup.Length); 91 92 // ColorMutator.DecomposeHdrColorの先頭5バイトにDecomposeHdrColor255へジャンプするコードを埋め込む 93 Marshal.WriteByte(this.decomposeHdrColorPointer, 0xE9); 94 Marshal.WriteInt32(this.decomposeHdrColorPointer, 1, (int)decomposeHdrColor255Pointer - (int)this.decomposeHdrColorPointer - this.decomposeHdrColorBackup.Length); 95 #endif 96 } 97 98 // オリジナルのColorMutator.DecomposeHdrColorはベースカラーの基準値が191だが、 99 // こちらは255を基準にベースカラーを決める 100 private static void DecomposeHdrColor255(Color linearColorHdr, out Color32 baseLinearColor, out float exposure) 101 { 102 baseLinearColor = linearColorHdr; 103 var maxColorComponent = linearColorHdr.maxColorComponent; 104 if (maxColorComponent is 0.0f or <= 1.0f and >= 1.0f / 255.0f) 105 { 106 exposure = 0.0f; 107 baseLinearColor.r = (byte)Mathf.RoundToInt(linearColorHdr.r * 255.0f); 108 baseLinearColor.g = (byte)Mathf.RoundToInt(linearColorHdr.g * 255.0f); 109 baseLinearColor.b = (byte)Mathf.RoundToInt(linearColorHdr.b * 255.0f); 110 } 111 else 112 { 113 var scaleFactor = 255.0f / maxColorComponent; 114 exposure = Mathf.Log(maxColorComponent, 2.0f); 115 baseLinearColor.r = (byte)Math.Min(255, Mathf.CeilToInt(scaleFactor * linearColorHdr.r)); 116 baseLinearColor.g = (byte)Math.Min(255, Mathf.CeilToInt(scaleFactor * linearColorHdr.g)); 117 baseLinearColor.b = (byte)Math.Min(255, Mathf.CeilToInt(scaleFactor * linearColorHdr.b)); 118 } 119 } 120}
マテリアル上でもIntensityが2になりました。
一見うまくいったように見えるかもしれませんが、逆にスクリプト側のベースカラー輝度が191でも、マテリアル側のベースカラー輝度は255になってしまいます。
(255, 128, 64)、Intensity 2の色と(191, 96, 48)、Intensity 2.416924の色は計算誤差を無視すれば同等で区別できず、カラーピッカー側で本来のベースカラー・Intensityを復元するのは無理っぽいです...
投稿2022/02/21 10:11
編集2022/03/02 12:36総合スコア10816
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2022/02/22 01:44
2022/02/22 13:46
2022/03/01 07:19
2022/03/02 12:36
2022/03/03 02:43