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の変換ができました。
・・・なんというか、かなり強引な感じなのでもっとスマートなやり方があれば教えて欲しいです。