swift + metalで3D描画

更新履歴
2017年11月２０日ソースコードへの参照を追加

#swift + metalで3D描画する

##はじめに
swift + metalで3Dアクションゲームを作りました。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">「メガネニテンセイ」略して「メガテン」という、メガネを操作して戦うゲーム作りました！<br>無料で容量も20MBくらいなんで、一度遊んでみてください！<br>お願いします\(´･∀･` )/<a href="https://t.co/Re6VZHj4lZ">https://t.co/Re6VZHj4lZ</a> <a href="https://t.co/4Tycuy6p3t">pic.twitter.com/4Tycuy6p3t</a></p>&mdash; 藤城@眼鏡転生公開 (@fuziki333) <a href="https://twitter.com/fuziki333/status/930606442603618304?ref_src=twsrc%5Etfw">2017年11月15日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

最初に描画するまでが大変だったので、objファイルを読み込んで描画するまでまとめて公開してみました。
これからmetalを使ってみようと考えている方の参考になれば嬉しいです。
ソースコードはこちら↓
https://github.com/fuziki/metalTest
![metaltest.png](https://qiita-image-store.s3.amazonaws.com/0/214313/55f9fea4-b9c7-9207-8e2e-cca90ab85289.png)

##MetalEzとWord
主にMetalEzがMetalのAPIの呼び出し、Worldがカメラや戦闘機の管理をしています。

###MetalEzクラス
MetalEzは描画開始前にMetalEzClassDelegatのupdateを呼び出します。
次に、pipelinestateを順番にセットして、MetalEzClassDelegatのdrawを呼び出します。
どのパイプラインが設定されているかは、RendererTypeによって識別します。
ViewController内でオブジェクトを生成し、MetalEzClassDelegateを自身が受け取るように設定します。

```GameViewController.swift
        mtlEz = MetalEz()
        mtlEz.delegate = self
        mtlEz.setupMetal(mtkView: self.view as! MTKView!)
```

```MetalEz.swift
protocol MetalEzClassDelegate {
    func update()
    func draw(type: MetalEzRenderingEngine.RendererType)
}
```
公開したコードには、3Dオブジェクトを描画するパイプラインが2種類存在します。
1つは影あり（左の立方体）、もう1つは影なし（右の戦闘機）です。
それぞれ、RendererTypeはmeshとmesh_nonlightingです。

```MetalEz.swift
enum RendererType: Int {
        case mesh
        case mesh_add
        case mesh_nonlighting
    }
```


###Worldクラス
描画するオブジェクトや、カメラの管理をします。

####Cameraクラス
カメラの位置と方向、上の向きを設定します。
MetalEzクラスのlookAtメソッドを用いて設定します。

```World.swift
class Camera {
    weak var world: World!
    private var rot: Float = 0
    private var sgn: Float = 0.01
    init(world myworld: World) {
        world = myworld
    }
    func update() {
        rot += sgn
        if abs(rot) > 0.8 { sgn *= -1 }
        world.mtlEz.lookAt(from: float3(0, 1, -1), direction: normalize(float3(sin(rot), -0.5, 1)), up: float3(0, 1, 0))
    }
}
```

####MeshDrawer
戦闘機と立方体はMeshDrawerを用いて描画しています。
updateが呼び出された時に、モデル変換行列をセットします。
drawメソッドを実行すると描画されます。

```MeshDrawer.swift
extension MetalEz {
    class MeshDrawer {
        private var mesh: MTKMesh
        private var texture: MTLTexture
        private var frameUniformBuffer: MTLBuffer
        private weak var mtlEz: MetalEz!
        var hidden: Bool = false
        init(mtlEz m: MetalEz ,mesh v: MTKMesh, texture t: MTLTexture)         
        func set(modelMatrix: matrix_float4x4)
        func draw() 
    }
}
```

```Realship.swift
class Realship {
    weak var world: World!
    private var drawer: MetalEz.MeshDrawer
    private var rot: Float = 0.0
    init(world myworld: World) {
        world = myworld
        let tex = world.mtlEz.loader.loadTexture(name: "shipDiffuse", type: "png")
        let mesh = world.mtlEz.loader.loadMesh(name: "realship")
        drawer = MetalEz.MeshDrawer(mtlEz: world.mtlEz ,mesh: mesh, texture: tex)
    }
    func update() {
        rot += 1
        var mat = matrix_identity_float4x4
        mat = matrix_multiply(mat, Utils.translation(float3(2, -1, 3)))
        mat = matrix_multiply(mat, Utils.rotation_y(radians: toRad(fromDeg: rot)))
        drawer.set(modelMatrix: mat)
    }
    func draw(type: MetalEzRenderingEngine.RendererType) {
        if type == .mesh_nonlighting {
            drawer.draw()
        }
    }
}
```

##まとめ
単純にAPIを呼び出しているだけなので、あまり書くことがありませんでした(;´･ω･)
ダメダメなコードですが、どなたかの役に立ったと信じてます(笑)
実際にリリースしたゲームでも同じ仕組みを使っています。
「メガネニテンセイ」というメガネに転生して戦うアクションゲームです。
無料で容量も20MB程度なので、swift + metalでゲームを作るとどんな感じになるのか、気になる場合は、一度ダウンロードしてみてくださいm(_ _)m
すぐに消していいのでお願いします！←
https://itunes.apple.com/jp/app/%E3%83%A1%E3%82%AC%E3%83%8D%E3%83%8B%E3%83%86%E3%83%B3%E3%82%BB%E3%82%A4/id1312709625?mt=8