Skip to content

Commit

Permalink
allow exporting compressed meshes and textures (e.g. draco + ktx2)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
hybridherbst committed May 23, 2023
1 parent c0f9764 commit 706e9cf
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 7 deletions.
35 changes: 30 additions & 5 deletions examples/jsm/exporters/GLTFExporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ import {
RepeatWrapping,
Scene,
Source,
SRGBColorSpace,
sRGBEncoding,
CompressedTexture,
Vector3
} from 'three';
import { decompress } from './../utils/TextureUtils.js';


/**
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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 );

}

Expand Down Expand Up @@ -1236,7 +1250,7 @@ class GLTFWriter {

if ( format !== RGBAFormat ) {

console.error( 'GLTFExporter: Only RGBAFormat is supported.' );
console.error( 'GLTFExporter: Only RGBAFormat is supported.', image );

}

Expand Down Expand Up @@ -1344,13 +1358,24 @@ class GLTFWriter {
*/
processTexture( map ) {

const writer = this;
const options = writer.options;
const cache = this.cache;
const json = this.json;

if ( cache.textures.has( map ) ) return cache.textures.get( map );

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';
Expand Down
95 changes: 95 additions & 0 deletions examples/jsm/utils/TextureUtils.js
Original file line number Diff line number Diff line change
@@ -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;

}
47 changes: 45 additions & 2 deletions examples/misc_exporter_gltf.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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,
Expand All @@ -111,7 +114,8 @@
exportSphere: exportSphere,
exportModel: exportModel,
exportObjects: exportObjects,
exportSceneObject: exportSceneObject
exportSceneObject: exportSceneObject,
exportCompressedObject: exportCompressedObject,
};

init();
Expand Down Expand Up @@ -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' );
Expand All @@ -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();

Expand Down Expand Up @@ -507,6 +544,12 @@

}

function exportCompressedObject() {

exportGLTF( [ coffeemat ] );

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
Expand Down
Binary file added examples/models/gltf/coffeemat.etc1s+draco.glb
Binary file not shown.
Binary file added examples/models/gltf/coffeemat.etc1s+meshopt.glb
Binary file not shown.
Binary file modified examples/screenshots/misc_exporter_gltf.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 706e9cf

Please sign in to comment.