LoginSignup
0
0

objective-cで3DオブジェクトをUSDZとして(強引に)出力する

Posted at

objective-c(swiftでも可)で3DモデルをUSDZとして出力する方法を模索した過程を、備忘録も兼ねてまとめました。

MDLAssetクラスからUSDZを出力する

最初に見つけたのはこの記事

記事によると、MDLAssetクラスのexportメソッドを使ってOBJファイルをUSDZに変換できるとのこと。MDLAssetクラスはUSDZを直接出力できないので、USDCとして出力してからUSDZに拡張子を変更しているようです。

拡張子を変えるだけで何とかなるのか?と思いつつも実践。その結果...

ファイルが正常に開けませんでした。

しかし、USDCとしては出力できていたのでそちらも確認してみました。すると...

モデルはあるがテクスチャが貼られていない!

テクスチャがないということは、ファイルパスが正しく指定されていないのでは?と思い、ダメ元でUSDCファイルを直接覗いてみました。

おぉぅ・・・解読不能。

SCNSceneクラスからUSDZを出力する

MDLAssetについて調べている過程でSCNSceneなるものを発見。SCNNodeにモデルやマテリアルをセットしてシーンに追加していくことができるようです。先程のやり方ではマテリアルを設定する過程がなかったので、これでテクスチャも設定できそうです。

NSURL *objUrl = [NSURL fileURLWithPath:"objのファイルパス"];
// MDLAssetを初期化
MDLAsset *objAsset = [[MDLAsset alloc]initWithURL:objUrl];
// シーンを初期化
SCNScene *scene = [SCNScene scene];
// MDLAssetを使ってノードを初期化
SCNNode *node = [SCNNode nodeWithMDLObject:[objAsset objectAtIndex:0]];
// マテリアルを生成
SCNMaterial *material = [SCNMaterial material];
// テクスチャを設定
material.diffuse.contents = [UIImage imageWithContentsOfFile:"画像のファイルパス"];
// マテリアルをノードに追加
node.geometry.firstMaterial = material;
// シーンに追加する
[scene.rootNode addChildNode:node];
// usdzとして書き出す
[scene writeToURL:usdzUrl options:nil delegate:nil progressHandler:nil];

そしてUSDZとして書き出してみたのですが...

テクスチャがない!(2度目)

SCNMaterialに設定した内容はUSDに反映されない?・・・埒が明かなくなってきたので、USDについて改めて調べることにしました。

3種類のUSD

USD形式にはテキスト形式 (usda) 、バイナリ形式 (usdc)、アーカイブ形式 (usdz)が存在します。テキスト形式のUSDAは解読ができそうです。こちらの記事を参考にテクスチャ付きのUSDAがどんなものなのか見てみます。

#usda 1.0

def Xform "hello"
{
   def Mesh "mesh"
   {
       uniform bool doubleSided = 0
       int[] faceVertexCounts = [4]
       int[] faceVertexIndices = [0, 1, 2, 3]
       rel material:binding = </hello/material_0>
       normal3f[] normals = [(0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0)]
       point3f[] points = [(-1, 0, -1), (-1, 0, 1), (1, 0, 1), (1, 0, -1)]
       texCoord2f[] primvars:st = [(0, 0), (1, 0), (1, 1), (0, 1)] (
           interpolation = "varying"
       )
   }

   def Material "material_0"
   {
       token inputs:frame:stPrimvarName = "st"
       token outputs:surface.connect = </hello/material_0/PBRShader.outputs:surface>

       def Shader "PBRShader"
       {
           uniform token info:id = "UsdPreviewSurface"
           color3f inputs:diffuseColor.connect = </hello/material_0/diffuseTexture.outputs:rgb>
           float inputs:metallic = 0.3
           float inputs:roughness = 0.3
           token outputs:surface
       }

       def Shader "stReader"
       {
           uniform token info:id = "UsdPrimvarReader_float2"
           token inputs:varname.connect = </hello/material_0.inputs:frame:stPrimvarName>
           float2 outputs:result
       }

       def Shader "diffuseTexture"
       {
           uniform token info:id = "UsdUVTexture"
           asset inputs:file = @tile_image.png@
           float2 inputs:st.connect = </hello/material_0/stReader.outputs:result>
           float3 outputs:rgb
       }
   }
}

"diffuseTexture"の中で画像ファイルを指定していることがわかります。

次にSCNSceneクラスからUSDAとして書き出した結果を見てみます。
※Materialを設定している箇所だけ抜粋します

def Scope "Materials"
{
   def Material "Material_0"
   {
       token outputs:surface.connect = </_20210328_163514/Materials/Material_0/surfaceShader.outputs:surface>

       def Shader "surfaceShader"
       {
           uniform token info:id = "UsdPreviewSurface"
           color3f inputs:emissiveColor = (0, 0, 0)
           normal3f inputs:normal = (1, 0.99999994, 1)
           float inputs:occlusion = 1
           float inputs:opacity = 1
           token outputs:surface
       }
   }
}

おや?画像ファイルを指定している箇所がありませんね。

画像を指定する記述があれば、3Dモデルに画像が貼られるはずです。
というわけで、diffuseTextureの例を参考にして書き足してみます。

def Scope "Materials"
{
   def Material "Material_0"
   {
       token outputs:surface.connect = </_20210328_163514/Materials/Material_0/surfaceShader.outputs:surface>

       def Shader "surfaceShader"
       {
            uniform token info:id = "UsdPreviewSurface"
            color3f inputs:emissiveColor = (0, 0, 0)
            color3f inputs:diffuseColor.connect = </_20210328_163514/Materials/Material_0/diffuseTexture.outputs:rgb>
            normal3f inputs:normal = (1, 0.99999994, 1)
            float inputs:occlusion = 1
            float inputs:opacity = 1
            token outputs:surface
        }
        
        def Shader "diffuseTexture"
        {
            uniform token info:id = "UsdUVTexture"
            asset inputs:file = @20210328_163514.png@
            float2 inputs:st.connect = </_20210328_163514/Materials/Material_0/stReader.outputs:result>
            float3 outputs:rgb
        }
    }
}

画像が貼られた3Dモデルが表示されました

このUSDAファイルをUSDZに変換してみます。

// usdaをusdzにして保存する
MDLAsset *usdaAsset = [[MDLAsset alloc]initWithURL:usdaUrl];
SCNScene *scene = [SCNScene scene];
SCNNode *node = [SCNNode nodeWithMDLObject:[usdaAsset objectAtIndex:0]];
[scene.rootNode addChildNode:node];
[scene writeToURL:usdzUrl options:nil delegate:nil progressHandler:nil];

テクスチャ付きのUSDZが出力できました。やったぜ。
※出力したusdzはこちら

まとめ

というわけで最終的に以下の方法で出力できました。

  • OBJファイルをUSDAとして出力
  • USDAファイルにMaterialに画像ファイルを指定する記述を追記して上書き
  • USDAファイルをUSDZとして出力

USDAファイルへの追記もスクリプトから実行できるので、結果的にはobjective-cのみでOBJからUSDZの変換ができました。

・・・なんというか、かなり強引な感じなのでもっとスマートなやり方があれば教えて欲しいです。

0
0
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
0
0