Apple Engine

Apple, iPhone, iOS, その周辺のことについて

ARKit や SceneKit で使用している simd について - 関数編 -

今回は simd の演算用の関数についての説明。

simd のライブラリはベクトル内の各要素対して1つの命令で各要素に適応させることができる。

以前に紹介した通り、+演算子を使用すると、2つのベクトルの要素ごとに合計を簡単に計算することができる。

let f3_1 = simd_float3(1.0, 1.0, 1.0)
let f3_2 = simd_float3(2.0, 3.0, 2.0)

f3_1 + f3_2
// 計算結果: float3(3.0, 4.0, 3.0) 
// simd_float3 と float3 の Type Alias なので同じもの

 

四則演算に関しては演算子で簡単に行うことができるが、若干面倒な計算に関しては簡単に処理できる関数が用意されている。

例えば simd_length を使用すると、原点 0 から対象ベクトルを使用し長さを算出する。
以下の例では、X と Y を 1 にしているため、√2が返る。
X と Y が 1 ということは直角三角形と同じなので、√2 になるのはわかりやすいだろう。

let f3 = float3(1.0, 1.0, 0.0)
simd_length(f3) // 1.414214 (√2)

 

simd_length は原点からの距離を取るが、2つの対象間の距離を取りたい場合は simd_distance を使用する。

以下の例では、X, Y が 2 と X, Y が 1 のものがあり、2点間の距離は √2 となる。

let f3_1 = simd_float3(1.0, 1.0, 0.0)
let f3_2 = simd_float3(2.0, 2.0, 0.0)

simd_distance(f3_2, f3_1) // 1.414214 (√2)

 

ゲームの場合、敵がプレイヤーとある一定の距離になったら攻撃してくるなど、3D 空間で距離を測ることがそこそこある。
ゲームや AR でこの関数を使用すると一発で距離が測れて便利だと思われる。

 

simd の算術関数

simd の 関数は simd で定義されている simd_ のプレフィックスがついている関数と simd.common や simd.geometry などで定義されている関数がある。

simd simd.common / geometry
simd_equal
abs simd_abs
ceil
clamp simd_clamp
cross
distance
distance_squared
dot
simd_incircle
simd_insphere
simd_length
simd_length_squared
min simd_min
max simd_max
mix simd_mix
sign simd_sign
norm_inf
norm_one
normalize
project
recip simd_recip
simd_fast_recip
simd_precise_recip
rsqrt simd_rsqrt
simd_fast_rsqrt
simd_precise_rsqrt
simd_fract
simd_muladd
simd_orient
reduce_add simd_reduce_add
reduce_min simd_reduce_min
simd_reduce_max
reflect
refract
simd_bitselect
simd_select
smoothstep simd_smoothstep
step simd_step
trunc

 

各関数の紹介

simd_equal

2つの引数から simd_bool (Bool) を返す。

let f3_1 = simd_float3(1.0, 1.0, 1.0)
let f3_2 = simd_float3(1.0, 1.0, 1.0)
let f3_3 = simd_float3()

simd_equal(f3_1, f3_2) // true
simd_equal(f3_1, f3_3) // false

 

simd_abs (abs)

引数の絶対値を返す。

let f3_1 = simd_float3(-1.0, -1.0, -1.0)
let f3_2 = simd_float3(2.0, -2.0, 2.0)

simd_abs(f3_1) // float3(1.0, 1.0, 1.0)
simd_abs(f3_2) // float3(2.0, 2.0, 2.0)

 

ceil

引数を整数値へ切り上げる。

let f3 = simd_float3(1.1, 1.6, 1.5)

ceil(f3) // float3(2.0, 2.0, 2.0)

 

simd_clamp (clamp)

最初の引数に対して min と max 間ので値を変更する。
以下のサンプルでは 1.0 から 2.5 の値に設定しているため、 0.8 は下限値 1 となり、2.4 はそのまま、3.1 は上限値を超えているため 2.5 に変更される。

let f3 = simd_float3(0.8, 2.4, 3.1)
let f3_min = simd_float3(1.0, 1.0, 1.0)
let f3_max = simd_float3(2.5, 2.5, 2.5)

clamp(f3, min: f3_min, max: f3_max)
// float3(1.0, 2.4, 2.5)

 

cross

2つの引数から外積を返す。
2点から直行するベクトルを返し、
以下の例では、Y と Z に 1 を設定しており、計算結果は X の 1 となる。

let f3_1 = simd_float3(0.0, 1.0, 0.0)
let f3_2 = simd_float3(0.0, 0.0, 1.0)

cross(f3_1, f3_2) // float3(1.0, 0.0, 0.0)

 

distance

2つの引数から2点間の距離を返す。

let f3_1 = simd_float3(1.0, 1.0, 0.0)
let f3_2 = simd_float3(2.0, 2.0, 0.0)

simd_distance(f3_2, f3_1) // 1.414214 (√2)

 

distance_squared

2つの引数から2点間の距離の2乗を返す。

let f3_1 = simd_float3(1.0, 1.0, 0.0)
let f3_2 = simd_float3(2.0, 2.0, 0.0)

simd_distance(f3_2, f3_1) // 2

 

dot

2つの引数から2点間の内積を返す。

dot 関数のみ simd_quatd, simd_quatf を引数にすることができる。

let f3_1 = simd_float3(1.0, 1.0, 0.0)
let f3_2 = simd_float3(1.0, 0.0, 1.0)

dot(f3_1, f3_2) // 1

 

floor

各成分(要素)の小数点を切り捨てる。

let f3 = float3(1.1, 2.2, 3.3)

floor(f3) // float3(1.0, 2.0, 3.0)

 

simd_incircle

x が a、b、c を通る円の内側か外側にあるかどうかを調べる。

(a、b、c)は正の向きで、x が円の内側にあれば正、x が円上にある場合はゼロ、x が円の外側にある場合は負の値になる。
(a、b、c)が負の向きの場合、結果の符号が反転する。

let f2_a = simd_float2(0.0, 0.0)
let f2_b = simd_float2(2.0, 0.0)
let f2_c = simd_float2(1.0, 2.0)

simd_incircle(float2(1.0, 1.0), f2_a, f2_b, f2_c) //  6
simd_incircle(float2(0.0, 0.0), f2_a, f2_b, f2_c) //  0
simd_incircle(float2(2.0, 2.0), f2_a, f2_b, f2_c) // -4

 

simd_insphere

x が a、b、c、d を通過する球の内側か外側にあるかどうかを調べる。

a、b、c、d の各点が正の方向を向いていると仮定すると、x が球の内側にある場合は正、x が球上にある場合はゼロ、x が球の外側にある場合は負の値になる。
各点がマイナスの場合、結果の符号が反転する。

let f2_a = simd_float3( 0.0, 2.0, 0.0)
let f2_b = simd_float3(-2.0,-2.0, 2.0)
let f2_c = simd_float3( 2.0,-2.0, 2.0)
let f2_d = simd_float3( 0.0,-2.0,-2.0)

simd_insphere(float3(1.0, 1.0, 1.0), f2_a, f2_b, f2_c, f2_d) //  224
simd_insphere(float3(0.0, 2.0, 0.0), f2_a, f2_b, f2_c, f2_d) //    0
simd_insphere(float3(1.0, 1.0, 3.0), f2_a, f2_b, f2_c, f2_d) // -160

 

simd_length

引数と原点から2点間の距離を返す。

let f3 = simd_float3(1.0, 1.0, 0.0)

simd_length(f3) // 1.414214 (√2)

 

simd_length_squared

引数と原点から2点間の距離を2乗で返す。

let f3 = simd_float3(1.0, 1.0, 0.0)

simd_length_squared(f3) // 2

 

simd_min (min)

2つの引数の中から最小要素を返す。

let f3_1 = simd_float3(1.0, 3.0, 0.5)
let f3_2 = simd_float3(5.0, 2.0, 2.0)

min(f3_1, f3_2)
// float3(1.0, 2.0, 0.5)

 

simd_max (max)

2つの引数の中から最大要素を返す。

let f3_1 = simd_float3(1.0, 3.0, 0.5)
let f3_2 = simd_float3(5.0, 2.0, 2.0)

max(f3_1, f3_2)
// float3(5.0, 3.0, 2.0)

 

simd_mix (mix)

2つの引数を引数 t で線形補間して返す。

let f3_1 = simd_float3(1.0, 1.0, 1.0)
let f3_2 = simd_float3(3.0, 5.0, 3.0)

mix(f3_1, f3_2, t: 0.25)
// float3(1.5, 2.0, 1.5)

let f3_3 = simd_float3(2.0, 2.0, 2.0)

mix(f3_1, f3_2, t: f3_3)
// float3(5.0, 9.0, 5.0)

 

simd_sign (sign)

0 より大きい場合は 1、0 より小さい場合は -1。 それ以外は 0 を返す。

let f3_1 = float3( 1.0,  2.0,  3.0)
let f3_2 = float3(-5.0, -4.0, -3.0)

sign(f3_1) // float3( 1.0,  1.0,  1.0)
sign(f3_2) // float3(-1.0, -1.0, -1.0)

 

norm_inf

引数から最大成分(最大要素)の絶対値(無限大ノルム / sup norm)を返す。

let f3_1 = simd_float3(1.0, 1.0, 1.0)
let f3_2 = simd_float3(3.0, 5.0, 2.0)

norm_inf(f3_1) // 1
norm_inf(f3_2) // 5

 

norm_one

引数から Taxicab norm(Manhattan norm)を返す。
例えば、要素 1 が1ブロックとした際に端から端までのブロックの最短経路の数を割り出すことができる。
3 x 5 x 2 の立方体があった時最短は 10 となる。

let f3_1 = simd_float3(1.0, 1.0, 1.0)
let f3_2 = simd_float3(3.0, 5.0, 2.0)

norm_one(f3_1) // 3
norm_one(f3_2) // 10

 

normalize

引数を正規化する。

let f3 = simd_float3(8.0, 4.0, 2.0)

normalize(f3)
// float3(0.87287146, 0.43643573, 0.21821786)

 

project

2つの引数の射影を返す。

let f3_1 = simd_float3(4.0, 2.0, 1.0)
let f3_2 = simd_float3(16.0, 8.0, 4.0)

project(f3_1, f3_2)
// float3(4.0, 2.0, 1.0)

 

simd_recip (recip)

逆数 (1/x) を返す。
0 は無限大になるので注意。

デフォルトでは simd_precise_recip と同じ振る舞い。
-ffast-math が設定されている場合は simd_fast_recip が動作する。

let f3 = simd_float3(2.0, 1.0, -2.0) // float3(0.49999997, 0.99999994, -0.49999997)

recip(f3)

 

simd_fast_recip

逆数 (1/x) を高速で返す。
結果がオーバーフロー、アンダーフローする可能性あり。
また、この関数は float は 11ビット、double は 22ビットまでしか正確さは保証されない。

let f3 = simd_float3(2.0, 1.0, -2.0)

simd_fast_recip(f3) // float3(0.49987793, 0.99975586, -0.49987793)

 

simd_precise_recip

逆数 (1/x) を精密な値で返す。
結果がオーバーフロー、アンダーフローする可能性あり。

let f3 = simd_float3(2.0, 1.0, -2.0)

simd_precise_recip(f3)
// float3(0.49999997, 0.99999994, -0.49999997)

 

simd_rsqrt (rsqrt)

逆平方根 (1/√x) を返す。
0 は無限大になるので注意。

デフォルトでは simd_precise_rsqrt と同じ振る舞い。
-ffast-math が設定されている場合は simd_fast_rsqrt が動作する。

let f3 = simd_float3(100, 16.0, 4.0)

rsqrt(f3)
// float3(0.1, 0.24999999, 0.49999997)

 

simd_fast_rsqrt

平方根 (1/√x) をを高速で返す。 結果がオーバーフロー、アンダーフローする可能性あり。
また、この関数は float は 11ビット、double は 22ビットまでしか正確さは保証されない。

let f3 = simd_float3(100, 16.0, 4.0)

simd_fast_rsqrt(f3)
// float3(0.099990845, 0.24993896, 0.49987793)

 

simd_precise_rsqrt

逆平方根 (1/√x) を精密な値で返す。
結果がオーバーフロー、アンダーフローする可能性あり。

let f3 = simd_float3(100, 16.0, 4.0)

simd_precise_rsqrt(f3)
// float3(0.1, 0.24999999, 0.49999997)

 

simd_fract

小数点の値を返す。 負の値の場合、反転するので注意。

let f3 = simd_float3(1.1, 2.2, -3.4)

simd_fract(f3) // float3(0.100000024, 0.20000005, 0.5999999)

 

simd_muladd

引数の積和演算をする。

下の例では float3(2,4,2) × float3(4,4,4) で float3(8,16,8)、
その結果を float3(8,8,8) と足すと float3(16.0, 24.0, 16.0) になる。

let f3_1 = float3(2,4,2)
let f3_2 = float3(4)
let f3_3 = float3(8)

simd_muladd(f3_1, f3_2, f3_3)
// float3(16.0, 24.0, 16.0)

 

simd_orient

2、3次元ベクトル、または三角錐からベクトルの向きをテストする。

2次元(0、x、y)、3次元 (x, y, z) で x と y の外積から z の内積を出す。 三角錐の場合 a-d と b-d の外積が c-d の内積に相当する。

let f3_1 = simd_float3(2.0, 0.0, 0.0)
let f3_2 = simd_float3(0.0, 2.0, 0.0)
let f3_3 = simd_float3(0.0, 0.0, 5.0)

simd_orient(f3_1, f3_2, f3_3)

 

simd_reduce_add (reduce_add)

成分(要素)の合計。
計算後に型の上限を超えオーバーフローする可能性があるので注意。

let f3 = simd_float3(2.0, 2.5, 5.0)

simd_reduce_add(f3) // 9.5

 

simd_reduce_min (reduce_min)

成分(要素)の最小値。

let f3 = simd_float3(-2.0, 2.5, 5.0)

simd_reduce_min(f3) // -2.0

 

simd_reduce_max

成分(要素)の最大値。

let f3 = simd_float3(-2.0, 2.5, 5.0)

simd_reduce_max(f3) // 5

 

reflect

反射の値を返す。

最初の引数から法線ベクトル n を元に反射させた値を返す。 以下の例では f3_2 は -x に座標があり、法線ベクトル x: 1.0 方向に反射されるため、 +x の値になったものを返す。

let f3_1 = simd_float3(-2.0, 2.0, 2.0)
let f3_2 = simd_float3(1.0, 0.0, 0.0)

reflect(f3_1, n: f3_2) // float3(2.0, 2.0, 2.0)

 

refract

屈折の値を返す。

最初の引数は入射ベクトル、n は屈折させる面の法線ベクトル、eta は屈折率。

入射ベクトルと表面の間の角度が非常に小さい場合は全反射が発生するため 0 が返る。

以下の例では x、y が屈折率 1.5 分だけ屈折方向に移動した値を返している。

let f3_1 = simd_float3(-2.0, 2.0, 0.0)
let f3_2 = simd_float3(1.0, 0.0, 0.0)

refract(f3_1, n: f3_2, eta: 1.5)
// float3(-2.7838821, 3.0, 0.0)

 

simd_bitselect

説明では以下のように書かれているがその結果にならず、0 またはプラスの値で x、マイナスの値で y を取る。
また、simd_select と同じ動きをするため違いがわからない。

ドキュメントの説明「結果の各ビットに対応するマスクのビットが 0 か 1 かによって、x または y の対応するビットを返す」

let f3_x = float3(2)
let f3_y = float3(-2)
let mask = simd_int3( 1, 1,-1)

simd_bitselect(f3_x, f3_y, mask)
// float3(2.0, 2.0, -2.0)

 

simd_select

説明では以下のように書かれているがその結果にならず、0 またはプラスの値で x、マイナスの値で y を取る。
また、simd_bitselect と同じ動きをするため違いがわからない。

ドキュメントの説明「結果の各レーンに対して、対応するマスクのレーンの上位ビットがそれぞれ 0 か 1 かに従って、x または y の対応する要素を返す」

let f3_x = float3(2)
let f3_y = float3(-2)
let mask = simd_int3(-1, 1, 1)

simd_select(f3_x, f3_y, mask)
// float3(-2.0, 2.0, 2.0)

 

smoothstep (simd_smoothstep)

edge0 から edge1 との値の間で 0 と 1 の間で3次補間を返す。

let f3 = simd_float3(0.1, 2.0, 4.0)

let f3_0 = simd_float3(0.0, 0.0, 0.0)
let f3_1 = simd_float3(4.0, 4.0, 4.0)

simd_smoothstep(f3, edge0: f3_0, edge1: f3_1)
// float3(0.00184375, 0.5, 1.0)

 

simd_step (step)

x < edge の場合は 0、それ以外の場合は 1 を返す。

let f3_edge = float3(3.0, 3.0, 3.0)
let f3_x    = float3(0.0, 3.0, 6.0)

simd_step(f3_edge, f3_x)
// float3(0.0, 1.0, 1.0)

 

trunc

成分(要素)に対して絶対値以下の最も近い整数値を返す。

let f3 = simd_float3(-2.1, 2.5, 5.7)

trunc(f3) // float3(-2.0, 2.0, 5.0)

 

まとめ

ARKit や SceneKit で使用する際にわりと簡単に計算できる関数が用意されていることがわかったと思われる。

次回は行列について