GLKMatrix4MakeOrtho の使い方を勘違いしていた件


## Normalized Device Coordinate
OpenGL などでは、最終的なデバイスの上での表示座標は NDC（Normalized Device Coordinate）で X座標、Y座標も`-1.0`〜`1.0`の範囲で表現できます。実際の表示エリアが、縦長か横長かに関係なく、`-1.0`〜`1.0`で、中心が `0.0` となります。
![3.png](https://qiita-image-store.s3.amazonaws.com/0/65634/d7640e77-a388-6cd4-fb8c-af544fc1bd0a.png)

さすがに、アプリがこのNDCを意識してコーディングするのは不便なので、3Dでは `GLKMatrix4MakePerspective` など、2D なら `GLKMatrix4MakeOrtho` を利用して、4x4 の Matrix（行列） を求め、この Matrix を 座標 の Vector と乗算して座標変換すれば、View の座標を NDC に変換する事ができます。

> NDCの座標 = その行列(4x4) * Viewの座標(x,y,z,w)

## 行列の演算

Swift を使っているなら、行列の演算は * オペレータを使って実装したいですね。4x4 の行列同士の乗算の結果は 4x4 の行列になりますし、4x4 と 1x4 の行列の乗算の結果は 1x4 の行列になります。

```swift
func * (l: GLKMatrix4, r: GLKMatrix4) -> GLKMatrix4 {
	return GLKMatrix4Multiply(l, r)
}

func * (l: GLKMatrix4, r: GLKVector4) -> GLKVector4 {
	return GLKMatrix4MultiplyVector4(l, r)
}
```

## GLKMatrix4MakeOrtho
`GLKMatrix4MakeOrtho` では View の上下左右を指定します。ドキュメントでは以下のような記述になっています。

```
func GLKMatrix4MakeOrtho(left: Float, _ right: Float, _ bottom: Float, _ top: Float, _ nearZ: Float, _ farZ: Float) -> GLKMatrix4
```

|Parameters | description |
|:---|:----------|
|left |	The left coordinate of the projection volume in eye coordinates.|
|right | The right coordinate of the projection volume in eye coordinates.|
|bottom	| The bottom coordinate of the projection volume in eye coordinates.|
|top | The top coordinate of the projection volume in eye coordinates.|
|nearZ | The near coordinate of the projection volume in eye coordinates.|
|farZ | The far coordinate of the projection volume in eye coordinates.|

よって、`nearZ` と `farZ` をちょっと置いておいて、例えば画面のサイズが 1024x768 の場合、左下を原点にした場合と中心を原点にした場合のそれぞれをコードと図で表現すると以下のようになります。

### 1. 左下を原点にした場合

```swift
let matrix = GLKMatrix4MakeOrtho(0, 1024, 768, 0, -1, 1)
```
![1.png](https://qiita-image-store.s3.amazonaws.com/0/65634/7178706e-f157-46e1-3024-8b4a92d3329e.png)

### 2. 中心を原点にした場合

```swift
let matrix = GLKMatrix4MakeOrtho(-512, 512, -384, 384, -1, 1)
```
![2.png](https://qiita-image-store.s3.amazonaws.com/0/65634/b5dfb8b0-09db-cf47-0ace-697d4ad2277b.png)



## 勘違い
私はこれで、View の座標を NDC座標系に変換できると思っていたので、先の図の説明に使った座標系の座標と `GLKMatrix4MakeOrtho` の行列を演算すれば、簡単に座標の変換ができると思い込んでいました。が、

実は、二つのどちらの行列も原点 `(0, 0)` は View の中心に変換され、どちらにしようと結果が同じ結果が得られるという残念な結果に気がつきました。もっと早く気がついていれば楽になっていたのですがね。「右」とか「左」とか指定するならそう思っちゃうじゃないですかぁ。

```swift
let t1 = GLKMatrix4MakeOrtho(-512, 512, -384, 384, -1, 1)
let t2 = GLKMatrix4MakeOrtho(0, 1024, 768, 0, -1, 1)

// 座標 (-512, -384) → (-1, -1)
t1 * GLKVector4Make(-512, -384, 0, 0) // "[-1.0, -1.0, -0.0, 0.0 ]"
t2 * GLKVector4Make(-512, -384, 0, 0) // "[-1.0, -1.0, -0.0, 0.0 ]"

// 座標 (0, 0) → (0, 0)
t1 * GLKVector4Make(0, 0, 0, 0) // "[0.0, 0.0, 0.0, 0.0 ]"
t2 * GLKVector4Make(0, 0, 0, 0) // "[0.0, 0.0, 0.0, 0.0 ]"

// 座標 (512, 384) → (1, 1)
t1 * GLKVector4Make(512, 384, 0, 0) // "[1.0, 1.0, 0.0, 0.0 ]"
t2 * GLKVector4Make(512, 384, 0, 0) // "[1.0, 1.0, 0.0, 0.0 ]"
```

## 上下反転させたい場合
さて、最後に、`GLKMatrix4MakeOrtho` で `top` と `bottom` を入れ替えると（`0` と `768`の順番に注意）、上下を反転させて変換できるようになります。

```swift
let t3 = GLKMatrix4MakeOrtho(0, 1024, 768, 0, -1, 1)
t3 * GLKVector4Make(-512, -384, 0, 0) // "[-1.0, 1.0, -0.0, 0.0 ]"
t3 * GLKVector4Make(0, 0, 0, 0) // "[0.0, 0.0, 0.0, 0.0 ]"
t3 * GLKVector4Make(512, 384, 0, 0) // "[1.0, -1.0, 0.0, 0.0 ]"
```

## 全体像はまだまだ
ここでは、GLKMatrix4MakeOrtho のみを扱ったが、 Model座標→ワールド座標など、全体を見れば一部の説明に過ぎないので注意されたい。

## ハードルはやはり高い
GPU を使うプログラミング は門外漢（ES 1.1はかじった事があるけど）なので、単純に Core Graphics を使う場合などに比べて、何かとハードルが高い。間違いや勘違いの指摘など気がついたら教えていただければ幸いです。