Xcodeではマテリアルを同じような値で指定しても、Blenderの時ほど金属ぽくない。
なんか、イマイチ
なぜなのか?
それはXcodeの方には金属に映り込む風景が存在しないから。
テカテカする金属の表面を見るてっのは、金属の表面そのものを見てるわけじゃなく、金属の表面に映り込む周りの風景を見てるってことに他ならない。
で、見る角度によって映り込む風景がチラチラと変わる。明るい風景を反射したら明るくなるし、暗い風景を反射したら暗くなる、この陰陽の変化を脳がとらえて金属っぽいって感じるわけです。
なんだけど、Xcodeではその映り込むべき風景が何もない暗黒なんですよ。
真っ暗闇の中にポツンと光が灯ってる状態。なので金属表面が陰陽の変化の少ないのっぺりした描写になる。で、なんか金属っぽくないなと…
え、でも、それはBlenderでも同じなんじゃ…と思った人、残念、Blenderでは事前に風景を用意済みなんですな。
Blenderの3Dビューのボトムバーにあるシェーディングってメニューをクリックするとわかります。
実は設定済みというね
ちなみに、円で表示されてる風景をクリックしたら別の風景も選べます。
この画像の円形での表示は、球体の内側に貼り付けられてる画像を見てる状態を表現してまして、実際は視線の後ろ側にも画像が広がってます。
なので、視線をどこに向けようが、360度全周に画像が存在するってわけです。
そのため、割り当てる画像は、球体の裏側360度全周に貼り付けられることを前提にしたものが必要で、縦横比が1:2の横長画像のメルカトル図法っぽい画像が要求されます。Blenderではそうでない画像を指定してもいいんだけど、その場合、360度全周に貼り付けた時にどこかしら破綻する。
注意)メルカトル図法なのかどうかは知らんけど
具体的に言えばリコーのシータで撮った画像ね。
こんな感じね。他人が写ってるんでぼかしたけど
![]() |
RICOH デジタルカメラ RICOH THETA 全天球 360°カメラ 0175760
12,990円
Amazon |
もしくはAppStoreにあるGoogle ストリートビューみたいなアプリで作成した画像ね。パノラマ撮影の全周版って感じで撮るのめんどくさいけど、iPhone使って無料で上記の全天球用の写真が撮れます。
で、Xcodeでは事前に設定はしてないものの、自分で設定することは可能っす。
上の写真をダウンロードしてSecneKit Catalogフォルダに入れちゃってください。サンプルだとSceneKit Asset Catalog.scnassetsってフォルダね。
わからん人は「見た目が大事」を読み返すべし。
フォルダに置くと、勝手にXcodeのナビゲーションエリアにファイル名が表示されます。
そこまでできたら、今度はナビゲーションエリアでCOLLADAファイル選んで、右端のShow the scene inspectorタブを選んで、Environmentメニュークリックして、フォルダに入れた画像ファイルを選択する。
これで金属っぽくなる。
グレート。
で、ここまで説明しておいて、またまた残念なお話なんだけど、そもそもCOLLADAファイルをXcode上で、前回やったようにマテリアルのShadingをPhygically basedに変更してもARKit(SceneKit)が読み取ってくれません。
なんか、もおおおおお、ね、色だけしか読み取ってくれない。
もう、素直にCOLLADAファイルをSCNファイルに変換してから修正するのがいいみたいっす。ま、そこらへんは「お気楽極楽Blender[4] ダンスダンスダンス後編」で説明してるので各自でやってください。
その場合、例えばyumbo.daeをyumbo copy.scnに変換した場合はExcavator.swiftのinit()でやってる
Excavator.swift
class Excavator { ・・・ init() { ・・・ guard let yumboURL = Bundle.main.url( forResource: "SceneKit Asset Catalog.scnassets/yumbo", withExtension: "dae") else { return } ・・・
てのを
class Excavator { ・・・ init() { ・・・ guard let yumboURL = Bundle.main.url( forResource: "SceneKit Asset Catalog.scnassets/yumbo copy", withExtension: "scn") else { return } ・・・
に変えるように。当たり前。
COLLADAファイルそのまま使いたい人はプログラムで変更しましょう。
サンプル:
http://tetera.jp/xcc/book-sample/yumbo-wktk.zip
Excavator.swift
class Excavator { ・・・ init() { ・・・ shoulder.firstMaterial?.diffuse.contents = UIColor.yellow guard let bucketNode = boomNode.childNode( withName: Excavator.bucketID, recursively: true) else { return } bucketNode.changeToPhysicallyBased(name:"red") boomNode.changeToPhysicallyBased(name:"body") root = baseNode } ・・・ extension SCNNode { func changeToPhysicallyBased(name:String) { let i = self.geometry?.materials.firstIndex( where: { (m:SCNMaterial) -> Bool in return m.name == name }) if let i = i, let m = self.geometry?.materials[i] { m.lightingModel = .physicallyBased m.metalness.contents = NSNumber(value: 1.0) m.roughness.contents = NSNumber(value: 0.0) } } }
前回、boomNode以外はオブジェクトとして取り出さなくてもいいようにしたんだけど、結局必要になってしまった…
bucketNodeはboomNodeの子SCNNodeになってるんで、boomNodeからchildNodeで探し出してる。で、そのbucketNodeとboomNodeのgeometryのmaterialsに設定されてる"red"と"body"って名前のマテリアルを、物理ベースの設定に変更してます。前回Scene Editorで変更したとこね。
変更にはSCNNodeクラスにextensionでchangeToPhysicallyBased(name:)てメソッドを追加して利用するようにしてます。extensionがわからん人は「リファクタリング」ね。
geometryのmaterialsから、指定の名前のSCNMaterialを探し出すには、Array構造体が持つfirstIndexメソッドを利用してる。
firstIndex(where:)
こいつはwhere:引数の後のクロージャに、materials配列内の各SCNMaterial要素が1つずつ
(引数名:SCNMaterial)-> Bool
という形で渡され、探してるSCNMaterialならtrue、そうでないならfalseを返すという決まりになってるんで、引数であるSCNMaterialに適当な引数名(サンプルではmにした)与えてnameプロパティとnameの比較結果を返すようにしてます。
{ (m:SCNMaterial)-> Bool in return m.name == name }
配列がわからん人は「色々と脱線」、クロージャがわからん人は「タップしてドン」ね。ただクロージャに関しては任意の処理を{・・・}で書けるとしか説明してないんで、ここでもうちょっと補足すると、必要なら、クロージャ呼び出し側がクロージャに引数を渡せるようにもなっていて、戻り値をもらえるようになっている。その場合{・・・}の初頭に
{ (引数名:引数の型,...) -> 戻り値の型 in }
と書くことで、呼び出し側からの引数を受け取り
return 値
で、呼び出し側に戻り値を返せるようになる。
それが、上のサンプルでやってること。
ここら辺はfirstIndexメソッド側が決めることで、使う側は、それに従ったクロージャを用意しなければいけないわけっすね。
クイックヘルプで調べれば、どんな風にクロージャが呼び出されるかわかるようになってます。
クイックヘルプがわからん人は「または私は如何にして心配するのを止めて…」ね。
なのでfirstIndexのwhere:で渡してるクロージャ
{ (m:SCNMaterial) -> Bool in return m.name == name }
てのは、materials側から、自分の要素SCNMaterialを1つ1つ取り出して、こいつが探してるSCNMaterialですかって聞かれることに対する処理になるわけですよ。
その処理がm.name == nameならtrueを返すって処理なわけで、これでnameに一致するSCNMaterialをmaterials側から取り出せることになる。
firstIndexて名前からわかると思うけど、配列内に該当する要素が複数ある場合、最初に見つかった要素のインディックスになります。
で、見つかったSCNMaterialのlightingModelプロパティに物理ベースのパラメータを指定するよって指示して、関係するmetalness、roughnessプロパティを設定してる。
lightingModel
「光あれ」で説明したシェーディング時の演算に、物理ベースを指定するには、このプロパティにSCNMaterial
他にどんなのがあるか興味ある人はクイックヘルプで調べましょう。
で、物理ベースで使われるSCNMaterialのプロパティはmetalness、roughnessで、こいつは0.0〜1.0の数値を指定することになってる。
指定するには、metalness、roughnessのcontentsプロパティにNSNumberで設定します。
NSNumber
数値を表現するクラスです。contentsプロパティは何らかのオブジェクトでないと指定できないので、float型の値を直接は設定できないんすよ。で、その代用としてNSNumberオブジェクトを使います。NSNumberのイニシャライザのvalue: 引数の後ろに設定したい値を書く。
m.metalness.contents = NSNumber(value: 1.0)
これでマテリアル側の設定は完了。
で、.physicallyBasedを指定した場合、重要なのが映り込む風景の指定。
COLLADAじゃなくSCNファイル使う場合でも、読み込んだSCNSceneをそのままARSCNViewのsceneとして使わないなら、映り込む周辺の画像は自前で設定する必要があります。サンプルではブーム、アーム、バケットの3形状を読み込んで使ってるだけなんでね。
何言ってるのか、わからん人は「仮想体を置いてみる」、「見た目が大事」を読みなおしましょう。
とにかく、プログラムで風景画像を指定する場合はSCNSceneのlightingEnvironmentプロパティにUIImageとして設定するのが基本っす。
lightingEnvironment
こいつにはSCNMaterial
ViewController.swift
class ViewController: UIViewController {
・・・
override func viewDidLoad() {
・・・
yumbo.root.position = SCNVector3(x:0, y:-1, z:-3)
scene.rootNode.addChildNode(yumbo.root)
scene.lightingEnvironment.contents = UIImage(
named: "SceneKit Asset Catalog.scnassets/environment.JPG")
}
で、まあ、これで十分テカテカなんだけど、クイックヘルプの.physicallyBasedの説明では.physicallyBased使うなら、SCNCameraのwantsHDRプロパティをtrueにしろとか書かれてるんで、やってみました。
wantHDR
HDRてのはHigh Dynamic Range(ハイダイナミックレンジ:豊かな階調)のイニシャルです。例えば黒にも様々な黒があるわけで、漆黒から淡い黒まで、きめ細やかに表現する場合に指定します。
wantsHDRプロパティをtrueにすることで、HDR希望ってことになるのかな。このプロパティを持ってるオブジェクトが
SCNCamera
こいつは仮想体撮影用の仮想カメラっす。
でもって画面描画に使用されるSCNCameraは、ARSCNView(その派生元であるSCNView)のpointOfViewプロパティのcameraプロパティからゲットできます。
pointOfView
このプロパティには視点となるSCNNodeが指定されるっぽい。
例えばバケットSCNNodeのcameraプロパティにSCNCameraを設定しておいて、このpointOfViewプロパティにバケットSCNNodeを設定すると、バケットから見た風景が表示されるんじゃないかと…
ゲームで言うところの本人視点シューティングゲームぽく。
ま、それは置いといて、とにかくwantsHDRプロパティを設定。
ViewController.swift
class ViewController: UIViewController {
・・・
override func viewDidLoad() {
・・・
scene.lightingEnvironment.contents = UIImage(
named: "SceneKit Asset Catalog.scnassets/environment.JPG")
if let camera = sceneView.pointOfView?.camera {
camera.wantsHDR = true
}
}
うーん、変わらんような…
よくわかりません。とりあえずサンプルはRunするとこんな感じになる。
ちなみに、このダイアモンドカット的なエッジの効いたバケットもいいけど、曲線部は滑らかにしたいよねえな人は、Blenderで、bucketオブジェクト選んだ状態で、3Dビューのボトムバーからオブジェクト>スムーズ面メニューを選びましょう。
注意)もちろんXcode側で設定する方法もあるが、それはいずれ
スムーズ面
スムーズ面指定するとこうなる。
元に戻したい場合は、オブジェクト>フラット面メニューを選びましょう。
これって、シェーディング時に法線をどう扱うかって話なんだけど、ARKitでもgeometryオブジェクト単位では対応してくれるみたいっす。
法線によって、周辺の風景のどの部分が反射して見えるかを計算してる
法線、なんすかそれな人は「光あれ」を読み返しましょう。
Blenderオンリーだと、もうちょっと小技が指定できて、プロパティビューのオブジェクトタブに、面と面が指定した角度以下の場合はスムーズ面にしないって指定もあったりする。
これだと側面は平坦になる。
ただしARKitは認識してくれませんでした。
注意)もっと細かいので、編集モードでスムース面化させたい面だけ選んでおいて、面>スムーズ面を選ぶってのもあるんだけど、もちろんARKitは未対応
で、法線もそうだけど、色の分布や粗さ具合の分布を平面単位じゃなく、平面に対応する2Dの分布図として設定するには、マテリアルタブの下にあるテクスチャタブを使います。
MMDでは使ってるし、ARKitでも表示されたんで、ある程度対応できてるぽいけど、どこまで対応かは不明。
ちなみにMMDのテクスチャ設定はマテリアルタブの下の方にあったりします。
テクスチャタブの下の物理演算タブなんかも、フォースフィールドやコリジョン、剛体なんかあって面白いんだけど、COLLADAファイルでどこまで引き継げるか、ARKitでどこまで対応なのか不明。というか対応してないっぽい。
というわけで、Blenderの話は一旦終了。
いよいよ、Xcodeで「ダンスダンスダンス後編」で軽く触れた、リジッドボディや物理計算に突入します。
突入せよ!あさま山荘。