LoginSignup
10
6

More than 5 years have passed since last update.

swift + metalで3D描画

Last updated at Posted at 2017-11-17

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

swift + metalで3D描画する

はじめに

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

最初に描画するまでが大変だったので、objファイルを読み込んで描画するまでまとめて公開してみました。
これからmetalを使ってみようと考えている方の参考になれば嬉しいです。
ソースコードはこちら↓
https://github.com/fuziki/metalTest
metaltest.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

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6