瀏覽代碼

EXRLoader: support single channel luminance - RedFormat (#23007)

* EXRLoader: support single channel luminance - RedFormat encoding

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

* clean up

Signed-off-by: Guilherme Avila <[email protected]>
Guilherme Avila 3 年之前
父節點
當前提交
ab528acacd
共有 1 個文件被更改,包括 204 次插入237 次删除
  1. 204 237
      examples/jsm/loaders/EXRLoader.js

+ 204 - 237
examples/jsm/loaders/EXRLoader.js

@@ -6,10 +6,9 @@ import {
 	LinearEncoding,
 	LinearFilter,
 	NearestFilter,
+	RedFormat,
 	RGBAFormat,
 	RGBEEncoding,
-	RGBEFormat,
-	RGBFormat,
 	UnsignedByteType
 } from '../../../build/three.module.js';
 import * as fflate from '../libs/fflate.module.js';
@@ -1312,8 +1311,7 @@ class EXRLoader extends DataTextureLoader {
 			var inDataView = info.viewer;
 			var inOffset = { value: info.offset.value };
 
-			var tmpBufSize = info.width * scanlineBlockSize * ( EXRHeader.channels.length * info.type );
-			var outBuffer = new Uint16Array( tmpBufSize );
+			var outBuffer = new Uint16Array( info.width * info.scanlineBlockSize * ( info.channels * info.type ) );
 			var bitmap = new Uint8Array( BITMAP_SIZE );
 
 			// Setup channel info
@@ -1333,6 +1331,7 @@ class EXRLoader extends DataTextureLoader {
 			}
 
 			// Read range compression data
+
 			var minNonZero = parseUint16( inDataView, inOffset );
 			var maxNonZero = parseUint16( inDataView, inOffset );
 
@@ -1489,7 +1488,7 @@ class EXRLoader extends DataTextureLoader {
 
 			var inDataView = info.viewer;
 			var inOffset = { value: info.offset.value };
-			var outBuffer = new Uint8Array( info.width * info.lines * ( EXRHeader.channels.length * info.type * INT16_SIZE ) );
+			var outBuffer = new Uint8Array( info.width * info.lines * ( info.channels * info.type * INT16_SIZE ) );
 
 			// Read compression header information
 			var dwaHeader = {
@@ -1737,16 +1736,6 @@ class EXRLoader extends DataTextureLoader {
 
 		}
 
-		function parseUlong( dataView, offset ) {
-
-			var uLong = dataView.getUint32( 0, true );
-
-			offset.value = offset.value + ULONG_SIZE;
-
-			return uLong;
-
-		}
-
 		function parseRational( dataView, offset ) {
 
 			var x = parseInt32( dataView, offset );
@@ -2037,270 +2026,268 @@ class EXRLoader extends DataTextureLoader {
 
 		}
 
-		var bufferDataView = new DataView( buffer );
-		var uInt8Array = new Uint8Array( buffer );
-
-		var EXRHeader = {};
-
-		bufferDataView.getUint32( 0, true ); // magic
-		bufferDataView.getUint8( 4, true ); // versionByteZero
-		bufferDataView.getUint8( 5, true ); // fullMask
-
-		// start of header
-
-		var offset = { value: 8 }; // start at 8, after magic stuff
-
-		var keepReading = true;
+		function parseHeader( dataView, buffer, offset ) {
 
-		while ( keepReading ) {
+			const EXRHeader = {};
 
-			var attributeName = parseNullTerminatedString( buffer, offset );
+			if ( dataView.getUint32( 0, true ) != 20000630 ) // magic
+				throw "THREE.EXRLoader: provided file doesn't appear to be in OpenEXR format.";
 
-			if ( attributeName == 0 ) {
+			EXRHeader.version = dataView.getUint8( 4, true );
 
-				keepReading = false;
-
-			} else {
-
-				var attributeType = parseNullTerminatedString( buffer, offset );
-				var attributeSize = parseUint32( bufferDataView, offset );
-				var attributeValue = parseValue( bufferDataView, buffer, offset, attributeType, attributeSize );
-
-				if ( attributeValue === undefined ) {
-
-					console.warn( `EXRLoader.parse: skipped unknown header attribute type \'${ attributeType }\'.` );
-
-				} else {
-
-					EXRHeader[ attributeName ] = attributeValue;
-
-				}
+			const spec = dataView.getUint8( 5, true ); // fullMask
 
+			EXRHeader.spec = {
+				singleTile: !!(spec & 1),
+				longName: !!(spec & 2),
+				deepFormat: !!(spec & 4),
+				multiPart: !!(spec & 8),
 			}
 
-		}
+			// start of header
 
-		// offsets
-		var dataWindowHeight = EXRHeader.dataWindow.yMax + 1;
+			offset.value = 8; // start at 8 - after pre-amble
 
-		var uncompress;
-		var scanlineBlockSize;
+			var keepReading = true;
 
-		switch ( EXRHeader.compression ) {
+			while ( keepReading ) {
 
-			case 'NO_COMPRESSION':
+				var attributeName = parseNullTerminatedString( buffer, offset );
 
-				scanlineBlockSize = 1;
-				uncompress = uncompressRAW;
-				break;
+				if ( attributeName == 0 ) {
 
-			case 'RLE_COMPRESSION':
+					keepReading = false;
 
-				scanlineBlockSize = 1;
-				uncompress = uncompressRLE;
-				break;
-
-			case 'ZIPS_COMPRESSION':
-
-				scanlineBlockSize = 1;
-				uncompress = uncompressZIP;
-				break;
-
-			case 'ZIP_COMPRESSION':
-
-				scanlineBlockSize = 16;
-				uncompress = uncompressZIP;
-				break;
+				} else {
 
-			case 'PIZ_COMPRESSION':
+					var attributeType = parseNullTerminatedString( buffer, offset );
+					var attributeSize = parseUint32( dataView, offset );
+					var attributeValue = parseValue( dataView, buffer, offset, attributeType, attributeSize );
 
-				scanlineBlockSize = 32;
-				uncompress = uncompressPIZ;
-				break;
+					if ( attributeValue === undefined ) {
 
-			case 'PXR24_COMPRESSION':
+						console.warn( `EXRLoader.parse: skipped unknown header attribute type \'${attributeType}\'.` );
 
-				scanlineBlockSize = 16;
-				uncompress = uncompressPXR;
-				break;
+					} else {
 
-			case 'DWAA_COMPRESSION':
+						EXRHeader[ attributeName ] = attributeValue;
 
-				scanlineBlockSize = 32;
-				uncompress = uncompressDWA;
-				break;
+					}
 
-			case 'DWAB_COMPRESSION':
+				}
 
-				scanlineBlockSize = 256;
-				uncompress = uncompressDWA;
-				break;
+			} 
 
-			default:
+			if ( spec != 0 ) {
+				console.error( "EXRHeader:", EXRHeader );
+				throw "THREE.EXRLoader: provided file is currently unsupported."
+			}
 
-				throw 'EXRLoader.parse: ' + EXRHeader.compression + ' is unsupported';
+			return EXRHeader;
 
 		}
 
-		var size_t;
-		var getValue;
-
-		// mixed pixelType not supported
-		var pixelType = EXRHeader.channels[ 0 ].pixelType;
-
-		if ( pixelType === 1 ) { // half
+		function setupDecoder( EXRHeader, dataView, uInt8Array, offset, outputType ) {
 
-			switch ( this.type ) {
+			const EXRDecoder = { 
+				size: 0,
+				viewer: dataView,
+				array: uInt8Array,
+				offset: offset,
+				width: EXRHeader.dataWindow.xMax - EXRHeader.dataWindow.xMin + 1,
+				height: EXRHeader.dataWindow.yMax - EXRHeader.dataWindow.yMin + 1,
+				channels: EXRHeader.channels.length,
+				bytesPerLine: null,
+				lines: null,
+				inputSize: null,
+				type: EXRHeader.channels[ 0 ].pixelType,
+				uncompress: null,
+				getter: null,
+				format: null,
+				encoding: null,
+			};
 
-				case UnsignedByteType:
-				case FloatType:
+			switch ( EXRHeader.compression ) {
 
-					getValue = parseFloat16;
-					size_t = INT16_SIZE;
+				case 'NO_COMPRESSION':
+					EXRDecoder.lines = 1;
+					EXRDecoder.uncompress = uncompressRAW;
 					break;
 
-				case HalfFloatType:
-
-					getValue = parseUint16;
-					size_t = INT16_SIZE;
+				case 'RLE_COMPRESSION':
+					EXRDecoder.lines = 1;
+					EXRDecoder.uncompress = uncompressRLE;
 					break;
 
-			}
+				case 'ZIPS_COMPRESSION':
+					EXRDecoder.lines = 1;
+					EXRDecoder.uncompress = uncompressZIP;
+					break;
 
-		} else if ( pixelType === 2 ) { // float
+				case 'ZIP_COMPRESSION':
+					EXRDecoder.lines = 16;
+					EXRDecoder.uncompress = uncompressZIP;
+					break;
 
-			switch ( this.type ) {
+				case 'PIZ_COMPRESSION':
+					EXRDecoder.lines = 32;
+					EXRDecoder.uncompress = uncompressPIZ;
+					break;
 
-				case UnsignedByteType:
-				case FloatType:
+				case 'PXR24_COMPRESSION':
+					EXRDecoder.lines = 16;
+					EXRDecoder.uncompress = uncompressPXR;
+					break;
 
-					getValue = parseFloat32;
-					size_t = FLOAT32_SIZE;
+				case 'DWAA_COMPRESSION':
+					EXRDecoder.lines = 32;
+					EXRDecoder.uncompress = uncompressDWA;
 					break;
 
-				case HalfFloatType:
+				case 'DWAB_COMPRESSION':
+					EXRDecoder.lines = 256;
+					EXRDecoder.uncompress = uncompressDWA;
+					break;
 
-					getValue = decodeFloat32;
-					size_t = FLOAT32_SIZE;
+				default:
+					throw 'EXRLoader.parse: ' + EXRHeader.compression + ' is unsupported';
 
 			}
 
-		} else {
-
-			throw 'EXRLoader.parse: unsupported pixelType ' + pixelType + ' for ' + EXRHeader.compression + '.';
-
-		}
-
-		var numBlocks = dataWindowHeight / scanlineBlockSize;
-
-		for ( var i = 0; i < numBlocks; i ++ ) {
+			EXRDecoder.scanlineBlockSize = EXRDecoder.lines;
 
-			parseUlong( bufferDataView, offset ); // scanlineOffset
+			if ( EXRDecoder.type == 1 ) {
 
-		}
+				// half
+				switch ( outputType ) {
 
-		// we should be passed the scanline offset table, start reading pixel data
+					case UnsignedByteType:
+					case FloatType:
+						EXRDecoder.getter = parseFloat16;
+						EXRDecoder.inputSize = INT16_SIZE;
+						break;
 
-		var width = EXRHeader.dataWindow.xMax - EXRHeader.dataWindow.xMin + 1;
-		var height = EXRHeader.dataWindow.yMax - EXRHeader.dataWindow.yMin + 1;
-		// Firefox only supports RGBA (half) float textures
-		// var numChannels = EXRHeader.channels.length;
-		var numChannels = 4;
-		var size = width * height * numChannels;
+					case HalfFloatType:
+						EXRDecoder.getter = parseUint16;
+						EXRDecoder.inputSize = INT16_SIZE;
+						break;
 
-		// Fill initially with 1s for the alpha value if the texture is not RGBA, RGB values will be overwritten
-		switch ( this.type ) {
+				}
 
-			case UnsignedByteType:
-			case FloatType:
+			} else if ( EXRDecoder.type == 2 ) {
 
-				var byteArray = new Float32Array( size );
+				// float
+				switch ( outputType ) {
 
-				if ( EXRHeader.channels.length < numChannels ) {
+					case UnsignedByteType:
+					case FloatType:
+						EXRDecoder.getter = parseFloat32;
+						EXRDecoder.inputSize = FLOAT32_SIZE;
+						break;
 
-					byteArray.fill( 1, 0, size );
+					case HalfFloatType:
+						EXRDecoder.getter = decodeFloat32;
+						EXRDecoder.inputSize = FLOAT32_SIZE;
 
 				}
 
-				break;
+			} else {
 
-			case HalfFloatType:
+				throw 'EXRLoader.parse: unsupported pixelType ' + EXRDecoder.type + ' for ' + EXRHeader.compression + '.';
 
-				var byteArray = new Uint16Array( size );
+			}
 
-				if ( EXRHeader.channels.length < numChannels ) {
+			EXRDecoder.blockCount = ( EXRHeader.dataWindow.yMax + 1 ) / EXRDecoder.scanlineBlockSize;
 
-					byteArray.fill( 0x3C00, 0, size ); // Uint16Array holds half float data, 0x3C00 is 1
+			for ( var i = 0; i < EXRDecoder.blockCount; i ++ )
+				parseInt64( dataView, offset ) // scanlineOffset 
+				
+			// we should be passed the scanline offset table, ready to start reading pixel data.
 
-				}
+			// RGB images will be converted to RGBA format, preventing software emulation in select devices.
+			EXRDecoder.outputChannels = ( ( EXRDecoder.channels == 3 ) ? 4 : EXRDecoder.channels );
+			const size = EXRDecoder.width * EXRDecoder.height * EXRDecoder.outputChannels;
 
-				break;
+			switch ( outputType ) {
 
-			default:
+				case UnsignedByteType:
+				case FloatType:
+					EXRDecoder.byteArray = new Float32Array( size );
 
-				console.error( 'THREE.EXRLoader: unsupported type: ', this.type );
-				break;
+					// Fill initially with 1s for the alpha value if the texture is not RGBA, RGB values will be overwritten
+					if ( EXRDecoder.channels < EXRDecoder.outputChannels )
+						EXRDecoder.byteArray.fill( 1, 0, size ); 
+				
+					break;
 
-		}
+				case HalfFloatType:
+					EXRDecoder.byteArray = new Uint16Array( size );
+					
+					if ( EXRDecoder.channels < EXRDecoder.outputChannels )
+						EXRDecoder.byteArray.fill( 0x3C00, 0, size ); // Uint16Array holds half float data, 0x3C00 is 1
 
-		var channelOffsets = {
-			R: 0,
-			G: 1,
-			B: 2,
-			A: 3
-		};
+					break;
 
-		var compressionInfo = {
+				default:
+					console.error( 'THREE.EXRLoader: unsupported type: ', outputType );
+					break;
 
-			size: 0,
-			width: width,
-			lines: scanlineBlockSize,
+			}
 
-			offset: offset,
-			array: uInt8Array,
-			viewer: bufferDataView,
+			EXRDecoder.bytesPerLine = EXRDecoder.width * EXRDecoder.inputSize * EXRDecoder.channels;
 
-			type: pixelType,
-			channels: EXRHeader.channels.length,
+			if ( EXRDecoder.outputChannels == 4 ) {
+				EXRDecoder.format = RGBAFormat;
+				EXRDecoder.encoding = ( outputType == UnsignedByteType ) ? RGBEEncoding : LinearEncoding;
+			} else {
+				EXRDecoder.format = RedFormat;
+				EXRDecoder.encoding = LinearEncoding;
+			}
 
-		};
+			return EXRDecoder;
 
-		var line;
-		var size;
-		var viewer;
-		var tmpOffset = { value: 0 };
+		}
 
-		for ( var scanlineBlockIdx = 0; scanlineBlockIdx < height / scanlineBlockSize; scanlineBlockIdx ++ ) {
+		// start parsing file [START]
 
-			line = parseUint32( bufferDataView, offset ); // line_no
-			size = parseUint32( bufferDataView, offset ); // data_len
+		const bufferDataView = new DataView( buffer );
+		const uInt8Array = new Uint8Array( buffer );
+		const offset = { value: 0 };
 
-			compressionInfo.lines = ( line + scanlineBlockSize > height ) ? height - line : scanlineBlockSize;
-			compressionInfo.offset = offset;
-			compressionInfo.size = size;
+		// get header information and validate format.
+		const EXRHeader = parseHeader( bufferDataView, buffer, offset );
 
-			viewer = uncompress( compressionInfo );
+		// get input compression information and prepare decoding.
+		const EXRDecoder = setupDecoder( EXRHeader, bufferDataView, uInt8Array, offset, this.type );
 
-			offset.value += size;
+		const tmpOffset = { value: 0 };
+		const channelOffsets = { R: 0, G: 1, B: 2, A: 3, Y: 0 };
 
-			for ( var line_y = 0; line_y < scanlineBlockSize; line_y ++ ) {
+		for ( let scanlineBlockIdx = 0; scanlineBlockIdx < EXRDecoder.height / EXRDecoder.scanlineBlockSize; scanlineBlockIdx ++ ) {
+			
+			const line = parseUint32( bufferDataView, offset ); // line_no
+			EXRDecoder.size = parseUint32( bufferDataView, offset ); // data_len
+			EXRDecoder.lines = ( ( line + EXRDecoder.scanlineBlockSize > EXRDecoder.height ) ? (EXRDecoder.height-line) : EXRDecoder.scanlineBlockSize );
 
-				var true_y = line_y + ( scanlineBlockIdx * scanlineBlockSize );
+			const isCompressed = EXRDecoder.size < EXRDecoder.lines * EXRDecoder.bytesPerLine;
+			const viewer = isCompressed ? EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder );
 
-				if ( true_y >= height ) break;
+			offset.value += EXRDecoder.size;
 
-				for ( var channelID = 0; channelID < EXRHeader.channels.length; channelID ++ ) {
+			for ( let line_y = 0; line_y < EXRDecoder.scanlineBlockSize; line_y ++ ) {
 
-					var cOff = channelOffsets[ EXRHeader.channels[ channelID ].name ];
+				const true_y = line_y + scanlineBlockIdx * EXRDecoder.scanlineBlockSize;
+				if ( true_y >= EXRDecoder.height ) break;
 
-					for ( var x = 0; x < width; x ++ ) {
+				for ( let channelID = 0; channelID < EXRDecoder.channels; channelID ++ ) {
 
-						var idx = ( line_y * ( EXRHeader.channels.length * width ) ) + ( channelID * width ) + x;
-						tmpOffset.value = idx * size_t;
+					const cOff = channelOffsets[ EXRHeader.channels[ channelID ].name ];
 
-						var val = getValue( viewer, tmpOffset );
+					for ( let x = 0; x < EXRDecoder.width; x ++ ) {
 
-						byteArray[ ( ( ( height - 1 - true_y ) * ( width * numChannels ) ) + ( x * numChannels ) ) + cOff ] = val;
+						tmpOffset.value = ( line_y * ( EXRDecoder.channels * EXRDecoder.width ) + channelID * EXRDecoder.width + x ) * EXRDecoder.inputSize;
+						const outIndex = ( EXRDecoder.height - 1 - true_y ) * ( EXRDecoder.width * EXRDecoder.outputChannels ) + x * EXRDecoder.outputChannels + cOff;
+						EXRDecoder.byteArray[ outIndex ] = EXRDecoder.getter( viewer, tmpOffset );
 
 					}
 
@@ -2310,24 +2297,23 @@ class EXRLoader extends DataTextureLoader {
 
 		}
 
-		if ( this.type === UnsignedByteType ) {
+		// convert to RGBE if user specifies Uint8 output on a RGB input texture
+		if ( EXRDecoder.encoding == RGBEEncoding ) {
 
 			let v, i;
-			const size = byteArray.length;
+			const size = EXRDecoder.byteArray.length;
 			const RGBEArray = new Uint8Array( size );
 
-			for ( let h = 0; h < height; ++ h ) {
+			for ( let h = 0; h < EXRDecoder.height; ++ h ) {
 
-				for ( let w = 0; w < width; ++ w ) {
+				for ( let w = 0; w < EXRDecoder.width; ++ w ) {
 
-					i = h * width * 4 + w * 4;
-
-					const red = byteArray[ i ];
-					const green = byteArray[ i + 1 ];
-					const blue = byteArray[ i + 2 ];
-
-					v = ( red > green ) ? red : green;
-					v = ( blue > v ) ? blue : v;
+					i = h * EXRDecoder.width * 4 + w * 4;
+					const red = EXRDecoder.byteArray[ i ];
+					const green = EXRDecoder.byteArray[ i + 1 ];
+					const blue = EXRDecoder.byteArray[ i + 2 ];
+					v = red > green ? red : green;
+					v = blue > v ? blue : v;
 
 					if ( v < 1e-32 ) {
 
@@ -2337,7 +2323,6 @@ class EXRLoader extends DataTextureLoader {
 
 						const res = frexp( v );
 						v = res[ 0 ] * 256 / v;
-
 						RGBEArray[ i ] = red * v;
 						RGBEArray[ i + 1 ] = green * v;
 						RGBEArray[ i + 2 ] = blue * v;
@@ -2349,19 +2334,18 @@ class EXRLoader extends DataTextureLoader {
 
 			}
 
-			byteArray = RGBEArray;
+			EXRDecoder.byteArray = RGBEArray;
 
 		}
-
-		const format = ( this.type === UnsignedByteType ) ? RGBEFormat : ( numChannels === 4 ) ? RGBAFormat : RGBFormat;
-
+		
 		return {
 			header: EXRHeader,
-			width: width,
-			height: height,
-			data: byteArray,
-			format: format,
-			type: this.type
+			width: EXRDecoder.width,
+			height: EXRDecoder.height,
+			data: EXRDecoder.byteArray,
+			format: EXRDecoder.format,
+			encoding: EXRDecoder.encoding,
+			type: this.type,
 		};
 
 	}
@@ -2377,28 +2361,11 @@ class EXRLoader extends DataTextureLoader {
 
 		function onLoadCallback( texture, texData ) {
 
-			switch ( texture.type ) {
-
-				case UnsignedByteType:
-
-					texture.encoding = RGBEEncoding;
-					texture.minFilter = NearestFilter;
-					texture.magFilter = NearestFilter;
-					texture.generateMipmaps = false;
-					texture.flipY = false;
-					break;
-
-				case FloatType:
-				case HalfFloatType:
-
-					texture.encoding = LinearEncoding;
-					texture.minFilter = LinearFilter;
-					texture.magFilter = LinearFilter;
-					texture.generateMipmaps = false;
-					texture.flipY = false;
-					break;
-
-			}
+			texture.encoding = texData.encoding;
+			texture.minFilter = ( texture.encoding == RGBEEncoding ) ? NearestFilter : LinearFilter;
+			texture.magFilter = ( texture.encoding == RGBEEncoding ) ? NearestFilter : LinearFilter;
+			texture.generateMipmaps = false;
+			texture.flipY = false;
 
 			if ( onLoad ) onLoad( texture, texData );