Ver Fonte

GLTFExporter: export from compressed texture data (#23321)

* allow exporting compressed meshes and textures (e.g. draco + ktx2)

added coffemat.etc1s.glb and etc1s+draco, etc1s+meshopt to test compressed formats

only use one temp render context, clean up renderer after writing file, make metalnessMap and roughnessMap readable

refactor: move to TextureUtils class and use that in GLTFExporter, respect sRGB vs. Linear, cache some generated objects

cleanup

simplify modifiedMap access

fix formatting for TextureUtils

dispose of temporary renderer

remove duplicate switch entries

* addressing PR feedback and updating to colorSpace

* remove meshopt references/test file for now

* rm old import

* PR feedback

* GLTFExporter, TextureUtils: Minor fixes.

* cleanup; use original coffeemat model; rm other coffeemat model again

---------

Co-authored-by: Don McCurdy <[email protected]>
hybridherbst há 2 anos atrás
pai
commit
412e471b97

+ 25 - 2
examples/jsm/exporters/GLTFExporter.js

@@ -21,8 +21,10 @@ import {
 	Scene,
 	Source,
 	SRGBColorSpace,
+	CompressedTexture,
 	Vector3
 } from 'three';
+import { decompress } from './../utils/TextureUtils.js';
 
 
 /**
@@ -827,6 +829,18 @@ class GLTFWriter {
 
 		console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' );
 
+		if ( metalnessMap instanceof CompressedTexture ) {
+
+			metalnessMap = decompress( metalnessMap );
+
+		}
+
+		if ( roughnessMap instanceof CompressedTexture ) {
+
+			roughnessMap = decompress( roughnessMap );
+
+		}
+
 		const metalness = metalnessMap ? metalnessMap.image : null;
 		const roughness = roughnessMap ? roughnessMap.image : null;
 
@@ -1146,7 +1160,7 @@ class GLTFWriter {
 
 		} else {
 
-			throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type.' );
+			throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type: ' + attribute.array.constructor.name );
 
 		}
 
@@ -1236,7 +1250,7 @@ class GLTFWriter {
 
 				if ( format !== RGBAFormat ) {
 
-					console.error( 'GLTFExporter: Only RGBAFormat is supported.' );
+					console.error( 'GLTFExporter: Only RGBAFormat is supported.', format );
 
 				}
 
@@ -1344,6 +1358,8 @@ class GLTFWriter {
 	 */
 	processTexture( map ) {
 
+		const writer = this;
+		const options = writer.options;
 		const cache = this.cache;
 		const json = this.json;
 
@@ -1351,6 +1367,13 @@ class GLTFWriter {
 
 		if ( ! json.textures ) json.textures = [];
 
+		// make non-readable textures (e.g. CompressedTexture) readable by blitting them into a new texture
+		if ( map instanceof CompressedTexture ) {
+
+			map = decompress( map, options.maxTextureSize );
+
+		}
+
 		let mimeType = map.userData.mimeType;
 
 		if ( mimeType === 'image/webp' ) mimeType = 'image/png';

+ 86 - 0
examples/jsm/utils/TextureUtils.js

@@ -0,0 +1,86 @@
+import {
+	PlaneGeometry,
+	ShaderMaterial,
+	Uniform,
+	Mesh,
+	PerspectiveCamera,
+	Scene,
+	WebGLRenderer,
+	Texture,
+	SRGBColorSpace
+} from 'three';
+
+let _renderer;
+let fullscreenQuadGeometry;
+let fullscreenQuadMaterial;
+let fullscreenQuad;
+
+export function decompress( texture, maxTextureSize = Infinity, renderer = null ) {
+
+	if ( ! fullscreenQuadGeometry ) fullscreenQuadGeometry = new PlaneGeometry( 2, 2, 1, 1 );
+	if ( ! fullscreenQuadMaterial ) fullscreenQuadMaterial = new ShaderMaterial( {
+		uniforms: { blitTexture: new Uniform( texture ) },
+		vertexShader: `
+            varying vec2 vUv;
+            void main(){
+                vUv = uv;
+                gl_Position = vec4(position.xy * 1.0,0.,.999999);
+            }`,
+		fragmentShader: `
+            uniform sampler2D blitTexture; 
+            varying vec2 vUv;
+
+            void main(){ 
+                gl_FragColor = vec4(vUv.xy, 0, 1);
+                
+                #ifdef IS_SRGB
+                gl_FragColor = LinearTosRGB( texture2D( blitTexture, vUv) );
+                #else
+                gl_FragColor = texture2D( blitTexture, vUv);
+                #endif
+            }`
+	} );
+
+	fullscreenQuadMaterial.uniforms.blitTexture.value = texture;
+	fullscreenQuadMaterial.defines.IS_SRGB = texture.colorSpace == SRGBColorSpace;
+	fullscreenQuadMaterial.needsUpdate = true;
+
+	if ( ! fullscreenQuad ) {
+
+		fullscreenQuad = new Mesh( fullscreenQuadGeometry, fullscreenQuadMaterial );
+		fullscreenQuad.frustrumCulled = false;
+
+	}
+
+	const _camera = new PerspectiveCamera();
+	const _scene = new Scene();
+	_scene.add( fullscreenQuad );
+
+	if ( ! renderer ) {
+
+		renderer = _renderer = new WebGLRenderer( { antialias: false } );
+
+	}
+
+	renderer.setSize( Math.min( texture.image.width, maxTextureSize ), Math.min( texture.image.height, maxTextureSize ) );
+	renderer.clear();
+	renderer.render( _scene, _camera );
+
+	const readableTexture = new Texture( renderer.domElement );
+
+	readableTexture.minFilter = texture.minFilter;
+	readableTexture.magFilter = texture.magFilter;
+	readableTexture.wrapS = texture.wrapS;
+	readableTexture.wrapT = texture.wrapT;
+	readableTexture.name = texture.name;
+
+	if ( _renderer ) {
+
+		_renderer.dispose();
+		_renderer = null;
+
+	}
+
+	return readableTexture;
+
+}

+ 35 - 2
examples/misc_exporter_gltf.html

@@ -30,6 +30,8 @@
 
 			import { GLTFExporter } from 'three/addons/exporters/GLTFExporter.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+			import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js';
+			import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
 
 			function exportGLTF( input ) {
@@ -99,7 +101,7 @@
 			let container;
 
 			let camera, object, object2, material, geometry, scene1, scene2, renderer;
-			let gridHelper, sphere, model;
+			let gridHelper, sphere, model, coffeemat;
 
 			const params = {
 				trs: false,
@@ -111,7 +113,8 @@
 				exportSphere: exportSphere,
 				exportModel: exportModel,
 				exportObjects: exportObjects,
-				exportSceneObject: exportSceneObject
+				exportSceneObject: exportSceneObject,
+				exportCompressedObject: exportCompressedObject,
 			};
 
 			init();
@@ -451,6 +454,29 @@
 
 				window.addEventListener( 'resize', onWindowResize );
 
+				// ---------------------------------------------------------------------
+				// Exporting compressed textures and meshes (KTX2 / Draco / Meshopt)
+				// ---------------------------------------------------------------------
+				const ktx2Loader = new KTX2Loader()
+					.setTranscoderPath( 'jsm/libs/basis/' )
+					.detectSupport( renderer );
+
+				const gltfLoader = new GLTFLoader().setPath( 'models/gltf/' );
+				gltfLoader.setKTX2Loader( ktx2Loader );
+				gltfLoader.setMeshoptDecoder( MeshoptDecoder );
+				gltfLoader.load( 'coffeemat.glb', function ( gltf ) {
+
+					gltf.scene.position.x = 400;
+					gltf.scene.position.z = - 200;
+
+					scene1.add( gltf.scene );
+
+					coffeemat = gltf.scene;
+
+				} );
+
+				//
+
 				const gui = new GUI();
 
 				let h = gui.addFolder( 'Settings' );
@@ -466,6 +492,7 @@
 				h.add( params, 'exportModel' ).name( 'Export Model' );
 				h.add( params, 'exportObjects' ).name( 'Export Sphere With Grid' );
 				h.add( params, 'exportSceneObject' ).name( 'Export Scene 1 and Object' );
+				h.add( params, 'exportCompressedObject' ).name( 'Export Coffeemat (from compressed data)' );
 
 				gui.open();
 
@@ -507,6 +534,12 @@
 
 			}
 
+			function exportCompressedObject() {
+
+				exportGLTF( [ coffeemat ] );
+
+			}
+
 			function onWindowResize() {
 
 				camera.aspect = window.innerWidth / window.innerHeight;

BIN
examples/screenshots/misc_exporter_gltf.jpg