Apple Engine

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

2019年はじめに思う Apple におこなってもらいたいこと

なんとなく書いてみた。
あまり内容はない。

 

iPhone のカメラ性能をよくする

スマートフォンのアプリでもっとも長い時間使われているのは SNS アプリであり、 SNS のアプリで閲覧や文字を書く以外で時間が使われているのは写真(動画)撮影だろう。

絵を描いたり、楽器の演奏や作曲、歌唱などは長い期間の練習を経て習得する技術であるが、 カメラに関しては習得するべき技術が身体的なものではないので、機械で技能を拡張することができる。

そのため、カメラ性能を上げる戦略は正しく、 Hauwei の様にレンズやセンサーを工夫したり、Google Pixel 3 のように専用チップを開発して、カメラ機能にウエイトを置くのは当然だと思われる。

噂では次期 iPhone では3つ目のカメラを搭載するとのことだが、個人的にはカメラセンサーの向上による鮮明さと夜間で撮影した際のノイズ除去を頑張ってほしい。

 

Siri をより賢く

正直、AR や VR の世界より先に Siri 等のヴァーチャルアシスタントシステムの機能向上の方が人々の生活に役立つとは思っている。

現状、アメリカで使用できて日本で使用できない Siri の機能があるので、まずはそこから。

 

HomeKit 製品の拡充

生活の中心となる家電でとして Media Hub や Home Hub などの構想はあったが、いまだに実現されておらず、生活の中ではスマートフォンがその役割を奪ってしまった。

ただ、家電の操作をリモートで行うことができれば、便利さが上がり、生活はもっと楽なものになるだろう。
家電各社は色々な試みを行なっているが HomeKit に対応していただきたいところ。

HomeKit 対応する場合、Lightning ケーブルの様に専用のチップや契約が必要であるため、 厳しさはあるが、今年の CES ではテレビなど HomeKit 対応を謳ったものが増えてきた。

とりあえず、日本では2年間費用無償とか土下座でもして Panasonic あたりに Apple は HomeKit 対応をしてもらってほしい。
照明スイッチやエアコンなど Apple TV 経由で家に近づくと ON になるとか可能になるので。

HomeKit 対応製品が増え広まれば、Apple が人々の生活を掌握できる様になる。
Apple 製品が売れている国に関しては早急にホームオートメーションのシェアを掴んでいく必要性があると思われる。

 

AR や VR と仮想現実空間

AR に関しては Siri や HomeKit の機能強化の先の話だと思われるが、 AR グラスなど常時体験できるならアシスタントデバイスとして、製品ラインナップやプラットフォームの強化がされる。

今の ARKit ではできない遮蔽物の認識が可能になれば、2.0 で追加された空間共有とともに仮想空間での幅広い表現で可能になるだろう。

また、A12 / A12X Bionic での Metal 機能強化で macOS の GPU 機能と同等のもの(性能ではないので注意)が使える様になったため、 AR グラスや VR なども期待できそうではある。

 

Apple Watch の高機能版

Apple Watch Pro のような存在。
ある程度需要はある気はする。

現状、iPhone アプリからのデータ転送なので、プリインストールされている Apple 謹製アプリはセルラーで動く場合があるが、多くのアプリが iPhone なしではちゃんと動作しないので。

バッテリー持たなそうだけど。

 

インナーイヤー型の AirPods

現状の AirPods は、東京など人が密になっているところでは、外界の音がうるさすぎてボリュームを上げる必要があり面倒なので。
Beats X を使えばよいのだが、バッテリーの問題でインナーイヤー型の AirPods が出てくれればと思っている。

Apple の中の人は山手線とか鉄道の駅や電車で AirPods を使ってフィールドテストして欲しい。わりと辛い。

 

今後の macOS

多分、いつか iOS と macOS は同等のものになるとは思っている。
Apple A チップの機能がディスクトップに迫ったり、Metal 機能が同じ様になったりと歩み寄っており、iOS のアプリにメニューバーやタッチバーつけるぐらいの手軽さにはなってほしい。

問題があるとするなら OpenGL が廃止になり Metal でアプリの表示を描画することになる点だろう。

ただ Metal は WWDC 2014 で発表されており、もう 5 年目を迎えそうなので、もうそろそろ諦めて OpenGL から解脱してほしい。

 

まとめ

Apple はハードとソフトを上手く融合させてユーザー体験を提供するメーカーではあるが、来るべき PC/Mac などのコンピューターの終焉(専門職以外使わなくなる)とスマートフォンのコモディティ化を考えると、Siri や AR などを絡めたアシスタントや Apple Music などサービスの提供を強化していく流れになるだろう。

ARKit や SceneKit で使用している simd について - クォータニオンでの関数編 その2 -

今回はクォータニオンで使用する線形補間の関数について。
基本的には球状での回転の補間となる。

用意されている関数は以下のもの。

  • simd_slerp
  • simd_slerp_longest
  • simd_spline
  • simd_bezier

 

ちなみに線形補間に関しては iOS でサンプルアプリがあるのでそちらを参考に。

Rotating a Cube by Transforming Its Vertices | Apple Developer Documentation

 

ちゃんとした説明は WWDC 2018 でも行なっているのでそちらを参照。

developer.apple.com

 

以下、コードを書いていく。
macOS の Swift Playground で試していたので、iOS で使用する場合は NSColor を UIColor に変更するべし。

 

simd_slerp

クォータニオン q0 と q1 の間の最短の弧に沿った線形補間を行う。

f:id:x67x6fx74x6f:20190105032736p:plain
simd_slerp

let rotations: [simd_quatf] = [
    simd_quatf(angle: 0,
                axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.05,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.8,
                axis: simd_normalize(simd_float3(x: 1, y: 0, z: -1))),
    simd_quatf(angle: .pi * 0.15,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.2,
                axis: simd_normalize(simd_float3(x: -1, y: 0, z: 1)))
]

for i in 0 ... rotations.count - 2 {
    for t: Float in stride(from: 0, to: 1, by: 0.001) {
        let q = simd_slerp(
            rotations[i],
            rotations[i + 1],
            t
        )

        let node = SCNNode()
        let sphere = SCNNode(geometry: SCNSphere(radius: CGFloat(t/4)))
        sphere.geometry?.firstMaterial?.diffuse.contents = NSColor(red: 1.0, green: CGFloat(t), blue: CGFloat(t), alpha: 1.0)
        node.addChildNode(sphere)
        node.simdPosition = q.act(float3(0,0,5))
        scene.rootNode.addChildNode(node)
    }
}

 

simd_slerp_longest

クォータニオン q0 と q1 の間からもっとも遠い弧に沿った線形補間を行う。

f:id:x67x6fx74x6f:20190105032817p:plain
simd_slerp_longest (正面だとわかりづらいので斜めに始点移動)

let rotations: [simd_quatf] = [
    simd_quatf(angle: 0,
                axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.05,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.8,
                axis: simd_normalize(simd_float3(x: 1, y: 0, z: -1))),
    simd_quatf(angle: .pi * 0.15,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.2,
                axis: simd_normalize(simd_float3(x: -1, y: 0, z: 1)))
]

for i in 0 ... rotations.count - 2 {
    for t: Float in stride(from: 0, to: 1, by: 0.001) {
        let q = simd_slerp_longest(
            rotations[i],
            rotations[i + 1],
            t
        )

        let node = SCNNode()
        let sphere = SCNNode(geometry: SCNSphere(radius: CGFloat(t/4)))
        sphere.geometry?.firstMaterial?.diffuse.contents = NSColor(red: 1.0, green: CGFloat(t), blue: CGFloat(t), alpha: 1.0)
        node.addChildNode(sphere)
        node.simdPosition = q.act(float3(0,0,5))
        scene.rootNode.addChildNode(node)
    }
}

 

simd_spline

クォータニオン q1 と q2 の間を補間し、q0 は始点であり、q3 は終点。
回転するシーケンス間をスムーズに補間する場合は slerp ではなくこちらを使う。

f:id:x67x6fx74x6f:20190105032905p:plain
simd_spline

let rotations: [simd_quatf] = [
    simd_quatf(angle: 0,
               axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: 0,
               axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.05,
               axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.8,
               axis: simd_normalize(simd_float3(x: 1, y: 0, z: -1))),
    simd_quatf(angle: .pi * 0.15,
               axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.2,
               axis: simd_normalize(simd_float3(x: -1, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.2,
               axis: simd_normalize(simd_float3(x: -1, y: 0, z: 1)))
]

for i in 1 ... rotations.count - 3 {
    for t: Float in stride(from: 0, to: 1, by: 0.001) {
        let q = simd_spline(rotations[i - 1],
                            rotations[i],
                            rotations[i + 1],
                            rotations[i + 2],
                            t)

        let node = SCNNode()
        let sphere = SCNNode(geometry: SCNSphere(radius: CGFloat(t/4)))
        sphere.geometry?.firstMaterial?.diffuse.contents = NSColor(red: 1.0, green: CGFloat(t), blue: CGFloat(t), alpha: 1.0)
        node.addChildNode(sphere)
        node.simdPosition = q.act(float3(0,0,5))
        scene.rootNode.addChildNode(node)
    }
}

 

simd_bezier

De Casteljau アルゴリズムを使用しクォータニオン q0、q3 をコントロールポイントとして扱う3次ベジェ曲線の補間を行う。
補間の終点が q0 か q3 になり、曲線は q1 または q2 を通過しない。

標準的なベジェ曲線のように凸包のプロパティが球上には保持されないので注意。

f:id:x67x6fx74x6f:20190105032932p:plain
simd_bezier

let rotations: [simd_quatf] = [
    simd_quatf(angle: 0,
                axis: simd_normalize(simd_float3(x: 0, y: 0, z: 1))),
    simd_quatf(angle: .pi * 0.05,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0))),
    simd_quatf(angle: .pi * 0.8,
                axis: simd_normalize(simd_float3(x: 1, y: 0, z: -1))),
    simd_quatf(angle: .pi * 0.15,
                axis: simd_normalize(simd_float3(x: 0, y: 1, z: 0)))
]

for t: Float in stride(from: 0, to: 1, by: 0.001) {
    let q = simd_bezier(rotations[0],
                        rotations[1],
                        rotations[2],
                        rotations[3],
                        t)

    let node = SCNNode()
    let sphere = SCNNode(geometry: SCNSphere(radius: CGFloat(t/4)))
    sphere.geometry?.firstMaterial?.diffuse.contents = NSColor(red: 1.0, green: CGFloat(t), blue: CGFloat(t), alpha: 1.0)
    node.addChildNode(sphere)
    node.simdPosition = q.act(float3(0,0,5))
    scene.rootNode.addChildNode(node)
}

 

まとめ

クォータニオンでの線形補間を使用した回転は行列での回転より早く処理できる可能性があるため、状況によっては積極的に使用すべきだろう。

 

simd に関しては一通り説明が終わった。
ARKit のアンカーや SceneKit のノードで多くのものを素早く変更する場合に有用な機能ではある。

また、Float、Double など、型の値の取り出しが可能であるため、ARKit や SceneKit 以外にも行列の処理で使用できる状況はあると思われる。

ARKit や SceneKit で使用している simd について - クォータニオンでの関数編 その1 -

今回はクォータニオンで使用する関数について。
線形補間関連の関数はまたいずれ。

演算子と関数について書くが、基本的にはプロパティと関数が対になっている。

 

演算子

等価演算子と四則演算が可能。

+, - に関しては値が加算、減算されるため、正規化しないとスケールがおかしくなるので注意。

let ix:Float = 0.0
let iy:Float = 0.707107
let iz:Float = 0.0
let r:Float  = 0.707107

let q = simd_quatf(ix: ix, iy: iy, iz: iz, r: r)
let q1 = simd_quatf(ix: ix, iy: iy, iz: iz, r: r)

q == q1 // true

q + q // simd_quatf(real: 1.41421, imag: float3(0.0, 1.41421, 0.0))
q - q // simd_quatf(real: 0.0, imag: float3(0.0, 0.0, 0.0))
q * q // simd_quatf(real: 0.0, imag: float3(0.0, 1.0, 0.0))
q / q // simd_quatf(real: 1.0, imag: float3(0.0, 0.0, 0.0))

simd_add(q, q) // q + q と同じ
simd_sub(q, q) // q - q と同じ
simd_mul(q, q) // q * q と同じ

 

プロパティ / 関数

q の値を以下のものに設定し各設定を見てゆく。

// 逆時計回りに90度回転させる
let q = simd_quatf(angle: .pi / 2, axis: simd_normalize(float3(0.0, 1.0, 0.0)))

 

angle / simd_angle()

角度をラジアンで返す。

q.angle // 1.570796

simd_angle(q)

 

axis / simd_axis()

正規化した軸のベクトル返す。

q.axis // float3(0.0, 1.0, 0.0)

simd_axis(q)

 

conjugate / simd_conjugate()

対偶を返し虚数部がマイナスになる。

q.conjugate
// simd_quatf(real: 0.707107, imag: float3(-0.0, -0.707107, -0.0))

simd_conjugate(q)

 

imag / simd_imag()

虚数部を返す。

q.imag // float3(0.0, 0.707107, 0.0)

simd_imag(q)

 

real / simd_real()

実数部を返す。

q.real // 0.7071068

simd_real(q)

 

inverse / simd_inverse()

逆数を返す。

q.inverse
// simd_quatf(real: 0.707107, imag: float3(-0.0, -0.707107, -0.0))

simd_inverse(q)

 

length / simd_length()

大きさを返す。
多くの場合は単位クオータニオンなので 1 となる。

q.length // 1

simd_length(q)

 

normalized / simd_normalize()

正規化した値を返す。

q.normalized
// simd_quatf(real: 0.707107, imag: float3(0.0, 0.707107, 0.0))

simd_normalize(q)

 

vector

float4 でのベクトル値を返す。

q.vector
// float4(0.0, 0.707107, 0.0, 0.707107)

 

simd_negate()

全ての値の符号反転して返す。

simd_negate(q)
// simd_quatf(real: -0.707107, imag: float3(-0.0, -0.707107, -0.0))

 

simd_dot()

内積を返す。
例では 1 を返し同じ角度となる。

let q_90 = simd_quatf(angle: 90 * .pi / 180, axis: simd_normalize(float3(0.0, 1.0, 0.0)))

let a = simd_dot(q, q_90) // 1

 

act()

ベクトルの回転はアクションとして知られているが、ベクトルを渡し2点間を補間する。

q.act(float3(1,0,0))
// float3(1.78814e-07, 0.0, -1.0)

 

今後、補間を紹介すると思われるが、クォータニオンの利点の1つとして回転座標間の補間が簡単になることがある。

 

まとめ

クォータニオンの操作についてある程度理解できたと思われる。

時間があれば他の補間も紹介したいと思う。

ARKit や SceneKit で使用している simd について - クォータニオン編 -

今回はクォータニオンについて。

確かこの Blog で SCNQuaternion もちゃんと紹介していないので、まずはクォータニオンについてのご説明。

 

クォータニオンのざっくり説明

クォータニオンは四元数と呼ばれ、スカラー(実数)部分と3つの虚数部分をまとめたベクトルによって定義され、
3次元でオブジェクトの回転の方法としてグラフィックプログラミングでよく使用される。

最大の利点は、行列によるオイラー角の回転ではジンバルロックが起る可能性があり、クォータニオンでは起きないところ。

 

simd でのクォータニオンの型と使用方法

simd では float の simd_quatf、double の simd_quatd が用意されていて以下のように書くことができる。
simd_quatf や simd_quatd は実部や虚部を返す型となっている。

let ix:Float = 1.0
let iy:Float = 4.0
let iz:Float = 8.0
let r:Float  = 9.0

let q = simd_quatf(ix: ix, iy: iy, iz: iz, r: r)
// simd_quatf(real: 9.0, imag: float3(1.0, 4.0, 8.0))

 

SceneKit でクォータニオンを渡す場合は、単位クォータニオンとして設定する必要がある。
単位クォータニオンとは長さが1となるもので、以下の方法で計算できる。

sqrt(ix * ix + iy * iy + iz * iz + r * r)

ちなみに、SCNQuaternion の中身は SCNVector4 で、xx + yy + zz + ww == 1 となる必要がある。

 

クォータニオンは、通常の行列による回転よりいくつか利点があり、 float の 3×3 行列は 48 バイト使用するが、クォータニオンは 16 バイトになる。
また、クォータニオンでは1回の回転は計算が複雑であるため、行列を使用した場合よりも少し遅くなるが、アクションを組み合わせると四元数は最大 30% 速くなる。

以下のコードはクォータニオンの一般的な使用例。
axis で y 方向に回転する軸を設定して、angle からラジアンで π 分(180度)回転させている。

axis は normalize して正規化しないとジオメトリがぺったんこになるよ。

let axis = simd_float3(x: 0, y: 20, z: 0)

let q = simd_quatf(angle: .pi, axis: simd_normalize(axis))

// retrieve the ship node
let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!

ship.simdLocalRotate(by: q)

 

初期化

すでにいくつか紹介しているが設定できるものをまとめてみる。

let ix:Float = 0.0
let iy:Float = 2.0
let iz:Float = 0.0
let r:Float  = 7.54979e-08

let i = simd_normalize(simd_float3(x: ix, y: iy, z: iz))

let f4x4 = simd_float4x4([
    float4(-1.0, 0.0, -1.50996e-07, 0.0),
    float4(0.0, 1.0, 0.0, 0.0),
    float4(1.50996e-07, 0.0, -1.0, 0.0),
    float4(0.0, 0.0, 0.0, 1.0)
])

let f3x3 = simd_float3x3([
    float3(-1.0, 0.0, -1.50996e-07),
    float3(0.0, 1.0, 0.0),
    float3(1.50996e-07, 0.0, -1.0)
])

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

// -------------------
// 初期化設定
// -------------------

// 回転: 0 度
let q0 = simd_quatf()

// 回転: 180 度
let q1 = simd_quatf(vector: float4(0.0, 1.0, 0.0, r))
let q2 = simd_quatf(f4x4)
let q3 = simd_quatf(f3x3)
let q4 = simd_quatf(angle: .pi, axis: i)
let q5 = simd_quatf(ix: 0.0, iy: 1.0, iz: 0.0, r: r)
let q6 = simd_quatf(real: r, imag: i)

// 回転: 90 度 
// (原点を中心に2点間から角度を取りクォータニオンを設定する)
let q7 = simd_quatf(from:f3_1, to:f3_2)

 

まとめ

SCNQuaternion の場合は値を計算するのが面倒だが、 simd_quatf や simd_quatd の場合は初期化をわりと簡単に設定できるものが用意されているので使用する機会があるのではないかと思っている。

気力があれば、いつか simd のクォータニオンで使用できる関数の方も紹介する。

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

今回は行列で用意されている関数について

 

用意されている関数

以下のものが用意されている。

  • simd_add()
  • simd_sub()
  • simd_mul()
  • simd_equal()
  • simd_almost_equal_elements()
  • simd_almost_equal_elements_relative()
  • simd_linear_combination()
  • simd_determinant()
  • simd_inverse()
  • simd_transpose()

 

simd_determinant、simd_inverse、simd_transpose は行列で用意されているプロパティと同じなので割愛。

 

各関数の説明

simd_add()

2つの行列の成分(要素)を足したものを返す。

let f4_1 = simd_float4x4(1)
let f4_2 = simd_float4x4(
    float4(1, 0, 0, 0),
    float4(0, 1, 0, 0),
    float4(0, 0, 1, 0),
    float4(3, 2, 1, 1)
)

simd_add(f4_1, f4_2)
/*
simd_float4x4([
    [2.0, 0.0, 0.0, 0.0)],
    [0.0, 2.0, 0.0, 0.0)],
    [0.0, 0.0, 2.0, 0.0)],
    [3.0, 2.0, 1.0, 2.0)]
])
*/

 

simd_sub()

2つの行列の成分(要素)を引いたものを返す。

let f4_1 = simd_float4x4(2)
let f4_2 = simd_float4x4(
    float4(1, 0, 0, 0),
    float4(0, 1, 0, 0),
    float4(0, 0, 1, 0),
    float4(3, 2, 1, 1)
)

simd_add(f4_1, f4_2)
/*
simd_float4x4([
    [1.0, 0.0, 0.0, 0.0)],
    [0.0, 1.0, 0.0, 0.0)],
    [0.0, 0.0, 1.0, 0.0)],
    [-3.0, -2.0, -1.0, 1.0)]
])
*/

 

simd_mul()

1つの値(スカラー)と行列、 または2つの行列の成分(要素)をかけたものを返す。

let f4_1 = simd_float4x4(2)
let f4_2 = simd_float4x4(
    float4(1, 0, 0, 0),
    float4(0, 1, 0, 0),
    float4(0, 0, 1, 0),
    float4(3, 2, 1, 1)
)

simd_mul(4.0, f4_2)
/*
simd_float4x4([
    [4.0, 0.0, 0.0, 0.0)],
    [0.0, 4.0, 0.0, 0.0)],
    [0.0, 0.0, 4.0, 0.0)],
    [12.0, 8.0, 4.0, 4.0)]
])
*/

simd_mul(f4_1, f4_2)
/*
simd_float4x4([
    [2.0, 0.0, 0.0, 0.0)],
    [0.0, 2.0, 0.0, 0.0)],
    [0.0, 0.0, 2.0, 0.0)],
    [6.0, 4.0, 2.0, 2.0)]
])
*/

 

simd_equal()

2つの行列を比較して同じなら true を返す。

let f4_1 = simd_float4x4(
    float4(1, 0, 0, 0),
    float4(0, 1, 0, 0),
    float4(0, 0, 1, 0),
    float4(0, 0, 0, 1)
)

let f4_2 = simd_float4x4(
    float4(1, 0, 0, 0),
    float4(0, 1, 0, 0),
    float4(0, 0, 1, 0),
    float4(0, 0, 0, 1)
)

simd_equal(f4_1, f4_2) // true

 

simd_almost_equal_elements()

2つの行列を比較して、3番めの引数で許容範囲を設定、その範囲にあるの場合なら true を返す。

許容範囲の値はマイナスも許容するため、2.5 にした場合は -2.0 も許容する。

let f4_1 = simd_float4x4(
    float4(1, 0, 0, 0),
    float4(0, 1, 0, 0),
    float4(0, 0, 1, 0),
    float4(0, 0, 0, 1)
)

let f4_2 = simd_float4x4(
    float4(  1,    0,   0, 0),
    float4(  0,    1,   0, 0),
    float4(  0,    0,   1, 0),
    float4(1.2, -2.0, 2.5, 1)
)

simd_almost_equal_elements(f4_1, f4_2, 2.5)
// true

simd_almost_equal_elements(f4_1, f4_2, 2.0)
// false

 

simd_almost_equal_elements_relative()

2つの行列を比較して、3番めの引数で許容範囲を設定、その範囲にあるの場合なら true を返す。

行列1 - 行列2 の値を比較するためマイナス値が許容されない。
例えば、最初の行列1に 0 があり、行列2の数値がある場合はマイナスになるため false になるので注意。

let f4_1 = simd_float4x4(
    float4(1, 0, 0, 0),
    float4(0, 1, 0, 0),
    float4(0, 0, 1, 0),
    float4(1, 1, 1, 1)
)

let f4_2 = simd_float4x4(
    float4(  1,   0,   0, 0),
    float4(  0,   1,   0, 0),
    float4(  0,   0,   1, 0),
    float4(2.0, 1.5, 3.0, 1)
)

simd_almost_equal_elements_relative(f4_1, f4_2, 3.0)
// true

simd_almost_equal_elements_relative(f4_1, f4_2, 1.0)
// false

 

simd_linear_combination()

値(スカラー)と行列の行の2セットを引数として設定し、 値と行をかけたものを足して行に設定した線型結合を返す。

simd_linear_combination(a, x, b, y) -> float4x4 の中身は

    x.columns[0] = a * x.columns[0] + b * y.columns[0]
    x.columns[1] = a * x.columns[1] + b * y.columns[1]
    x.columns[2] = a * x.columns[2] + b * y.columns[2]
    x.columns[3] = a * x.columns[3] + b * y.columns[3]
    return x;

  

let f4_1 = simd_float4x4(1)
let f4_2 = simd_float4x4(
    float4(1, 0, 0, 0),
    float4(0, 1, 0, 0),
    float4(0, 0, 1, 0),
    float4(3, 2, 1, 1)
)

simd_linear_combination(2, f4_1, 4, f4_2)
/*
simd_float4x4([
    [6.0, 0.0, 0.0, 0.0)],
    [0.0, 6.0, 0.0, 0.0)],
    [0.0, 0.0, 6.0, 0.0)],
    [12.0, 8.0, 4.0, 6.0)]
])
*/

 

まとめ

行列の方は関数が少ないが、比較や線型結合はそれなりに使う可能性があるので簡単に使えるのはよいかと。

残りは新しく追加されたクォータニオンの型があるが、説明が面倒なのでちょいと先になるかも。

ARKit や SceneKit で使用している simd について - 行列型編 -

今回は 行列型と初期化について。
ARKit や SceneKit で主に使用する float4x4 を説明する。

double4x4 は float4x4 と上限値以外は同じなので 割愛。

float4x4 と double4x4、クオータニオン以外では、以下の行列が用意されており、double や float、行列の数が違うだけで扱いは同じ。

  • simd_float2x2
  • simd_float3x2
  • simd_float4x2
  • simd_float2x3
  • simd_float3x3
  • simd_float4x3
  • simd_float2x4
  • simd_float3x4
  • simd_double2x2
  • simd_double3x2
  • simd_double4x2
  • simd_double2x3
  • simd_double3x3
  • simd_double4x3
  • simd_double2x4
  • simd_double3x4

 

simd_float4x4 (float4x4)

4つの float4 型を持つ型(構造体)で、パラメーターは 4 x 4 なので、総数は16個となる。
以前あった matrix_float4x4 と同等のもの。

こちらは simd_float4x4 で型がつくられており、float4x4 は simd_float4x4 の Type Alias となっている。

 

初期化

対角成分に同じ値を入れる
var f4x4 = simd_float4x4(1)
/*
simd_float4x4([
    [1.0, 0.0, 0.0, 0.0)],
    [0.0, 1.0, 0.0, 0.0)],
    [0.0, 0.0, 1.0, 0.0)],
    [0.0, 0.0, 0.0, 1.0)]
])
*/
index x y z w
0 1 0 0 0
1 0 1 0 0
2 0 0 1 0
3 0 0 0 1

 

対角成分に設定した値を入れる
var f4x4 = simd_float4x4(diagonal: float4(4.0, 3.0, 2.0, 1.0) )
/*
simd_float4x4([
    [4.0, 0.0, 0.0, 0.0)],
    [0.0, 3.0, 0.0, 0.0)],
    [0.0, 0.0, 2.0, 0.0)],
    [0.0, 0.0, 0.0, 1.0)]
])
*/
index x y z w
0 4 0 0 0
1 0 3 0 0
2 0 0 2 0
3 0 0 0 1

 

float4 の配列か、float4 をそのまま渡し初期化する
var f4x4:simd_float4x4

f4x4 = simd_float4x4(
    float4(1.0, 0.0, 0.0, 0.0),
    float4(0.0, 1.0, 0.0, 0.0),
    float4(0.0, 0.0, 1.0, 0.0),
    float4(4.0, 3.0, 2.0, 1.0)
)

f4x4 = simd_float4x4([
    float4(1.0, 0.0, 0.0, 0.0),
    float4(0.0, 1.0, 0.0, 0.0),
    float4(0.0, 0.0, 1.0, 0.0),
    float4(4.0, 3.0, 2.0, 1.0)
])

/*
simd_float4x4([
    [1.0, 0.0, 0.0, 0.0)],
    [0.0, 1.0, 0.0, 0.0)],
    [0.0, 0.0, 1.0, 0.0)],
    [4.0, 3.0, 2.0, 1.0)]
])
*/
index x y z w
0 1 0 0 0
1 0 1 0 0
2 0 0 1 0
3 4 3 2 1

 

列ベクトルの flaot4 を行で初期化する
var f4x4 = simd_float4x4(rows: [
    float4(1.0, 0.0, 0.0, 0.0),
    float4(0.0, 1.0, 0.0, 0.0),
    float4(0.0, 0.0, 1.0, 0.0),
    float4(4.0, 3.0, 2.0, 1.0)
])

/*
simd_float4x4([
    [1.0, 0.0, 0.0, 4.0)],
    [0.0, 1.0, 0.0, 3.0)],
    [0.0, 0.0, 1.0, 2.0)],
    [0.0, 0.0, 0.0, 1.0)]
])
*/
index x y z w
0 1 0 0 4
1 0 1 0 3
2 0 0 1 2
3 0 0 0 1

 

呼び出し

値は通常の多重配列の様に呼び出すことができる。

var f4x4 = simd_float4x4(1)

f4x4[0]    // float4(1.0, 0.0, 0.0, 0.0)
f4x4[0][0] // 1.0

 

演算子

== や != での論理演算、、符号の変更、割り算を除いた四則演算が可能。 掛け算と片方が simd_flaot4x4 以外のものがあるが、左辺右辺が逆でも演算可能。

ただし、行列であるため左辺右辺を逆にすると演算結果が変わる場合があるので注意。

let f4x4 = simd_float4x4(1)
let f4x4_0 = simd_float4x4(1)

f4x4 == f4x4_0 // true

-f4x4
/*
simd_float4x4([
    [-1.0, 0.0, 0.0, 0.0)],
    [0.0, -1.0, 0.0, 0.0)],
    [0.0, 0.0, -1.0, 0.0)],
    [0.0, 0.0, 0.0, -1.0)]
])
*/

var f4x4_1 = simd_float4x4(2)
var f4x4_2 = simd_float4x4(
    float4(1.0, 0.0, 0.0, 0.0),
    float4(0.0, 1.0, 0.0, 0.0),
    float4(0.0, 0.0, 1.0, 0.0),
    float4(4.0, 3.0, 2.0, 1.0)
)

f4x4_1 + f4x4_2
f4x4_1 += f4x4_2
/*
simd_float4x4([
    [3.0, 0.0, 0.0, 0.0)],
    [0.0, 3.0, 0.0, 0.0)],
    [0.0, 0.0, 3.0, 0.0)],
    [4.0, 3.0, 2.0, 3.0)]
])
*/

f4x4_1 - f4x4_2
f4x4_1 -= f4x4_2
/*
simd_float4x4([
    [ 1.0,  0.0,  0.0,  0.0)],
    [ 0.0,  1.0,  0.0,  0.0)],
    [ 0.0,  0.0,  1.0,  0.0)],
    [-4.0, -3.0, -2.0,  1.0)]
])
*/

f4x4_1 * f4x4_2
f4x4_1 *= f4x4_2
/*
simd_float4x4([
    [2.0, 0.0, 0.0, 0.0)],
    [0.0, 2.0, 0.0, 0.0)],
    [0.0, 0.0, 2.0, 0.0)],
    [8.0, 6.0, 4.0, 2.0)]
])
*/

f4x4_2 * 2.0
/*
simd_float4x4([
    [2.0, 0.0, 0.0, 0.0)],
    [0.0, 2.0, 0.0, 0.0)],
    [0.0, 0.0, 2.0, 0.0)],
    [8.0, 6.0, 4.0, 2.0)]
])
*/

f4x4_2 * float4(2.0, 2.0, 2.0, 2.0)
// float4(10.0, 8.0, 6.0, 2.0)


var f4x4_3 = simd_float4x4(
    float4(1.0, 2.0, 3.0, 0.0),
    float4(2.0, 1.0, 2.0, 0.0),
    float4(3.0, 2.0, 1.0, 0.0),
    float4(4.0, 3.0, 2.0, 1.0)
)

f4x4_3 * float2x4(2)
/*
simd_float2x4([
    [2.0, 4.0, 6.0, 0.0)],
    [4.0, 2.0, 4.0, 0.0)]
])
*/

f4x4_3 * float3x4(2)
/*
simd_float3x4([
    [2.0, 4.0, 6.0, 0.0)],
    [4.0, 2.0, 4.0, 0.0)],
    [6.0, 4.0, 2.0, 0.0)]
])
*/

 

その他

転置行列、逆行列、行列式の結果を返すプロパティがある。

var f4x4 = simd_float4x4(
    float4(1.0, 0.0, 0.0, 0.0),
    float4(0.0, 1.0, 0.0, 0.0),
    float4(0.0, 0.0, 1.0, 0.0),
    float4(4.0, 3.0, 2.0, 1.0)
)

f4x4.tranpose
/*
simd_float4x4([
    [1.0, 0.0, 0.0, 4.0)],
    [0.0, 1.0, 0.0, 3.0)],
    [0.0, 0.0, 1.0, 2.0)],
    [0.0, 0.0, 0.0, 1.0)]
])
*/

f4x4.inverse
/*
simd_float4x4([
    [0.99999994, 0.0, 0.0, 0.0)],
    [0.0, 0.99999994, 0.0, 0.0)],
    [0.0, 0.0, 0.99999994, 0.0)],
    [-3.9999998, -2.9999998, -1.9999999, 0.99999994)]
])
*/

f4x4.determinant // 1.0

 

columns を使うと行をタプルで呼び出しができる。

f4x4.columns
/*
[
    float4(1.0, 0.0, 0.0, 0.0),
    float4(0.0, 1.0, 0.0, 0.0),
    float4(0.0, 0.0, 1.0, 0.0),
    float4(4.0, 3.0, 2.0, 1.0)
]
*/

f4x4.columns.3
// float4(4.0, 3.0, 2.0, 1.0)

 

まとめ

float4x4 も Float の値を入れ初期化し、簡単に行列を計算できることがわかったと思われる。

次回は行列で使用できる関数について。

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 で使用する際にわりと簡単に計算できる関数が用意されていることがわかったと思われる。

次回は行列について