diff --git a/editor/js/Menubar.File.js b/editor/js/Menubar.File.js index b34a744be8b898..9666b6f658f01f 100644 --- a/editor/js/Menubar.File.js +++ b/editor/js/Menubar.File.js @@ -390,7 +390,7 @@ function MenubarFile( editor ) { var exporter = new USDZExporter(); - saveArrayBuffer( exporter.parse( editor.scene, { binary: true } ), 'model.usdz' ); + saveArrayBuffer( await exporter.parse( editor.scene, { binary: true } ), 'model.usdz' ); } ); options.add( option ); diff --git a/examples/jsm/exporters/USDZExporter.js b/examples/jsm/exporters/USDZExporter.js index a9763074e532a7..812086e35173d0 100644 --- a/examples/jsm/exporters/USDZExporter.js +++ b/examples/jsm/exporters/USDZExporter.js @@ -2,26 +2,71 @@ import { zipSync, strToU8 } from '../libs/fflate.module.min.js'; class USDZExporter { - parse( scene ) { + async parse( scene ) { let output = buildHeader(); const materials = {}; + const textures = {}; scene.traverse( ( object ) => { if ( object.isMesh ) { - materials[ object.material.uuid ] = object.material; - output += buildXform( object, buildMesh( object.geometry, object.material ) ); + const geometry = object.geometry; + const material = object.material; + + materials[ material.uuid ] = material; + + if ( material.map !== null ) textures[ material.map.uuid ] = material.map; + if ( material.normalMap !== null ) textures[ material.normalMap.uuid ] = material.normalMap; + if ( material.aoMap !== null ) textures[ material.aoMap.uuid ] = material.aoMap; + if ( material.roughnessMap !== null ) textures[ material.roughnessMap.uuid ] = material.roughnessMap; + if ( material.metalnessMap !== null ) textures[ material.metalnessMap.uuid ] = material.metalnessMap; + if ( material.emissiveMap !== null ) textures[ material.emissiveMap.uuid ] = material.emissiveMap; + + output += buildXform( object, buildMesh( geometry, material ) ); } } ); output += buildMaterials( materials ); + output += buildTextures( textures ); + + const files = {}; + + for ( const uuid in textures ) { - return zipSync( { 'model.usda': strToU8( output ) }, { level: 0 } ); + const texture = textures[ uuid ]; + files[ 'Texture_' + texture.id + '.jpg' ] = await img2U8( texture.image ); + + } + + return zipSync( { 'model.usda': strToU8( output ), 'textures': files }, { level: 0 } ); + + } + +} + +async function img2U8( image ) { + + if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || + ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) || + ( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) || + ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) { + + const canvas = document.createElement( 'canvas' ); + canvas.width = image.width; + canvas.height = image.height; + + const context = canvas.getContext( '2d' ); + context.translate( 0, canvas.height ); + context.scale( 1, - 1 ); + context.drawImage( image, 0, 0, canvas.width, canvas.height ); + + const blob = await new Promise( resolve => canvas.toBlob( resolve, 'image/jpeg' ) ); + return new Uint8Array( await blob.arrayBuffer() ); } @@ -201,6 +246,45 @@ ${ array.join( '' ) } function buildMaterial( material ) { + const textures = []; + + if ( material.map !== null ) { + + textures.push( ` float3 inputs:diffuseColor.connect = ` ); + + } + + if ( material.normalMap !== null ) { + + textures.push( ` float3 inputs:normal.connect = ` ); + + } + + if ( material.aoMap !== null ) { + + textures.push( ` float inputs:occlusion.connect = ` ); + + } + + if ( material.roughnessMap !== null ) { + + textures.push( ` float inputs:roughness.connect = ` ); + + } + + if ( material.metalnessMap !== null ) { + + textures.push( ` float inputs:metalness.connect = ` ); + + } + + if ( material.emissiveMap !== null ) { + + textures.push( ` float3 inputs:emissive.connect = ` ); + + } + + return ` def Material "Material_${ material.id }" { @@ -209,10 +293,11 @@ function buildMaterial( material ) { def Shader "PreviewSurface" { uniform token info:id = "UsdPreviewSurface" - color3f inputs:diffuseColor = ${ buildColor( material.color ) } - color3f inputs:emissiveColor = ${ buildColor( material.emissive ) } + float3 inputs:diffuseColor = ${ buildColor( material.color ) } float inputs:metallic = ${ material.metalness } float inputs:roughness = ${ material.roughness } +${ textures.join( '\n' ) } + int inputs:useSpecularWorkflow = 0 token outputs:surface } } @@ -220,6 +305,43 @@ function buildMaterial( material ) { } +function buildTextures( textures ) { + + const array = []; + + for ( const uuid in textures ) { + + const texture = textures[ uuid ]; + + array.push( buildTexture( texture ) ); + + } + + return `def "Textures" +{ +${ array.join( '' ) } +} + +`; + +} + +function buildTexture( texture ) { + + return ` + def Shader "Texture_${ texture.id }" + { + uniform token info:id = "UsdUVTexture" + asset inputs:file = @textures/Texture_${ texture.id }.jpg@ + token inputs:isSRGB = "auto" + token inputs:wrapS = "repeat" + token inputs:wrapT = "repeat" + float3 outputs:rgb + } +`; + +} + function buildColor( color ) { return `(${ color.r }, ${ color.g }, ${ color.b })`;