2
0
Эх сурвалжийг харах

EXRExporter: implements support for DataTexture export (#26810)

* EXRExporter: DataTexture support

Signed-off-by: Guilherme Avila <[email protected]>

* EXRExporter: update API & docs

Signed-off-by: Guilherme Avila <[email protected]>

* Examples: add misc_exporter_exr

Signed-off-by: Guilherme Avila <[email protected]>

* fix MIME-type

Signed-off-by: Guilherme Avila <[email protected]>

* update DataTexture usage example

Signed-off-by: Guilherme Avila <[email protected]>

* update screenshot

Signed-off-by: Guilherme Avila <[email protected]>

---------

Signed-off-by: Guilherme Avila <[email protected]>
Guilherme Avila 1 жил өмнө
parent
commit
53ee3779ee

+ 9 - 0
docs/examples/en/exporters/EXRExporter.html

@@ -78,6 +78,15 @@ ZIPS_COMPRESSION
 		Generates a .exr output from the input render target.
 		</p>
 
+		<h3>[method:null parse]( [param:DataTexture dataTexture], [param:Object options] )</h3>
+		<p>
+		[page:Function dataTexture] — DataTexture containing data used for exporting EXR image.<br /> 
+		[page:Options options] — Export options (details above).<br />
+		</p>
+		<p>
+		Generates a .exr output from the input data texture.
+		</p>
+
 		<h2>Source</h2>
 
 		<p>

+ 1 - 0
examples/files.json

@@ -409,6 +409,7 @@
 		"misc_exporter_ply",
 		"misc_exporter_stl",
 		"misc_exporter_usdz",
+		"misc_exporter_exr",
 		"misc_lookat"
 	],
 	"css2d": [

+ 102 - 24
examples/jsm/exporters/EXRExporter.js

@@ -21,61 +21,107 @@ const ZIP_COMPRESSION = 3;
 
 class EXRExporter {
 
-	parse( renderer, renderTarget, options ) {
+	parse( arg1, arg2, arg3 ) {
 
-		if ( ! supported( renderer, renderTarget ) ) return undefined;
+		if ( ! arg1 || ! ( arg1.isWebGLRenderer || arg1.isDataTexture ) ) {
 
-		const info = buildInfo( renderTarget, options ),
-			dataBuffer = getPixelData( renderer, renderTarget, info ),
-			rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ),
-			chunks = compressData( rawContentBuffer, info );
+			throw Error( 'EXRExporter.parse: Unsupported first parameter, expected instance of WebGLRenderer or DataTexture.' );
 
-		return fillData( chunks, info );
+		} else if ( arg1.isWebGLRenderer ) {
 
-	}
+			const renderer = arg1, renderTarget = arg2, options = arg3;
 
-}
+			supportedRTT( renderTarget );
+
+			const info = buildInfoRTT( renderTarget, options ),
+				dataBuffer = getPixelData( renderer, renderTarget, info ),
+				rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ),
+				chunks = compressData( rawContentBuffer, info );
+
+			return fillData( chunks, info );
 
-function supported( renderer, renderTarget ) {
+		} else if ( arg1.isDataTexture ) {
 
-	if ( ! renderer || ! renderer.isWebGLRenderer ) {
+			const texture = arg1, options = arg2;
 
-		console.error( 'EXRExporter.parse: Unsupported first parameter, expected instance of WebGLRenderer.' );
+			supportedDT( texture );
 
-		return false;
+			const info = buildInfoDT( texture, options ),
+				dataBuffer = texture.image.data,
+				rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ),
+				chunks = compressData( rawContentBuffer, info );
+
+			return fillData( chunks, info );
+
+		}
 
 	}
 
+}
+
+function supportedRTT( renderTarget ) {
+
 	if ( ! renderTarget || ! renderTarget.isWebGLRenderTarget ) {
 
-		console.error( 'EXRExporter.parse: Unsupported second parameter, expected instance of WebGLRenderTarget.' );
+		throw Error( 'EXRExporter.parse: Unsupported second parameter, expected instance of WebGLRenderTarget.' );
+
+	}
+
+	if ( renderTarget.isWebGLCubeRenderTarget || renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) {
 
-		return false;
+		throw Error( 'EXRExporter.parse: Unsupported render target type, expected instance of WebGLRenderTarget.' );
 
 	}
 
 	if ( renderTarget.texture.type !== FloatType && renderTarget.texture.type !== HalfFloatType ) {
 
-		console.error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture type.' );
-
-		return false;
+		throw Error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture type.' );
 
 	}
 
 	if ( renderTarget.texture.format !== RGBAFormat ) {
 
-		console.error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture format, expected RGBAFormat.' );
+		throw Error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture format, expected RGBAFormat.' );
+
+	}
+
+}
+
+function supportedDT( texture ) {
+
+	if ( texture.type !== FloatType && texture.type !== HalfFloatType ) {
+
+		throw Error( 'EXRExporter.parse: Unsupported DataTexture texture type.' );
+
+	}
+
+	if ( texture.format !== RGBAFormat ) {
+
+		throw Error( 'EXRExporter.parse: Unsupported DataTexture texture format, expected RGBAFormat.' );
+
+	}
+
+	if ( ! texture.image.data ) {
+
+		throw Error( 'EXRExporter.parse: Invalid DataTexture image data.' );
+
+	}
+
+	if ( texture.type === FloatType && texture.image.data.constructor.name !== 'Float32Array' ) {
 
-		return false;
+		throw Error( 'EXRExporter.parse: DataTexture image data doesn\'t match type, expected \'Float32Array\'.' );
 
 	}
 
+	if ( texture.type === HalfFloatType && texture.image.data.constructor.name !== 'Uint16Array' ) {
 
-	return true;
+		throw Error( 'EXRExporter.parse: DataTexture image data doesn\'t match type, expected \'Uint16Array\'.' );
+
+	}
 
 }
 
-function buildInfo( renderTarget, options = {} ) {
+function buildInfoRTT( renderTarget, options = {} ) {
 
 	const compressionSizes = {
 		0: 1,
@@ -87,7 +133,6 @@ function buildInfo( renderTarget, options = {} ) {
 		HEIGHT = renderTarget.height,
 		TYPE = renderTarget.texture.type,
 		FORMAT = renderTarget.texture.format,
-		COLOR_SPACE = renderTarget.texture.colorSpace,
 		COMPRESSION = ( options.compression !== undefined ) ? options.compression : ZIP_COMPRESSION,
 		EXPORTER_TYPE = ( options.type !== undefined ) ? options.type : HalfFloatType,
 		OUT_TYPE = ( EXPORTER_TYPE === FloatType ) ? 2 : 1,
@@ -99,7 +144,40 @@ function buildInfo( renderTarget, options = {} ) {
 		height: HEIGHT,
 		type: TYPE,
 		format: FORMAT,
-		colorSpace: COLOR_SPACE,
+		compression: COMPRESSION,
+		blockLines: COMPRESSION_SIZE,
+		dataType: OUT_TYPE,
+		dataSize: 2 * OUT_TYPE,
+		numBlocks: Math.ceil( HEIGHT / COMPRESSION_SIZE ),
+		numInputChannels: 4,
+		numOutputChannels: NUM_CHANNELS,
+	};
+
+}
+
+function buildInfoDT( texture, options = {} ) {
+
+	const compressionSizes = {
+		0: 1,
+		2: 1,
+		3: 16
+	};
+
+	const WIDTH = texture.image.width,
+		HEIGHT = texture.image.height,
+		TYPE = texture.type,
+		FORMAT = texture.format,
+		COMPRESSION = ( options.compression !== undefined ) ? options.compression : ZIP_COMPRESSION,
+		EXPORTER_TYPE = ( options.type !== undefined ) ? options.type : HalfFloatType,
+		OUT_TYPE = ( EXPORTER_TYPE === FloatType ) ? 2 : 1,
+		COMPRESSION_SIZE = compressionSizes[ COMPRESSION ],
+		NUM_CHANNELS = 4;
+
+	return {
+		width: WIDTH,
+		height: HEIGHT,
+		type: TYPE,
+		format: FORMAT,
 		compression: COMPRESSION,
 		blockLines: COMPRESSION_SIZE,
 		dataType: OUT_TYPE,

+ 224 - 0
examples/misc_exporter_exr.html

@@ -0,0 +1,224 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - exporter - exr</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - exporter - exr
+		</div>
+
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { EXRExporter, ZIP_COMPRESSION, ZIPS_COMPRESSION, NO_COMPRESSION } from 'three/addons/exporters/EXRExporter.js';
+			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			let scene, camera, renderer, exporter, mesh, controls, renderTarget, dataTexture;
+
+			const params = {
+				target: 'pmrem',
+				type: 'HalfFloatType',
+				compression: 'ZIP',
+				export: exportFile
+			};
+
+			init();
+			animate();
+
+			function init() {
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				//
+
+				camera = new THREE.PerspectiveCamera( 80, window.innerWidth / window.innerHeight, 0.1, 100 );
+				camera.position.set( 40, 0, 0 );
+
+				scene = new THREE.Scene();
+
+				exporter = new EXRExporter();
+				const rgbeloader = new RGBELoader();
+
+				//
+
+				const pmremGenerator = new THREE.PMREMGenerator( renderer );
+				pmremGenerator.compileEquirectangularShader();
+
+				rgbeloader.load( 'textures/equirectangular/san_giuseppe_bridge_2k.hdr', function ( texture ) {
+
+					texture.mapping = THREE.EquirectangularReflectionMapping;
+
+					renderTarget = pmremGenerator.fromEquirectangular( texture );
+					scene.background = renderTarget.texture;
+
+				} );
+
+				createDataTexture();
+
+				//
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.update();
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				const gui = new GUI();
+
+				const input = gui.addFolder( 'Input' );
+				input.add( params, 'target' ).options( [ 'pmrem', 'data-texture' ] ).onChange( swapScene );
+
+				const options = gui.addFolder( 'Output Options' );
+				options.add( params, 'type' ).options( [ 'FloatType', 'HalfFloatType' ] );
+				options.add( params, 'compression' ).options( [ 'ZIP', 'ZIPS', 'NONE' ] );
+
+				gui.add( params, 'export' ).name( 'Export EXR' );
+				gui.open();
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+				controls.update();
+				renderer.render( scene, camera );
+
+			}
+
+			function createDataTexture() {
+
+				const normal = new THREE.Vector3();
+				const coord = new THREE.Vector2();
+				const size = 800, radius = 320, factor = Math.PI * 0.5 / radius;
+				const data = new Float32Array( 4 * size * size );
+
+				for ( let i = 0; i < size; i ++ ) {
+
+					for ( let j = 0; j < size; j ++ ) {
+
+						const idx = i * size * 4 + j * 4;
+						coord.set( j, i ).subScalar( size / 2 );
+
+						if ( coord.length() < radius )
+							normal.set(
+								Math.sin( coord.x * factor ),
+								Math.sin( coord.y * factor ),
+								Math.cos( coord.x * factor )
+							);
+						else
+							normal.set( 0, 0, 1 );
+
+						data[ idx + 0 ] = .5 + .5 * normal.x;
+						data[ idx + 1 ] = .5 + .5 * normal.y;
+						data[ idx + 2 ] = .5 + .5 * normal.z;
+						data[ idx + 3 ] = 1.;
+
+					}
+
+				}
+
+				dataTexture = new THREE.DataTexture( data, size, size, THREE.RGBAFormat, THREE.FloatType );
+				dataTexture.needsUpdate = true;
+
+				const material = new THREE.MeshBasicMaterial( { map: dataTexture } );
+				const quad = new THREE.PlaneGeometry( 50, 50 );
+				mesh = new THREE.Mesh( quad, material );
+				mesh.visible = false;
+
+				scene.add( mesh );
+
+			}
+
+			function swapScene() {
+
+				if ( params.target == 'pmrem' ) {
+
+					camera.position.set( 40, 0, 0 );
+					controls.enabled = true;
+					scene.background = renderTarget.texture;
+					mesh.visible = false;
+
+				} else {
+
+					camera.position.set( 0, 0, 40 );
+					controls.enabled = false;
+					scene.background = new THREE.Color( 0, 0, 0 );
+					mesh.visible = true;
+
+				}
+
+			}
+
+			function exportFile() {
+
+				let result, exportType, exportCompression;
+
+				if ( params.type == 'HalfFloatType' )
+					exportType = THREE.HalfFloatType;
+				else
+					exportType = THREE.FloatType;
+
+				if ( params.compression == 'ZIP' )
+					exportCompression = ZIP_COMPRESSION;
+				else if ( params.compression == 'ZIPS' )
+					exportCompression = ZIPS_COMPRESSION;
+				else
+					exportCompression = NO_COMPRESSION;
+
+				if ( params.target == 'pmrem' )
+					result = exporter.parse( renderer, renderTarget, { type: exportType, compression: exportCompression } );
+				else
+					result = exporter.parse( dataTexture, { type: exportType, compression: exportCompression } );
+
+				saveArrayBuffer( result, params.target + '.exr' );
+
+			}
+
+			function saveArrayBuffer( buffer, filename ) {
+
+				const blob = new Blob( [ buffer ], { type: 'image/x-exr' } );
+				const link = document.createElement( 'a' );
+
+				link.href = URL.createObjectURL( blob );
+				link.download = filename;
+				link.click();
+
+			}
+
+		</script>
+
+	</body>
+</html>

BIN
examples/screenshots/misc_exporter_exr.jpg