ユーザー定義関数と組み込み関数
ここでは、ユーザー定義関数と、他の章で解説してこなかった標準的な関数をまとめます。
ユーザー定義関数(v1.0.03より)
ユーザー定義関数は、fnというキーワードで始めて、仮引数に型指定を書いたブロックを続ける、というものになっています。
fn add2|x:i32, y:i32| {
x+y
}
テンソルやrsumなどのブロックと違い、引数に型指定が必要になります。 戻りの型の表記は無く、ブロックの最後の式の型がこの関数の戻りの型となります。
こうして定義した関数は通常の組み込み関数と同様にカッコで呼び出す事で使う事が出来ます。
def result_u8 |x, y| {
let res = add2(x, y)
u8[res, 0, 0, 0]
}
使える引数はスカラーとベクトルのみです。タプルやテンソルは使えません。 ベクトルはf32v2などの表記で表します。
fn adder |v:f32v2| {
v.x+v.y
}
関数名のあとの空白はあっても無くても構いません。
関数のAPIの記述方法
以下ではMFGの組み込み関数のドキュメントを提供します。
ですが、MFGでは関数のreturn typeはあらわには明示されないため、ドキュメンテーションの観点からは不足があります。
そのために、このドキュメント上で引数や戻りの型を記述するためのノーテーションを最初に決めておきます。
関数名の前に戻りの型を、引数の名前のあとにコロンで引数の型を書きます。
f32 sin |x:f32|
これは、引数一つで型はf32、戻りはf32である事を表します。 複数の引数はカンマで区切ります。
f32 clamp|x:f32, minVal:f32, maxVal:f32|
特殊なベクトルを表すT, Ti
MFGの関数には、OpenGLの用語でいう所のgenType、Metalの用語でいう所のTに相当する関数が多くあります。 それらが分かる人はそれと同じ話と思って以下を読んでください。
MFGでは、ベクトルまたはスカラーのどちらかをサポートする関数というものがあります。また、ベクトルをサポートしスカラーはサポートしない、という関数もあります。 ベクトルというのは型でも説明しましたが、同じ型のタプルで2〜4要素のものです。 4要素まで、という制約があるのは、下の環境が提供している関数の制約がそのまま反映される為です。
例えばlengthなどが典型的なベクトルをサポートした関数です。
length([1.0, 2.0, 3.0])
lengthは引数が2から4要素までのベクトルしかサポートしていませんし、 また、要素はすべてf32型でなくてはならず、中にi32が混ざっていてもコンパイルエラーです。
このように、タプルのうちf32のベクトルだけをサポートする関数、というのが幾つかあります。 ベクトルは2次元でも3次元でも4次元でも良い、というものになるので、普通にf32v2とかf32v3とか一つの書き方では書き表せないため、 特別な記法を作っておく方が簡便です。
さらに、ベクトルの次元は2〜4までどれでも良いけれど、2つの引数の次元が揃っていないといけない、 という関数もあります。
2つのベクトルの間の距離を求めるdistanceなどはその一例です。
# OK
distance([1.0, 1.0], [2.0, 2.0])
distance([1.0, 1.0, 1.0], [2.0, 2.0, 2.0])
# NG
distance([1.0, 1.0], [2.0, 2.0, 2.0])
このように、次元は2次元でも3次元でも4次元でもいいけれど、2つの引数の次元が揃っていないといけない、 という関数が良くあります。
ほとんどのこの手の関数は、ベクトルの引数や戻りの型がいくつかあって、その全てが同じ次元でなくてはいけない、 というパターンになっています。
こういう言葉にするとややこしい事情を表記するために、 f32のベクトルをTという表記で書き、同じTは同じ次元のベクトル、という事を表す事にします。
f32 distance|x:T, y:T|
この時、Tはf32の2要素〜4要素のタプルで、xとyは同じ次元となります。
また、Tと同様だが要素がi32のものをTiと表記します。 これはallやanyなどの論理演算で出てきます。
ベクトルのみをサポートする関数と、ベクトルとスカラーの両方をサポートする関数もあります。 これは記法では区別せずに、スカラーも可能なものは「Tはスカラーも可」と補足する事にします。
ベクトライズで使える関数
ベクトルをサポートしているケースと似ているけれど違うものとして、ベクトライズ、という機能があります。 ベクトライズは基本的には1要素のf32を引数を取る関数で使える機能で、4要素に限らない、無制限の要素数のタプルに対して使えます。 R言語のベクトライズを知っている人なら、同じものと思ってもらってOKです。
例えばsinは以下のような関数ですが、
f32 sin |x:f32|
これはベクトライズで使えます。
ベクトライズは、タプルを渡すとその個々の要素に関数を呼び出したかのように振る舞う機能です。
let res = sin([1.0, 2.0, 3.0, 4.0, 5.0])
これはシンタックスシュガーで、内部では以下のように変換されます。
let res = [sin(1.0) sin(2.0), sin(3.0), sin(4.0), sin(5.0)]
この場合、APIの表記としては単一引数の表記をして、そのあとに ベクトライズ可 と付記するようにしています。
色変換とサポートしている色
MFGでは、必要な計算に応じてガンマ補正をしたりCIE XYZカラーにしたりする必要があるため、これらの間の変換をサポートしています。
色の略称
それぞれの色には略称がつけられています。
| 色の名前 | 略称 | 解説 |
|---|---|---|
| u8カラー、u16カラー | u8color、u16color | 各BGRAをu8やu16といった整数の4次元ベクトルで保持する色です。リニア化されてないBGRAで、input_u8などがそれで、BGRAをu8の4次元ベクトルで扱います。リニア化についてはガンマ補正を参照。 |
| ノーマライズドカラー | ncolor | u8カラーやu16カラーを単純に0.0〜1.0の範囲にスケールしたものをノーマライズドカラー、ncolorと呼んでいます。これもリニア化されていません。 |
| リニアライズドBGR, BGRA | lbgr、lbgra | ncolorをリニア化したものを、linealized bgrと呼んでいて、lbgrと略記します。lbgraはそれにアルファを加えたものですが、アルファは最初からリニアなのでそのままの値を保持しています。 |
| CIE XYZ, 及びそのアルファ | xyz, xyza | lbgrをCIE XYZカラーに変換したものをxyzと略記しています。swizzleのxyzと被ってしまっているので注意が必要です。xyzはいつもリニアライズされています。 |
それぞれの間の変換
基本的には 「元のカラー名_to_結果のカラー名」という関数名で変換します。lbgr_to_xyz などです。
u8color以外は基本的にはアルファがなければf32v3、アルファがあればf32v4となります。 lbgr_to_xyzなどはf32v3を引数にf32v3を返します。
f32v3 lbgr_to_xyz |col:f32v3|
u8v4 xyza_to_u8color |col:f32v4|
また、元のカラー名がu8colorの時は省略されます。
f32v4 to_xyza |col:u8v4|
f32v4 to_lbgra |col:u8v4|
to_ncolor
f32v4 to_ncolor |col:u8v4|
u8v4のBGRAの色を、ノーマライズドカラーに変換する。 f32は0.0〜1.0の範囲。
例
let ncolor = to_ncolor(input_u8(x, y))
to_u8color
u8v4 to_u8color| col:f32v4 |
ncolorからu8v4のBGRAへの変換を行う。 colの要素は0.0〜1.0でなくてはいけない。
to_u8colorは0.0〜1.0にclampしてu8にするので、1.0より大きな値は1.0に、マイナスの値は0.0として扱われる。
例
let u8_bgra = to_u8color(ncolor)
リニア化されたBGRA関連
ガンマ補正済みのリニア化されたBGRA関連の関数としては以下があります。(v1.0.01にて実装)
f32v4 to_lbgra|col:u8v4|
u8v4 lbgra_to_u8color|col:f32v4|
例
def result_u8 |x, y| {
let lcol = to_lbgra(input_u8(x, y))
lbgra_to_u8color(lcol)
}
CIE XYZカラー関連
以下の関数があります。(v1.0.01にて実装)
f32v4 to_xyza| col:u8v4 |
u8v4 xyza_to_u8color| col:f32v4 |
f32v3 lbgr_to_xyz| col:f32v3 |
f32v3 xyz_to_lbgr| col:f32v3 |
例
def result_u8 |x, y| {
let xcol = to_xyza(input_u8(x, y))
xyza_to_u8color(xcol)
}
ガンマ補正
ガンマ補正を処理したい時は、そのまま to_lbgraやto_xyzaを呼んでリニア化された色を取得する方が一般的ですが、 より細かい処理をしたい時はリニア化だけをするための関数も提供されています。
以下の2つの関数がガンマ補正の基本となる関数です。
T gamma2linear| ncolor:T |T linear2gamma| ncolor:T |
Tはf32のベクトル、またはスカラーです。
4要素のタプルまで動くけれど、ガンマ補正はRGBについて行うものでアルファは普通はリニアなものなので、 普通は3要素のタプルで使います。
引数としてはノーマライズドカラー(floatの0.0〜1.0の色)です。
通常の画像ファイルなどはガンマ補正が入った値になっているので、それをlinearのスケールに戻して計算を進め、 そのあと最後にふたたびガンマ補正するのが通常の流れとなります。
典型的には以下のようなコードになります。
let bgra = to_ncolor(input_u8(x, y))
let lin_bgr = gamma2linear(bgra.xyz)
let alpha = bgra.w
# 何か処理
to_u8color([*linear2gamma(lin_bgr), alpha])
具体例としてはガウスぼかしの「ガウスフィルタ、linRGB2」を参照の事。
現在では以下のように書いて、直接lbgrを取得する方が簡単で同じ挙動になります。
let lbgra = to_lbgra(input_u8(x, y))
let lin_bgr = lbgra.xyz
let alpha = lbgra.w
# 何か処理
lbgra_to_u8color(lbgra)
また、アルファをそのまま素通ししてそのほかの要素だけそれぞれを適用する、末尾にAのついた関数もあります。
f32v4 gamma2linearA| ncolor:f32v4 |(v1.0.01より)f32v4 linear2gammaA| ncolor:f32v4 |(v1.0.01より)
この場合は4次元固定です。以下の2つは同じ意味になります。
let lcol1 = gamma2linearA(ncol)
let lcol2 = [*gamma2linear(ncol.xyz), ncol.w]
gamma2linear
# Tはf32のスカラーでもOK
T gamma2linear| ncolor:T |
f32v4 gamma2linearA| ncolor:f32v4 | # v1.0.01より
引数はガンマ補正されているノーマライズドカラーの成分。成分ごとに計算するので別にBGRの順番でなくてもいいし、例えば全てRでも構わない。 input_u8はガンマ補正された状態の値です。
linear2gamma
# Tはf32のスカラーでもOK
T linear2gamma| ncolor:T |
f32v4 linear2gammaA| ncolor:f32v4 | # v1.0.01より
リニアライズされたノーマライズドカラーを、ガンマ補正したノーマライズドカラーに変換する。 result_u8に戻す前にはガンマ補正された値にする必要がある。
rand
f32 rand| |
0.0〜1.0の乱数を返します。
三角関数やlogなどの関数
引数が一つのものはだいたいベクトライズして使えます。 atan2は引数が2つなのでベクトライズは出来ず、Tだけサポートとなります。
| API | ベクトライズ可? | 説明 |
|---|---|---|
f32 exp| x:f32 | | yes | eの指数乗 |
f32 exp2| x:f32 | | yes | 2の指数乗 |
f32 sin| x:f32 | | yes | 角度はラジアン |
f32 cos| x:f32 | | yes | |
f32 tan| x:f32 | | yes | |
f32 log| x:f32 | | yes | 底がeの対数を計算 |
f32 log2| x:f32 | | yes | 底が2の対数を計算 |
f32 sqrt| x:f32 | | yes | |
T atan2| y:T, x:T | | no | y/xのアークタンジェントを計算 |
小数の端数や符号などの計算
| API | ベクトライズ可? | 説明 |
|---|---|---|
f32 abs|x: f32| | yes | i32も可能 |
f32 ceil|x: f32| | yes | 切り上げ。x以上の最小の整数を返す。 |
f32 floor|x: f32| | yes | x以下の最大の整数を返す |
f32 fract|x: f32| | yes | xの小数部分を返す |
f32 round|x: f32| | yes | xを四捨五入した整数を返す |
f32 saturate|x: f32| | yes | xを0.0から1.0の間にclampした値を返す |
f32 sign|x: f32| | yes | xが正なら1.0, xが負なら-1.0を返す |
f32 trunc|x: f32| | yes | xの端数部を切り捨てた整数を返す |
i32 isnan|x:f32| | yes | xがnanなら1を、そうでなければ0を返す |
i32 isinf|x:f32| | yes | xがinfなら1を、そうでなければ0を返す |
ベクトル系関数
引数にベクトルを取る関数。全てベクトライズは出来ません。
| API | 説明 |
|---|---|
i32 all|x: Ti| | xの要素が全てノンゼロなら1を、そうでなければ0を返す |
i32 any|x: Ti| | xの要素が一つでもノンゼロなら1を、全てゼロなら0を返す |
f32 distance|x:T, y:T| | 2つのベクトル、xとyの間の距離を求める |
f32 dot|x:T, y:T| | xとyの内積を求める |
f32v3 cross|x:f32v3, y:f32v3| | xとyの外積を求める |
f32 length|x: T| | ベクトルxの長さを求める |
T normalize|x: T| | ベクトルxを長さ1にノーマライズした同じ向きのベクトルを返す(x/length(x)と同じ) |
スカラーとベクトルの両方を使える関数
引数にf32か、f32のベクトルを取る関数。つまり以下のTは全てf32も可です。 全てベクトライズ出来ません。
| API | 説明 |
|---|---|
T mix|x:T, y:T, a:T| | xとyの線形補間、x+(y-x)*a を返す。aは0.0から1.0までの範囲でなくてはいけない。 |
T step|edge:T, x:T| | xがedgeより小さいと0.0、それ以外だと1.0を返す |
T smoothstep|edge0:T, edge1:T, x:T| | xがedge0より小さいと0.0、edge1以上だと1.0を返し、間はスムースになるような補完を行う(後述) |
smoothstepはシェーダーで一般的な関数です。 smoothstepの補完は以下の計算式で行います。
let t = clamp((x – edge0)/(edge1 –edge0), 0.0, 1.0)
t * t * (3.0 – 2.0 * t);
ベクトルを返すvec3とvec4
スカラーを渡してその同じ値を3要素に持つベクトルを返すvec3と、 同じ値を4要素持つベクトルを返すvec4があります。
どちらもベクトライズは出来ません。
i32v3 vec3|x:i32|またはf32v3 vec3|x:f32|i32v4 vec4|x:i32|またはf32v4 vec4|x:f32|
例
let fvec = vec3(3.0)
let ivec = vec4(1)
解説
引数がf32ならf32のベクトルを、引数がi32ならi32のベクトルを返します。
シェーダー言語では複数要素を指定する事が出来るのが一般的ですが、 MFGではこれはサポートしていません。
MFGではただのタプルが同じ意味なのでそちらを使ってください。
# let fvec = vec3(1.0, 2.0, 3.0) はサポートしてない。以下が同じ意味
let fvec = [1.0, 2.0, 3.0]
スカラー、ベクトル、i32とf32のサポート関係が複雑なものたち
以下の関数は、ここまで述べたどの関数とも異なる引数の制約を持ちます。
- clamp
- min
- max
clamp
clampはスカラーとベクトルが使えて、さらにi32とf32のどちらにも対応しています。
厳密に書けば、以下の4通りがあるという事です。
f32 clamp|x:f32, minVal:f32, maxVal:f32|
i32 clamp|x:i32, minVal:i32, maxVal:i32|
T clamp|x:T, minVal:T, maxVal:T|
Ti clamp|x:Ti, minVal:Ti, maxVal:Ti|
clampはxがminValより小さければminValを、maxValより大きければmaxValを、それ以外はxの値を返します。
minとmaxは可変長引数
minとmaxは可変長引数で個々の要素が全て同じ型のケースを対応しています。
可変長引数というのは、以下のようになっているという事です。
min(1.5, 3.2, 2.0)
max(3, 2, 5, 7)
これは一見するとベクトライズのように見えますが、タプルでない所が違います。
# ベクトライズ。引数は1つだがタプルの要素数が任意
sin([1.0, 2.0, 3.0, 4.0])
# minは引数が4つ
min(1.5, 3.2, 2.0, 4.0)
また、各引数がベクトルの場合も対応しています。
だから、以下のような事が出来ます。
# 結果は[3, 3, 3]
max([1, 2, 3], [3, 2, 1], [1, 3, 2])
各要素のmaxの結果のタプルとなります。
これまでの書き方だと、
T max|x1:T, x2:T, ...|
T min|x1:T, x2:T, ...|
さらにTはTiも使えて、f32とi32も使える、というものになります。
カラー取得関数(since v1.0.08)
| API | 説明 |
|---|---|
f32v4 fore_color| | | 前景色を取得(ncolor) |
f32v4 back_color| | | 背景色を取得(ncolor) |
MFGが動いているペイントアプリ(FireAlpacaなど)の現在選ばれている色と、 現在選ばれている背景色をそれぞれとる。
グラデーションなどで使われる前傾色と背景色。
色はncolor、つまり0.0〜1.0の4要素ベクトルだが、アルファ要素はいつも1.0。
そのほかの関数
他にどこにも置き場の無い関数をここにまとめます。
| API | 説明 |
|---|---|
Ti xor|x:Ti, y:Ti| | ビットごとのxとyの排他的論理和(xor) |
xorのTiはu32も対応しています。 通常のシェーダーでは^に対応するものですが、 MFGではこれは累乗に使っているので、ビット演算でxorだけ関数になっています。