【Unity】【C#】HDR Color を計算(変換)する 
2022/05/20 Fri [edit]
現在の VRM Live Viewer にはパーティクルの発光色を設定できる機能があり、HDRカラーピッカーが実装されているのだけど、これを作ったときのメモ。
そもそも Unity にはエディタ上で普通に HDRカラーピッカーが入ってるのだけど、なぜか API にはそういった Utility クラスが無いんだよね。なので、ググってたら、いくつか見つけたので、それを少しばかり使いやすくした例。
(参考) How to Get / Set HDR Color intensity
■HDR Color → LDR Color と intensity に分離する
■HDR Color から intensity のみを取り出す
(※) Unity 2020.3.26f1 / Windows11(x64) で確認
■LDR Color と intensity → HDR Color に変換する
参考資料を見てみると、どうやら HDR Color の計算は LDR Color の アルファを除いた RGB の各要素に 2 の intensity 乗(= 2intensity) を掛ければ良いようだ。実際にやってみたら、Unity エディタ上のカラーピッカーの色とも合致する。なので、変換する拡張関数を定義すると以下のようになる。
(参考) How to Get / Set HDR Color intensity
●LDR Color と intensity → HDR Color に変換する
using UnityEngine;
namespace Exsample
{
public static partial class Extensions //※クラス名は任意
{
/// <summary>
/// LDR Color と intensity → HDR Color に変換する
/// https://answers.unity.com/questions/1652854/how-to-get-set-hdr-color-intensity.html
/// </summary>
/// <param name="ldrColor">LDR Color</param>
/// <param name="intensity">輝度(intensity)</param>
/// <returns>HDR Color</returns>
public static Color ToHDRColor(this Color ldrColor, float intensity)
{
var factor = Mathf.Pow(2, intensity);
return new Color(
ldrColor.r * factor,
ldrColor.g * factor,
ldrColor.b * factor,
ldrColor.a
);
}
}
}
これは参考資料をそのまま拡張メソッドにしただけである。intensity = 0 のときは 20 = 1 なので、元のままとなるね。2 の intensity 乗(= 2intensity) というのは、そういうものと覚えておけば良いだろう。
(参考) How to Get / Set HDR Color intensity
■HDR Color → LDR Color と intensity に分離する
次は前述の HDR Color 変換とは逆で、HDR Color を LDR Color と intensity に分離する計算だ。といっても参考資料そのままでも良いので、ここでは少しだけ使いやすく、戻り値を Tuple 型(.NET 4.x)にして、表現の値を255値にした例を書いておこう。
●HDR Color → LDR Color と intensity に分離する
using UnityEngine;
namespace Exsample
{
public static partial class Extensions //※クラス名は任意
{
private const byte k_MaxByteForOverexposedColor = 191; //internal Unity const
/// <summary>
/// HDR Color を baseColor, intensity(exposure) に分離する (Tuple)
/// https://answers.unity.com/questions/1652854/how-to-get-set-hdr-color-intensity.html
/// ※overExposed = 255 を与えると直感的になる : (白) baseColor = RGBA(1.000, 1.000, 1.000, 1.000), intensity = 1
/// Unity デフォ = 191 で HDR Color ピッカーと同等 : (白) baseColor = RGBA(0.749, 0.749, 0.749, 1.000), intensity = 1.416925
/// </summary>
/// <param name="linearColorHdr">HDR Color</param>
/// <param name="overExposed">internal Unity const = 191 (デフォルト: 255)</param>
/// <returns>(LDR Color, intensity)</returns>
public static (Color32, float) DecomposeHdrColor2(this Color linearColorHdr, byte overExposed = 255)
{
Color32 baseLinearColor32 = linearColorHdr; //※暗黙変換には型が必要
var exposure = 0f;
var maxColorComponent = linearColorHdr.maxColorComponent; //※たぶん (r,g,b) 内で一番高い値 → 1f を超えてたら HDR となる
// replicate Photoshops's decomposition behaviour : Photoshopの分解動作を複製する
if (maxColorComponent == 0f || maxColorComponent <= 1f && maxColorComponent >= 1 / 255f) //1/255 = 0.003921568627451
{
exposure = 0f;
baseLinearColor32.r = (byte)Mathf.RoundToInt(linearColorHdr.r * 255f);
baseLinearColor32.g = (byte)Mathf.RoundToInt(linearColorHdr.g * 255f);
baseLinearColor32.b = (byte)Mathf.RoundToInt(linearColorHdr.b * 255f);
}
else
{
// calibrate exposure to the max float color component : 最大フロートカラーコンポーネントへの露出を調整します
var scaleFactor = overExposed / maxColorComponent;
exposure = Mathf.Log(255f / scaleFactor) / Mathf.Log(2f);
// maintain maximal integrity of byte values to prevent off-by-one errors when scaling up a color one component at a time
baseLinearColor32.r = Math.Min(overExposed, (byte)Mathf.CeilToInt(scaleFactor * linearColorHdr.r));
baseLinearColor32.g = Math.Min(overExposed, (byte)Mathf.CeilToInt(scaleFactor * linearColorHdr.g));
baseLinearColor32.b = Math.Min(overExposed, (byte)Mathf.CeilToInt(scaleFactor * linearColorHdr.b));
}
return (baseLinearColor32, exposure);
}
}
}
//メインでは…
//var (baseColor32, intensity) = emissionColor.DecomposeHdrColor2(); //overExposed = 255
コードも参考資料とほとんど変わらない。ただ、Unity エディタで HDRカラーピッカーを使っている時、Intensity の値を上げると、再び開き直した時、なぜか 191 になってるのが不思議だったが、どうやら内部の定数値「k_MaxByteForOverexposedColor = 191」が効いていたようだ。なので overExposed で 255 をデフォルトで与えることによって(RGB 要素で 255 を超えた場合の表現方法らしい)、より LDR Color が直感的になったので変更している。Unity エディタと同じにしたければ、overExposed = k_MaxByteForOverexposedColor (191) を引数に与えれば良い。元コードのコメントを読むと、191 は PhotoShop に合わせた値らしいが、色コード("#FFFFFF" のような形式)として扱う場合には、ちょっと使いづらいんだよね…。
(参考) How to Get / Set HDR Color intensity
■HDR Color から intensity のみを取り出す
これは前述の HDR Color → LDR Color と intensity に分離する の縮小版みたいなものだ。実際に DecomposeHdrColor2() のコードの一部のみを抜き出した感じとなる。輝度(Intensity)だけ欲しいときなど、短く書けるので一応載せておこう(もちろん、DecomposeHdrColor2() を使って intensity だけを得ても同じ)。
●HDR Color → LDR Color と intensity に分離する
using UnityEngine;
namespace Exsample
{
public static partial class Extensions //※クラス名は任意
{
private const byte k_MaxByteForOverexposedColor = 191; //internal Unity const
/// <summary>
/// HDR Color から Intensity のみを取得する
/// https://answers.unity.com/questions/1652854/how-to-get-set-hdr-color-intensity.html
/// ※overExposed = 255 を与えると直感的になる : (白) baseColor = RGBA(1.000, 1.000, 1.000, 1.000), intensity = 1
/// Unity デフォ = 191 で HDR Color ピッカーと同等 : (白) baseColor = RGBA(0.749, 0.749, 0.749, 1.000), intensity = 1.416925
/// </summary>
/// <param name="hdrColor">HDR Color</param>
/// <param name="overExposed">internal Unity const = 191</param>
/// <returns>輝度(intensity)</returns>
public static float ExtractIntensity(this Color hdrColor, byte overExposed = 255)
{
var maxColorComponent = hdrColor.maxColorComponent;
var scaleFactor = overExposed / maxColorComponent;
return Mathf.Log(255f / scaleFactor) / Mathf.Log(2f);
}
}
}
これは参考資料の最後にあるコードと同じ。これも DecomposeHdrColor2() と同じように、overExposed の引数を k_MaxByteForOverexposedColor (191) にすれば、Unity エディタ上の HDRカラーピッカーと同じ値になる。
(参考)
・How to Get / Set HDR Color intensity
(関連記事)
【Unity】【C#】ガンマ(Gamma, sRGB) - リニア(Linear) 値の相互変換
【Unity】色形式:Unity の Color と Android の ARGB(int32) の相互変換をする
- 関連記事
トラックバック
トラックバックURL
→http://fantom1x.blog130.fc2.com/tb.php/413-d33f8669
この記事にトラックバックする(FC2ブログユーザー)
| h o m e |