- 2022/06/20 【Unity】【C#】音量設定用にデシベル(dB)変換をする
- 2021/09/16 【Unity】【C#】ガンマ(Gamma, sRGB) - リニア(Linear) 値の相互変換
- 2021/07/23 【C#】文字列 → float (浮動小数点) 変換でエラーが出るときは…
- 2020/12/20 【Unity】【C#】範囲を指定できる Mathf.Repeat
- 2020/11/14 【C#】最小公倍数を求める(ユークリッドの互除法)
« prev next »
【Unity】【C#】音量設定用にデシベル(dB)変換をする 
2022/06/20 Mon [edit]
これも VRM Live Viewer の音量設定を作ったときのメモ。
Unity には AudioMixer という複数の音量(Master, Music, SE, ...等)をミキシングして出力できる便利な機能があるのだけれど、設定する値は 0~1f ではなく、デシベル[dB](-80dB~0dB) なんだよね。
デシベル変換はググればいくらでも出てくるけど、定型処理なら拡張メソッドにしておくと楽なので、いつものように簡単に定義しておいたもの。
あと AudioMixer 使う際に気になる事があったので、少しばかり工夫してる。
(参考)デシベルのすすめ (KAYAC engineers' blog)
(※) Unity 2020.3.34f1 / Windows11(x64) で確認
■音量(0~1f) → デシベル(-80~0dB) に変換する
●音量(0~1f) → デシベル(-80~0dB) に変換する
using UnityEngine;
namespace Exsample
{
public static partial class Extensions //※クラス名は任意
{
//無音とみなすデジベル(dB) や音量
const float DECIBEL_MIN = -80f; //この dB 以下をミュート(音無し)とする
const float DECIBEL_MUTE = DECIBEL_MIN * 10; //単位: dB (-80dB だと僅かに聞こえる気がするので、とりあえず10倍してる)
const float VOLUME_MIN = 0.0001f; //この volume 以下をミュート(音無し)とする
/// <summary>
/// 音量(0~1f) → デシベル(-80(-800)~0dB) に変換
/// ※ただし、volume ≤ 0.0001f のとき -800 [db] を返す
/// (参考) https://techblog.kayac.com/linear-vs-decibel
/// </summary>
/// <param name="volume">0~1f</param>
/// <returns>-80(-800)~0 [dB]</returns>
public static float ToDecibel(this float volume)
{
if (volume <= VOLUME_MIN) //0.0001f を最小値とする
return DECIBEL_MUTE; //-800dB (-80dB だと僅かに聞こえる気がするので、とりあえず10倍してる)
var db = 20 * Mathf.Log10(volume);
return Mathf.Clamp(db, DECIBEL_MUTE, 0f);
}
}
}
参考資料そのままだが、冒頭に書いた「少しばかり工夫してる」とは、最小値 (0.0001) 以下では DECIBEL_MUTE (-800dB) を返すところで、AudioMixer の Threshold のデフォ値(-80dB) 付近だと、わずかに音が聞こえる気がするからだ。もちろん Threshold の値を上げても良いが、毎回調整するのは面倒なので(笑)、とりま 10倍した値(-800dB)を返すようにしてる。これをそのまま AudioMixer.SetFloat に当ててやれば、確実に無音(ミュート)できる。
(参考)デシベルのすすめ (KAYAC engineers' blog)
アプリではわかりやすいように音量を 0~100(%) 表記してるが、やはり定型処理となるので、以下のような簡単な拡張メソッド(.NET 4.x)を作るのも良いだろう。
//音量(0~100%) → デシベル(-80(-800)~0dB) に変換
public static float PercentToDecibel(this float per) => ToDecibel(per * 0.01f);
■デシベル(-80~0dB) → 音量(0~1f) に変換する
●デシベル(-80~0dB) → 音量(0~1f) に変換する
using UnityEngine;
namespace Exsample
{
public static partial class Extensions //※クラス名は任意
{
/// <summary>
/// デシベル(-80~0dB) → 音量(0~1f) に変換
/// (参考) https://techblog.kayac.com/linear-vs-decibel
/// </summary>
/// <param name="decibel">-80~0dB</param>
/// <returns>0~1f</returns>
public static float FromDecibel(this float decibel)
{
if (decibel <= DECIBEL_MIN) //-80dB を最小値とする
return 0f;
return Mathf.Pow(10f, decibel / 20f);
}
}
}
これも参考資料そのままなので、詳細はそちらを参考にして欲しい。
(参考)デシベルのすすめ (KAYAC engineers' blog)
これも少し手を加えたのは、-80dB を引数に与えると 0.0001 の計算値になるので、強制的に 0 (無音) にしている所。この方が実用的で使いやすかっただけのこと。
例えば簡単にテストすると、以下のような結果になる。
var volMin = 0f;
var volMax = 1f;
var volOverMin = -1f;
var volOverMax = 2f;
//volume (0~1f) → dB (-80(-800)~0dB) 変換
var dBMin = volMin.ToDecibel();
var dBMax = volMax.ToDecibel();
var dBOverMin = volOverMin.ToDecibel();
var dBOverMax = volOverMax.ToDecibel();
Debug.Log($"volMin = {volMin}, ToDecibel = {dBMin}");
Debug.Log($"volMax = {volMax}, ToDecibel = {dBMax}");
Debug.Log($"volOverMin = {volOverMin}, ToDecibel = {dBOverMin}");
Debug.Log($"volOverMax = {volOverMax}, ToDecibel = {dBOverMax}");
//dB (-80(-800)~0dB) → volume (0~1f) 変換
var dBMinToVol = dBMin.FromDecibel();
var dBMaxToVol = dBMax.FromDecibel();
var dBOverMinToVol = dBOverMin.FromDecibel();
var dBOverMaxToVol = dBOverMax.FromDecibel();
Debug.Log($"dBMin FromDecibel = {dBMinToVol}");
Debug.Log($"dBMax FromDecibel = {dBMaxToVol}");
Debug.Log($"dBOverMin FromDecibel = {dBOverMinToVol}");
Debug.Log($"dBOverMax FromDecibel = {dBOverMaxToVol}");
volMax = 1, ToDecibel = 0
volOverMin = -1, ToDecibel = -800
volOverMax = 2, ToDecibel = 0
dBMin FromDecibel = 0
dBMax FromDecibel = 1
dBOverMin FromDecibel = 0
dBOverMax FromDecibel = 1
(関連記事)
【Unity】【C#】HDR Color を計算(変換)する
【Unity】【C#】ガンマ(Gamma, sRGB) - リニア(Linear) 値の相互変換
【C#】文字列 → float (浮動小数点) 変換でエラーが出るときは…
【Unity】【C#】ガンマ(Gamma, sRGB) - リニア(Linear) 値の相互変換 
2021/09/16 Thu [edit]
そろそろ UniVRM をアップデートしたいなぁ、と思ったけど、とうとう UniVRM0.79 以降はカラースペース(Color Space)をリニア(Linear)に統一するらしく、インポートしたら強制的に変更されるようになってしまった。
VRM Live Viewer は3年前、Unity2017初期の頃からはじまったのだが、CRS ステージは Unity4, Crystal ステージは Unity5 時代のものを流用してるんだよね。なので、ガンマ(Gamma)スペースなのだ。
まぁ、正直言って、特に Unity2018 以降の機能は Linear 依りになってきたので(なぜか今でも Unity のデフォは Gamma だが)、いずれ移行したいとは思ってたんだけどね。
しかし、さすがにマイナーバージョンアップデートで、前後の互換性がないのはおかしいし(不具合にしか見えないため)、メジャーアプデとして考えていた。なので急遽移行を迫られる形になってしまった。でも Color Space 変更って、プロジェクト素材全てに影響するので、とてもじゃないけどすぐにはできないんだよね(見た目がかなり変わる)。
なので、自動化できるところはスクリプトで変換などするために、色々調べることになった。これはそのメモ。ほぼ出典そのままだが、汎用的に使えるので掲載しておこう。
■汎用的な値(0~1f:正規化した値)での変換
●定義からの厳密なコード
●高速化近似式 (2.2乗, 1/2.2乗) での変換
●シンプルな3次の高速近似式での変換
■シェーダ(.shader 等)での利用
(※) Unity 2019.4 / Windows10(x64) で確認
■Unity の Color, Mathf での変換
ちなみに、Unity では Color 構造体が linear, gamma というプロパティがあり、Mathf には Mathf.GammaToLinearSpace, Mathf.LinearToGammaSpace という関数があるので、簡単に変換できる。例えば以下のようにできる。
using UnityEngine;
//ガンマ→リニア
var gammaColor = new Color(0.3f, 0.4f, 0.5f);
var linearColor = gammaColor.linear; //→ RGBA(0.073, 0.133, 0.214, 1.000)
var linearValue = Mathf.GammaToLinearSpace(0.5f); //→ 0.2140411ff
//リニア→ガンマ
var linearColor = new Color(0.073f, 0.133f, 0.214f);
var gammaColor = linearColor.gamma; //→ RGBA(0.300, 0.400, 0.500, 1.000)
var gammaValue = Mathf.LinearToGammaSpace(0.2140411f); //→ 0.5f
これで事足りてしまう場合は良いのだが、もう少しつっこんだ実装をしたいとか、高速化した近似式を使いたいというときもあるだろう。それらは以降のようになる。
■汎用的な値(0~1f:正規化した値)での変換
●定義からの厳密な Gamma - Linear 相互変換 (0~1f:正規化した値での計算)
using UnityEngine;
/// <summary>
/// ガンマ(sRGB)→リニア (0.0-1.0:正規化した値) 色空間変換 (厳密なコード)
/// (参考)
/// https://en.wikipedia.org/wiki/SRGB
/// https://tech.cygames.co.jp/archives/2339/
/// </summary>
/// <param name="gamma">0.0-1.0</param>
/// <returns>0.0-1.0</returns>
public static float ToLinearValueStrict(this float gamma)
{
if (gamma <= 0.04045f)
{
return gamma / 12.92f;
}
else
{
return Mathf.Pow((gamma + 0.055f) / 1.055f, 2.4f);
}
}
/// <summary>
/// リニア→ガンマ(sRGB) (0.0-1.0:正規化した値) 色空間変換 (厳密なコード)
/// (参考)
/// https://en.wikipedia.org/wiki/SRGB
/// https://tech.cygames.co.jp/archives/2339/
/// </summary>
/// <param name="linear">0.0-1.0</param>
/// <returns>0.0-1.0</returns>
public static float ToGammaValueStrict(this float linear)
{
if (linear <= 0.0031308f)
{
return linear * 12.92f;
}
else
{
return 1.055f * Mathf.Pow(linear, 0.4166666f) - 0.055f; //0.4166666f = 1/2.4
}
}
ここでは sRGB とガンマを同等に扱っているが、実際にはガンマ補正はディスプレイや機器などで値が違うようなので、あくまで一般的な仕様に合わせたものだと思って欲しい。
式は以下の参考資料そのままだ。定義は Wikipedia に掲載されている。
(定義) sRGB (From sRGB to CIE XYZ, From CIE XYZ to sRGB)
(参考:式) 物理ベースレンダリング -リニアワークフロー編
●高速化近似式 (2.2乗, 1/2.2乗) での変換
また、式の参考ページにも記載されているが、高速化用の近似式は以下のようになる。
●高速化近似式 Gamma - Linear 相互変換 (0~1f:正規化した値での計算)
using UnityEngine;
/// <summary>
/// ガンマ(sRGB)→リニア (0.0-1.0:正規化した値) 色空間変換 (高速化近似式: 2.2乗)
/// (参考) https://tech.cygames.co.jp/archives/2339/
/// </summary>
/// <param name="gamma">0.0-1.0</param>
/// <returns>0.0-1.0</returns>
public static float ToLinearValueFast(this float gamma)
{
return Mathf.Pow(gamma, 2.2f); //or 2.233333f
}
/// <summary>
/// リニア→ガンマ(sRGB) (0.0-1.0:正規化した値) 色空間変換 (高速化近似式: 1/2.2乗)
/// (参考) https://tech.cygames.co.jp/archives/2339/
/// </summary>
/// <param name="linear">0.0-1.0</param>
/// <returns>0.0-1.0</returns>
public static float ToGammaValueFast(this float linear)
{
return Mathf.Pow(linear, 0.45454545f); //1/2.2
}
なぜそうなるのかというのは、以下を参照すると良いだろう。とてもわかりやすく解説されている。
(参考資料)
・ガンマ補正のうんちく
・分かる!リニアワークフローのコンポジット
まぁ端的に言うと、曲線が似てれば、値も似るということだね(笑)。英語では「Magic Number」みたいにも言われている。
ちなみに、冒頭に書いた Unity の Color.linear や Color.gamma, Mathf.GammaToLinearSpace, Mathf.LinearToGammaSpace と値を照合してみたら、「~Strict」の関数(厳密なコード)はほぼ同じで、「~Fast」(高速化近似式)の方は6割くらい一致するようだ(※ただし、誤差を甘めで、小数点3ケタくらいで比較した場合。べき乗を 2.233333f にすると 67%くらい)。しかし見た目では、静止画ならともかく、動いてる絵なら見分けが付かないくらいだ。
また、これらの式をシェーダに使っている例もある。というより、近いものを Unity の PostProcessing で実装されているらしい。最後に抜粋してるので、興味があったら見てみると良い。
●シンプルな3次の高速近似式での変換
また、下記のシェーダの計算を見ていると、2.2乗の近似式は単純に2乗(c * c)にされているが、もう1つ近似式があるね。コメントの ref の先に解説もあるが、なるほど計算負荷が低いらしい(基本的に平方根やべき乗などは、単純な掛け算や足し算より負荷が高いため)。
もう1つの高速化近似式を C# で書いてみよう(そのまま)。
●シンプルな3次の高速近似式での Gamma → Linear 変換 (0~1f:正規化した値での計算)
using UnityEngine;
/// <summary>
/// ガンマ(sRGB)→リニア (0.0-1.0:正規化した値) 色空間変換 (高速化近似式: シンプルな3次)
/// (参考) http://chilliant.blogspot.com.au/2012/08/srgb-approximations-for-hlsl.html?m=1
/// </summary>
/// <param name="gamma">0.0-1.0</param>
/// <returns>0.0-1.0</returns>
public static float ToLinearValueFastCubic(this float gamma)
{
return gamma * (gamma * (gamma * 0.305306011f + 0.682171111f) + 0.012522878f);
}
最初の高速近似式(2.2乗)のコード内のコメントに「or 2.233333f」と書いてあるのは、こちらの資料にあったからだ。
実際に試してみると、こちらも動いてる分には、ぱっと見た目わからない。平方根使うより計算負荷が軽いので、Unity も採用しているのだろう。これは助かる。
(参考資料) sRGB Approximations for HLSL
■シェーダでの利用
以下のコードは実際に Unity の PostProcessing で使われているようだ。そのまま抜粋させて頂いた。
●シェーダ内 (.shader 等)での Gamma - Linear 相互変換 (0~1f:正規化した値での計算)
// sRGB transfer functions
// Fast path ref: http://chilliant.blogspot.com.au/2012/08/srgb-approximations-for-hlsl.html?m=1
//
half SRGBToLinear(half c)
{
#if USE_VERY_FAST_SRGB
return c * c;
#elif USE_FAST_SRGB
return c * (c * (c * 0.305306011 + 0.682171111) + 0.012522878);
#else
half linearRGBLo = c / 12.92;
half linearRGBHi = PositivePow((c + 0.055) / 1.055, 2.4);
half linearRGB = (c <= 0.04045) ? linearRGBLo : linearRGBHi;
return linearRGB;
#endif
}
half3 SRGBToLinear(half3 c)
{
#if USE_VERY_FAST_SRGB
return c * c;
#elif USE_FAST_SRGB
return c * (c * (c * 0.305306011 + 0.682171111) + 0.012522878);
#else
half3 linearRGBLo = c / 12.92;
half3 linearRGBHi = PositivePow((c + 0.055) / 1.055, half3(2.4, 2.4, 2.4));
half3 linearRGB = (c <= 0.04045) ? linearRGBLo : linearRGBHi;
return linearRGB;
#endif
}
half4 SRGBToLinear(half4 c)
{
return half4(SRGBToLinear(c.rgb), c.a);
}
half LinearToSRGB(half c)
{
#if USE_VERY_FAST_SRGB
return sqrt(c);
#elif USE_FAST_SRGB
return max(1.055 * PositivePow(c, 0.416666667) - 0.055, 0.0);
#else
half sRGBLo = c * 12.92;
half sRGBHi = (PositivePow(c, 1.0 / 2.4) * 1.055) - 0.055;
half sRGB = (c <= 0.0031308) ? sRGBLo : sRGBHi;
return sRGB;
#endif
}
half3 LinearToSRGB(half3 c)
{
#if USE_VERY_FAST_SRGB
return sqrt(c);
#elif USE_FAST_SRGB
return max(1.055 * PositivePow(c, 0.416666667) - 0.055, 0.0);
#else
half3 sRGBLo = c * 12.92;
half3 sRGBHi = (PositivePow(c, half3(1.0 / 2.4, 1.0 / 2.4, 1.0 / 2.4)) * 1.055) - 0.055;
half3 sRGB = (c <= 0.0031308) ? sRGBLo : sRGBHi;
return sRGB;
#endif
}
half4 LinearToSRGB(half4 c)
{
return half4(LinearToSRGB(c.rgb), c.a);
}
内容的には、厳密なコードと高速化近似式 (2.2乗 や 1/2.2乗) に同じか近いね。プリプロセッサの「USE_VERY_FAST_SRGB」や「USE_FAST_SRGB」で実行速度を選択できるらしい。
PositivePow は以下の参考資料を(LIGHT11)見て欲しい。正の値しか使わないのなら、ただの pow() でも可能のようだ。
(参考)
・PostProcessing/PostProcessing/Shaders/Colors.hlsl (Unity の PostProcessing 内)
・【Unity】シェーダにおける値のリニア <-> sRGB変換関数 (LIGHT11:PositivePow 掲載)
●PositivePow (負の値にしないべき乗) 抜粋
#define FLT_EPSILON 1.192092896e-07
// Using pow often result to a warning like this
// "pow(f, e) will not work for negative f, use abs(f) or conditionally handle negative values if you expect them"
// PositivePow remove this warning when you know the value is positive and avoid inf/NAN.
float PositivePow(float base, float power)
{
return pow(max(abs(base), float(FLT_EPSILON)), power);
}
float2 PositivePow(float2 base, float2 power)
{
return pow(max(abs(base), float2(FLT_EPSILON, FLT_EPSILON)), power);
}
float3 PositivePow(float3 base, float3 power)
{
return pow(max(abs(base), float3(FLT_EPSILON, FLT_EPSILON, FLT_EPSILON)), power);
}
float4 PositivePow(float4 base, float4 power)
{
return pow(max(abs(base), float4(FLT_EPSILON, FLT_EPSILON, FLT_EPSILON, FLT_EPSILON)), power);
}
結局、どれを使うかはケースバイケースだと思うが、実装を知ってると色々応用が効くので知っておいて損は無い。例えば、ガンマ補正の値は 2.2乗 みたいになってるが(sRGBディスプレイの現在のデファクトスタンダードであり、過去には色々値が異なっていたらしい)、これを引数をして変更すると、見た目も変わる。これを応用してシェーダの方で Properties に入れておくと、インスペクタで変化の加減を調整できるので、とても便利だ。
実際、VRM Live Viewer のプロジェクト内の素材を Linear 用に調整している最中だが、透過の無いテクスチャなどは、マテリアルの調整やいっそ PhotoShop でテクスチャの色調補正すれば、Gamma のときと同じ感じになるので事足りる。しかし、透過や反射するようなマテリアル(色や光の合成が行われるマテリアル)などは、どんなに調整しても Gamma のときと同じようにはならない(参考資料の「加算/合成が変わる」がわかりやすい)。そういう場合は、シェーダに変換式を入れないと、それっぽくはならないようだ(それでも完全には無理だが)。
(参考資料)
・分かる!リニアワークフローのコンポジット
(関連記事)
【Unity】Standard Assets の Flare は Gamma 用だった?
【Unity】色形式:Unity の Color と Android の ARGB(int32) の相互変換をする
【Unity】【C#】Quality (グラフィック品質) を文字列で取得/設定する
【Unity】【C#】画面解像度とアクペクト比(整数)を求める
【HTML】HTMLカラー名・カラーコード表
【C#】文字列 → float (浮動小数点) 変換でエラーが出るときは… 
2021/07/23 Fri [edit]
もう随分前の話になるが、フランス語圏の人から「VRM Live Viewer を使いたいのだが、起動ができない」というメッセージが来た。
とりあえずアプリのバージョンと状況を聞いて確認してみたが、普通に起動できる。念の為、BOOTH にも出しているので DL して試したが、やはり同じ結果だ。
しばらく何の事か全く見当が付かなかったのだが、根気よくスクショを送って貰ったり、動画を見せて貰ったりしているうちに、環境の違い(OS)くらいしかもう無いと意見が一致したので、彼は英語を、私はフランス語をインストールして OS を再起動してみた。
そしたらどうだ。彼は「起動に成功した」と言い、私は彼が言っていたように起動に失敗した。
原因は起動時に読み込まれる BVH ファイルで、そのファイルはテキストで書かれていて、小数点は "."(ドット) で書かれている。実はフランス語、アイスランド語など特定の言語圏では、小数点は "," (カンマ) で書くので、気の利いた OS (笑)がエラーを吐いていたのだ。
かくして、ライブラリ作者に不具合報告をして、今ではそのフランスの VTuber もめでたく利用して貰っている。
・特定の言語環境下において、BVHロードに失敗する
まぁ、そんなこんなで「文字列を float (浮動小数点)に変換するなら、拡張メソッドを作っておいた方が良いよ」(※使うなら double 等も) という事で、簡単なものを紹介しておこう。
●フランス語、アイスランド語など特定の言語圏でも対応できる 文字列 → float (浮動小数点) 変換
using System;
using System.Globalization;
public static class Extensions //名前は任意
{
/// <summary>
/// 文字列 → float 変換 (CultureInfo.InvariantCulture 指定)
/// </summary>
/// <param name="s">変換元文字列</param>
/// <returns>変換後の float (失敗は 0)</returns>
public static float ToFloat(this string s)
{
if (float.TryParse(s, NumberStyles.Number, CultureInfo.InvariantCulture, out var value))
{
return value;
}
return 0f;
}
/// <summary>
/// 文字列 → float 変換 (CultureInfo.InvariantCulture 指定)
/// </summary>
/// <param name="s">変換元文字列</param>
/// <param name="result">変換成功時 = float 値 / 変換失敗時 = 0</param>
/// <returns>true = 変換成功 / false = 変換失敗</returns>
public static bool TryParseFloat(this string s, out float result)
{
if (float.TryParse(s, NumberStyles.Number, CultureInfo.InvariantCulture, out var value))
{
result = value;
return true;
}
result = 0;
return false;
}
}
●メインコード例
using System;
var s = "1.234";
Console.WriteLine("ToFloat : " + s.ToFloat());
if (s.TryParseFloat(out var result))
{
Console.WriteLine("TryParseFloat : " + result);
}
else
{
Console.WriteLine("TryParseFloat failed");
}
TryParseFloat : 1.234
結果だけ見ると何て事ないのだが、この関数なら、フランス語、アイスランド語など特定の言語圏でもエラーにはならない。
ちなみに、float 変換のオプションに使っている「CultureInfo.InvariantCulture」は「カルチャに依存しない」(無視する) ものらしい。要するに OS の言語とは関係なく、常に一定(英語圏)で変換されるようだ。むしろそっちの方がデフォだと有り難いのだが…(例えば文字列比較の Equals 等もデフォだとカルチャが効くので言語によって結果が異なる可能性がある)。
必要なら double 等、他の浮動小数点も作っておいた方が良いだろう。
動作確認するには OS にフランス語を入れて、OS の言語設定でフランス語に設定した後で、OS を再起動して確かめるしかないが、特に最近はアプリが世界中で使われるような時代になったので、これは常備した方が良いだろう。実際私もフランスの人から報告貰うまでは考えもしなかった。
実は、不具合報告って開発者にとっては非常に有用な情報なので「なんか動作がおかしいな…」と思ったら、連絡してあげると良い。我々はバグ取りのエキスパートでもあるので、すぐに原因究明から修正案まで出せると思う。
実際、この不具合(特定の言語環境下において、BVHロードに失敗する) はライブライリ元の UniVRM が抱えていた問題だったので、これが Fix されると、フランス圏の人もアプリや VRM が利用できるようになったわけだしね(少なくとも BVH を使うアプリはそれまでフランス圏では利用できなかったハズ:3tene とかも BVH 機能入ってた気がする)。
単純に不具合が直ると自分も幸せだし、他の多くの利用者も幸せになれるしね(笑)。
(関連記事)
【Unity】【C#】StartsWith, EndsWith が遅いのなら、Equals も遅いのか?
【Unity】【C#】範囲を指定できる Mathf.Repeat
【C#】【Unity】enum 型と string, int 型の相互変換など
【Unity】色形式:Unity の Color と Android の ARGB(int32) の相互変換をする
- 関連記事
-
-
【C#】GUID の生成と書式
-
【C#】ソート済み List を動的に作る拡張メソッド
-
【C#】配列やコレクションの IsNullOrEmpty
-
【C#】文字列 → float (浮動小数点) 変換でエラーが出るときは…
-
【C#】多次元配列とジャグ配列(2次元配列)のサイズ(長さ)、相互変換など
-
category: C#
thread: プログラミング
janre: コンピュータ
tag: C#ライブラリ C#リファレンス 算術関数【Unity】【C#】範囲を指定できる Mathf.Repeat 
2020/12/20 Sun [edit]
今回は数学的な関数のちょっとした便利拡張メソッド。
Unity においては、特に角度の計算に Mathf.Repeat() はよく使う。例えば入力された値(角度)を -180~180 度に収めたいなどの時だ。
しかし、標準の Mathf.Repeat() では引数が元の値(t)と長さ(length)[=最大(を含まない)] なので、0~max(を含まない) の計算となる。なので、0~360(359) 度などには良いが、0 以外の開始値だと少し不便だったりする。
そこで、Mathf.Repeat() に少し手を加えて、-180~180(を含まない) または -180~180(を含む) のような、範囲内を返せる2つの拡張メソッドを作っておこう。
ちなみに Mathf クラスは C# 標準の Math クラスの軽量版で、C# 標準が double の計算に対して、Unity では float で計算するものと考えて良い。要するに精度より速度を優先したものである。なので、高い計算精度が欲しいときは Math クラスを使った方が良いだろう(当然処理速度は重くなるので注意)。
(※) Unity 2019.4 / Windows10(x64) で確認 (※バージョン依存はないと思う)
●下限~上限(を含まない)範囲を返す Mathf.Repeat
using UnityEngine;
public static class MathfExtentions //※クラス名は任意
{
/// <summary>
/// 下限~上限(を含まない)範囲を返す Mathf.Repeat
/// 2020/12/20 Fantom
/// http://fantom1x.blog130.fc2.com/blog-entry-390.html
/// </summary>
/// <param name="value">元の値</param>
/// <param name="min">下限 (inclusive)</param>
/// <param name="max">上限 (exclusive)</param>
/// <returns>min(inclusive)~max(exclusive) の Mathf.Repeat</returns>
public static float Repeat(this float value, float min, float max)
{
return Mathf.Repeat(value - min, max - min) + min;
}
//int 版
public static int Repeat(this int value, int min, int max)
{
return (int)Mathf.Repeat(value - min, max - min) + min;
}
}
●メインコード例
using UnityEngine;
using System.Text;
var sb = new StringBuilder(1024);
for (int i = -45; i <= 45; i++)
{
var x = i.Repeat(-45, 45);
if (sb.Length > 0)
sb.Append(", ");
sb.Append(x);
}
Debug.Log(sb.ToString());
注意点としては、値の上限(max)を「含まない」という点だ。
この例の場合、max = 45 のため「…, 43, 44, -45, -44, …」のように値がループする。
●下限~上限(を含む)範囲を返す Mathf.Repeat
)範囲を返す Mathf.Repeat
using UnityEngine;
public static class MathfExtentions //※クラス名は任意
{
/// <summary>
/// 下限~上限(を含む)範囲を返す Mathf.Repeat
/// 2020/12/20 Fantom
/// http://fantom1x.blog130.fc2.com/blog-entry-390.html
/// </summary>
/// <param name="value">元の値</param>
/// <param name="min">下限 (inclusive)</param>
/// <param name="max">上限 (inclusive)</param>
/// <returns>min(inclusive)~max(inclusive) の Mathf.Repeat</returns>
public static float RepeatInclusive(this float value, float min, float max)
{
return (value == max) ? max : Repeat(value, min, max); //※「下限~上限(を含まない)」関数が必要
}
//int 版
public static int RepeatInclusive(this int value, int min, int max)
{
return (value == max) ? max : Repeat(value, min, max);
}
}
●メインコード例
using UnityEngine;
using System.Text;
var sb = new StringBuilder(1024);
for (int i = -45; i <= 45; i++)
{
var x = i.RepeatInclusive(-45, 45);
if (sb.Length > 0)
sb.Append(", ");
sb.Append(x);
}
Debug.Log(sb.ToString());
注意点としては、値の上限(max)を「含む」という点だ。関数内部で前述の「下限~上限(を含まない)」版も利用している。
この例の場合、max = 45 のため「…, 43, 44, 45, -44, …」のように値がループする。
また、値の意味としては「-45 と 45 は同値扱い」となる。これは我々の感覚として「-180度 と 180 度が同じ」のように使えるわけで、特に UI において、ユーザーから値を指定してもらう際には便利になる(-180~179 度でも良いが、-180~180 度の方が、人の感覚としてはわかり易い)。
UI の実例としてはスライダー等に使うとわかり易いね。大したものではないが、意外と利用頻度は高いと思う。
|
3D 背景モデル:Floating Island of the Potion Brewer / @vilmariina
360度画像:++skies;008 / @aokcub
(関連記事)
【Unity】【C#】倍数での Floor, Ceil, Round(一定間隔での切り捨て、切り上げ、四捨五入) [float 版]
【C#】最大公約数を求める/分数の約分をする(ユークリッドの互除法)
【Java】最大公約数・最小公倍数を求める(ユークリッドの互除法)
【Java】3つ以上の最大公約数を求める(ユークリッドの互除法②)
【Java】拡張ユークリッドの互除法
- 関連記事
-
-
【Unity】Androidでチェックボックス付きのアラートダイアログ(AlertDialog)を使う
-
【Unity】A Native Collection has not been disposed, resulting in a memory leak. Enable Full StackTraces to get more details. と出たら…
-
【Unity】【エディタ拡張】ヒエラルキー(シーン)の Image, RawImage に使われている画像(Texture)を検出するエディタ拡張(ツール)
-
【Unity】5.6 のアニメーションは非アクティブ化でリセットされない(5.6のアニメーション挙動の違い)
-
【Unity】【C#】長押し(ロングタップ)を取得してコールバックする
-
category: Unity
thread: ゲーム開発
janre: コンピュータ
tag: Unityライブラリ Unityリファレンス 算術関数【C#】最小公倍数を求める(ユークリッドの互除法) 
2020/11/14 Sat [edit]
前回の最大公約数の求め方のついで。
ユークリッドの互除法が実装できてれば、最小公倍数を求めるのは簡単だ。
前回の Gcd (Greatest Common Divisor) を用いて、Lcm (Least Common Multiple) を書いてみよう。
●最小公倍数を求める(ユークリッドの互除法)
/// <summary>
/// 最小公倍数を求める(Least Common Multiple)
/// 2020/11/14 Fantom
/// http://fantom1x.blog130.fc2.com/blog-entry-385.html
/// http://fantom1x.blog130.fc2.com/blog-entry-384.html
/// http://fantom1x.blog130.fc2.com/blog-entry-215.html#lcm
/// </summary>
/// <param name="a">数値1 (>0)</param>
/// <param name="b">数値2 (>0)</param>
/// <returns>最小公倍数</returns>
public static int Lcm(this int a, int b)
{
return a * b / Gcd(a, b); //※Gcd は前回の記事を参照
}
●メインコード例
using System;
int h = 16;
int v = 9;
int m = Lcm(h, v);
Console.WriteLine("m = " + m);
a > 0, b > 0 となっているので注意(特に負の剰余は言語によって結果が違うので注意)。
Gcd() は前回の記事からコピペしてきて欲しい。
これも Java からのそのまま移植なので、解説は以前の記事を見て欲しい。ユークリッド互除法や最小公倍数の定義に関する説明も Wikipedia で良いだろう。数学的なものはこの辺りの例を見ればわかるだろう。
定義をそのままコーディングしたようなものなので、他言語でも簡単に移植できると思う。
・【Java】最大公約数・最小公倍数を求める(ユークリッドの互除法)
・ユークリッドの互除法 (Wikipedia)
・最小公倍数 (Wikipedia)
・最大公約数と最小公倍数
(関連記事)
【C#】最大公約数を求める/分数の約分をする(ユークリッドの互除法)
【Unity】【C#】画面解像度とアクペクト比(整数)を求める
【Java】最大公約数・最小公倍数を求める(ユークリッドの互除法)
【Java】3つ以上の最大公約数を求める(ユークリッドの互除法②)
【Java】拡張ユークリッドの互除法