Apple Engine

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

iOS で SceneKit を試す(Swift 3) その32 - コードからカスタムジオメトリをつくってみる

コードからジオメトリをつくろうと思う。
ほぼ、自前でジオメトリを描画することはないと思われるので今回はさわりだけ。

 

テンプレートにあった宇宙船やビルトインのジオメトリ。
これらはオブジェクトファイルの読み込み、Scene Editor からのドラッグ&ドロップ、コードからクラスを呼び出しなどで手軽に行なっているが、
内部的には今回のような設定を元に描画されている。

 

概要

ジオメトリは外観を設定している情報があり、
その情報から SCNMaterial が光の情報やテクスチャと共にジオメトリの描画情報を作成している。

 

ジオメトリは外観を構成するものは以下のもの

  • ポリゴンを構成するために必要な点の情報(Vertex)
  • ポリゴン表示するための面を構成するために必要なインデックス情報
  • ライトを適用する際に必要な法線情報(Normal)
  • テクスチャを適応するために必要なテクスチャ座標情報(UV 座標)

 

流れ

SCNGeometrySource でポリゴンの情報、法線情報、テクスチャ情報を設定し、
SCNGeometryElement ポリゴンの面をどのように生成するか設定。
この2つを SCNGeometry に渡し、SCNNode の geometry で読み込みシーンに配置する。

 

三角形の平面を描画してみる

以下のコードを viewDidLoad() に設定

// シーン設定
let scnView = self.view as! SCNView
scnView.showsStatistics = true
scnView.allowsCameraControl = true
//scnView.debugOptions = .showWireframe // ワイヤーフレーム表示

let scene = SCNScene()
scene.background.contents = UIColor.darkGray
scnView.scene = scene

// カメラ設定
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(2.372,1.473,2.399)
cameraNode.eulerAngles = SCNVector3(-Float.pi * 0.125,Float.pi * 0.25,0)
scene.rootNode.addChildNode(cameraNode)

// ライト設定
let lightNode = SCNNode()
let omniLight = SCNLight()
omniLight.type = .omni
lightNode.light = omniLight
lightNode.position = SCNVector3(0,5,5)
scene.rootNode.addChildNode(lightNode)

// SCNGeometry
let verticesArray = [
    SCNVector3(0, 1, 0),
    SCNVector3(-0.866, -0.5, 0),
    SCNVector3(0.866, -0.5, 0)
]

let indicesArray: [Int32] = [
    0, 1, 2
]

let vertices = SCNGeometrySource(vertices: verticesArray, count: verticesArray.count)
let faces = SCNGeometryElement(indices: indicesArray, primitiveType: .triangles)
let geometry = SCNGeometry(sources: [vertices], elements: [faces])

// SCNNode
let node = SCNNode(geometry: geometry)
node.position = SCNVector3Zero
scene.rootNode.addChildNode(node)

 

verticesArray で反時計回りで三角形の頂点を設定し、 indicesArray で verticesArray に対応した点から面を貼る順序を決めている。

SCNGeometrySource で Vertex の設定をし、SCNGeometryElement で三角のポリゴンを貼る順序を設定。 その後、SCNGeometry でこの2つを設定し、SCNNode に渡している。

 

ビルドすると三角が表示される。

f:id:x67x6fx74x6f:20170713182048p:plain

今回、三角形で面を張っているが、点、線、OpenGL などでもあった Triangle Strip などを使用することもできる。

 

ライトを適応してみる

SCNGeometry の部分を変更

// SCNGeometry
let verticesArray = [
    SCNVector3(0, 1, 0),
    SCNVector3(-0.866, -0.5, 0),
    SCNVector3(0.866, -0.5, 0)
]

let indicesArray: [Int32] = [
    0, 1, 2
]

let normalsArray = [
    SCNVector3(0, 0, 1),
    SCNVector3(0, 0, 1),
    SCNVector3(0, 0, 1)
]

let vertices = SCNGeometrySource(vertices: verticesArray, count: verticesArray.count)
let normals = SCNGeometrySource(normals: normalsArray, count: normalsArray.count)
let faces = SCNGeometryElement(indices: indicesArray, primitiveType: .triangles)
let geometry = SCNGeometry(sources: [vertices, normals], elements: [faces])

 

normalsArray に法線情報を詰め込む。Z 方向を向いた平面なので Z 軸に値を入れている。 SCNGeometrySource で法線情報を格納し、SCNGeometry で設定している。

今回のサンプルでは微妙だが、斜め上にあるオムニライトが適応されている。

f:id:x67x6fx74x6f:20170713182115p:plain

 

テクスチャを適応してみる

何らかの画像を読み込んで、以下のコードに変更

一応、今回使用した画像。

f:id:x67x6fx74x6f:20170713182236p:plain

// SCNGeometry
let verticesArray = [
    SCNVector3(0, 1, 0),
    SCNVector3(-0.866, -0.5, 0),
    SCNVector3(0.866, -0.5, 0)
]

let indicesArray: [Int32] = [
    0, 1, 2
]

let normalsArray = [
    SCNVector3(0, 0, 1),
    SCNVector3(0, 0, 1),
    SCNVector3(0, 0, 1)
]

let texcoordsArray: [CGPoint] = [
    CGPoint(x:0.5, y:0),
    CGPoint(x:0, y:1),
    CGPoint(x:1, y:1),
]

let vertices = SCNGeometrySource(vertices: verticesArray, count: verticesArray.count)
let normals = SCNGeometrySource(normals: normalsArray, count: normalsArray.count)
let texcoords = SCNGeometrySource(textureCoordinates: texcoordsArray)
let faces = SCNGeometryElement(indices: indicesArray, primitiveType: .triangles)
let geometry = SCNGeometry(sources: [vertices, normals, texcoords], elements: [faces])

geometry.firstMaterial?.diffuse.contents = UIImage(named: "tex.png")
geometry.firstMaterial?.isDoubleSided = true

 

texcoordsArray でテクスチャの UV 座標を設定し、
SCNGeometrySource に適応させて、
SCNGeometry に適応。

いつも通り、firstMaterial でテクスチャを設定している。
DoubleSided を設定したので、光の当たらない背面は黒くなる。

f:id:x67x6fx74x6f:20170713182222p:plain

 

確認用にアクションを入れても良いかものしれない。

node.runAction(
    SCNAction.repeatForever(
        SCNAction.rotateBy(x: 0, y: CGFloat(Float.pi), z: 0, duration: 2)
    )
)

 

初歩的なものを書いたが、 全ての物体は三角形で表現できるため、これらを繰り返して三角形を描画していくと立体ができあがり、ビルトインやファイルから読み込むオブジェクトのようなものとなる。

 

余談1

今回 SCNGeometrySource には Vertex, 法線, テクスチャを設定している。 この他にも、ボーン、Vertex Color や Subdivision Surfaces 用の Crease の設定や、設定自体を Data で渡すことができる。

 

余談2

ジオメトリはボーンによるスキニングアニメーションや、他のオブジェクトの Vertex を参照して現在の Vertex を移動させるモーフアニメーション、シェーダーからジオメトリの Vertex を操作してアニメーションを行うことができる。

 

今回はここまで。