Răsfoiți Sursa

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
Felix Herbst 3 ani în urmă
părinte
comite
706e9cfdcd

+ 30 - 5
examples/jsm/exporters/GLTFExporter.js

@@ -20,9 +20,11 @@ import {
 	RepeatWrapping,
 	Scene,
 	Source,
-	SRGBColorSpace,
+	sRGBEncoding,
+	CompressedTexture,
 	Vector3
 } from 'three';
+import { decompress } from './../utils/TextureUtils.js';
 
 
 /**
@@ -827,8 +829,20 @@ class GLTFWriter {
 
 		console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' );
 
-		const metalness = metalnessMap ? metalnessMap.image : null;
-		const roughness = roughnessMap ? roughnessMap.image : null;
+		if ( metalnessMap instanceof CompressedTexture ) {
+
+			metalnessMap = decompress( metalnessMap );
+
+		}
+
+		if ( roughnessMap instanceof CompressedTexture ) {
+
+			roughnessMap = decompress( roughnessMap );
+
+		}
+
+		const metalness = metalnessMap?.image;
+		const roughness = roughnessMap?.image;
 
 		const width = Math.max( metalness ? metalness.width : 0, roughness ? roughness.width : 0 );
 		const height = Math.max( metalness ? metalness.height : 0, roughness ? roughness.height : 0 );
@@ -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 );
 
 		}
 
@@ -1236,7 +1250,7 @@ class GLTFWriter {
 
 				if ( format !== RGBAFormat ) {
 
-					console.error( 'GLTFExporter: Only RGBAFormat is supported.' );
+					console.error( 'GLTFExporter: Only RGBAFormat is supported.', image );
 
 				}
 
@@ -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,15 @@ 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 );
+			// remove from cache, as the underlaying canvas is always the same between decompressed textures
+			cache.images.delete( map.image );
+
+		}
+
 		let mimeType = map.userData.mimeType;
 
 		if ( mimeType === 'image/webp' ) mimeType = 'image/png';

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

@@ -0,0 +1,95 @@
+import {
+	PlaneGeometry,
+	ShaderMaterial,
+	Uniform,
+	Mesh,
+	PerspectiveCamera,
+	Scene,
+	WebGLRenderer,
+	Texture,
+	sRGBEncoding
+} from 'three';
+
+let temporaryRenderer;
+let fullscreenQuadGeometry;
+let fullscreenQuadMaterial;
+let fullscreenQuad;
+
+export function decompress( texture, maxTextureSize, 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;
+
+            // took from threejs 05fc79cd52b79e8c3e8dec1e7dca72c5c39983a4
+            vec4 conv_LinearTosRGB( in vec4 value ) {
+                return vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );
+            }
+
+            void main(){ 
+                gl_FragColor = vec4(vUv.xy, 0, 1);
+                
+                #ifdef IS_SRGB
+                gl_FragColor = conv_LinearTosRGB( texture2D( blitTexture, vUv) );
+                #else
+                gl_FragColor = texture2D( blitTexture, vUv);
+                #endif
+            }`
+	} );
+
+	fullscreenQuadMaterial.uniforms.blitTexture.value = texture;
+	fullscreenQuadMaterial.defines.IS_SRGB = texture.encoding == sRGBEncoding;
+	fullscreenQuadMaterial.needsUpdate = true;
+
+	if ( ! fullscreenQuad ) {
+
+		fullscreenQuad = new Mesh( fullscreenQuadGeometry, fullscreenQuadMaterial );
+		fullscreenQuad.frustrumCulled = false;
+
+	}
+
+	const temporaryCam = new PerspectiveCamera();
+	const temporaryScene = new Scene();
+	temporaryScene.add( fullscreenQuad );
+
+	if ( ! renderer ) {
+
+		if ( ! temporaryRenderer )
+			temporaryRenderer = new WebGLRenderer( { antialias: false } );
+
+		renderer = temporaryRenderer;
+
+	}
+
+	renderer.setSize( Math.min( texture.image.width, maxTextureSize ), Math.min( texture.image.height, maxTextureSize ) );
+	renderer.clear();
+	renderer.render( temporaryScene, temporaryCam );
+
+	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;
+
+	readableTexture.userData.mimeType = 'image/png';
+
+	if ( temporaryRenderer ) {
+
+		temporaryRenderer.dispose();
+
+	}
+
+	return readableTexture;
+
+}

+ 45 - 2
examples/misc_exporter_gltf.html

@@ -30,6 +30,9 @@
 
 			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 { DRACOLoader } from 'three/addons/loaders/DRACOLoader.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 +102,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 +114,8 @@
 				exportSphere: exportSphere,
 				exportModel: exportModel,
 				exportObjects: exportObjects,
-				exportSceneObject: exportSceneObject
+				exportSceneObject: exportSceneObject,
+				exportCompressedObject: exportCompressedObject,
 			};
 
 			init();
@@ -451,6 +455,38 @@
 
 				window.addEventListener( 'resize', onWindowResize );
 
+				// ---------------------------------------------------------------------
+				// Exporting compressed textures and meshes (KTX2 / Draco / Meshopt)
+				// ---------------------------------------------------------------------
+				const ktx2Loader = new KTX2Loader()
+					.setTranscoderPath( 'jsm/libs/basis/' )
+					.detectSupport( renderer );
+
+				const dracoLoader = new DRACOLoader()
+					.setDecoderPath( 'jsm/libs/draco/' );
+
+				const gltfLoader = new GLTFLoader().setPath( 'models/gltf/' );
+				gltfLoader.setKTX2Loader( ktx2Loader );
+				gltfLoader.setDRACOLoader( dracoLoader );
+				gltfLoader.setMeshoptDecoder( MeshoptDecoder );
+				gltfLoader.load( 'coffeemat.etc1s+draco.glb', function ( gltf ) {
+
+					// coffeemat.etc1s+draco.glb was produced from the source scene using gltf-transform:
+					// gltf-transform etc1s coffeemat/scene.gltf coffeemat.etc1s.glb
+					// gltf-transform draco coffeemat.etc1s.glb coffeemat.etc1s+draco.glb
+					// The resulting model uses KHR_texture_basisu (for texture compression using ETC1S) and DRACO mesh compression.
+
+					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 +502,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 (compressed)' );
 
 				gui.open();
 
@@ -507,6 +544,12 @@
 
 			}
 
+			function exportCompressedObject() {
+
+				exportGLTF( [ coffeemat ] );
+
+			}
+
 			function onWindowResize() {
 
 				camera.aspect = window.innerWidth / window.innerHeight;

BIN
examples/models/gltf/coffeemat.etc1s+draco.glb


BIN
examples/models/gltf/coffeemat.etc1s+meshopt.glb


BIN
examples/screenshots/misc_exporter_gltf.jpg