Browse Source

Merge pull request #12891 from richardmonette/dev

add EXRLoader example
Mr.doob 7 years ago
parent
commit
ec1d8ae9e5

+ 346 - 0
examples/js/loaders/EXRLoader.js

@@ -0,0 +1,346 @@
+/**
+ * @author Richard M. / https://github.com/richardmonette
+ */
+
+// https://github.com/mrdoob/three.js/issues/10652
+// https://en.wikipedia.org/wiki/OpenEXR
+
+THREE.EXRLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.EXRLoader.prototype = Object.create( THREE.DataTextureLoader.prototype );
+
+THREE.EXRLoader.prototype._parser = function ( buffer ) {
+
+	var parseNullTerminatedString = function( buffer, offset ) {
+
+		var uintBuffer = new Uint8Array( buffer );
+		var endOffset = 0;
+
+		while ( uintBuffer[ offset.value + endOffset ] != 0 ) {
+
+			endOffset += 1;
+
+		}
+
+		var stringValue = new TextDecoder().decode(
+			new Uint8Array( buffer ).slice( offset.value, offset.value + endOffset )
+		);
+
+		offset.value = offset.value + endOffset + 1;
+
+		return stringValue;
+
+	}
+
+	var parseFixedLengthString = function( buffer, offset, size ) {
+
+		var stringValue = new TextDecoder().decode(
+			new Uint8Array( buffer ).slice( offset.value, offset.value + size )
+		);
+
+		offset.value = offset.value + size;
+
+		return stringValue;
+
+	}
+
+	var parseUlong = function( buffer, offset ) {
+
+		var uLong = new DataView( buffer.slice( offset.value, offset.value + 4 ) ).getUint32( 0, true );
+
+		offset.value = offset.value + 8;
+
+		return uLong;
+
+	}
+
+	var parseUint32 = function( buffer, offset ) {
+
+		var Uint32 = new DataView( buffer.slice( offset.value, offset.value + 4 ) ).getUint32( 0, true );
+
+		offset.value = offset.value + 4;
+
+		return Uint32;
+
+	}
+
+	var parseUint8 = function( buffer, offset ) {
+
+		var Uint8 = new DataView( buffer.slice( offset.value, offset.value + 1 ) ).getUint8( 0, true );
+
+		offset.value = offset.value + 1;
+
+		return Uint8;
+
+	}
+
+	var parseFloat32 = function( buffer, offset ) {
+
+		var float = new DataView( buffer.slice( offset.value, offset.value + 4 ) ).getFloat32( 0, true );
+
+		offset.value += 4;
+
+		return float;
+
+	}
+
+	// https://stackoverflow.com/questions/5678432/decompressing-half-precision-floats-in-javascript
+	var decodeFloat16 = function( binary ) {
+
+		var exponent = ( binary & 0x7C00 ) >> 10,
+		fraction = binary & 0x03FF;
+
+		return ( binary >> 15 ? - 1 : 1 ) * (
+				exponent ?
+				(
+						exponent === 0x1F ?
+						fraction ? NaN : Infinity :
+						Math.pow( 2, exponent - 15 ) * ( 1 + fraction / 0x400 )
+				) :
+				6.103515625e-5 * ( fraction / 0x400 )
+		);
+
+	}
+
+	var parseFloat16 = function( buffer, offset ) {
+
+		var float = new DataView( buffer.slice( offset.value, offset.value + 2 ) ).getUint16( 0, true );
+
+		offset.value += 2;
+
+		return decodeFloat16( float );
+
+	}
+
+	var parseChlist = function( buffer, offset, size ) {
+
+		var startOffset = offset.value;
+		var channels = [];
+
+		while ( offset.value < ( startOffset + size - 1 ) ) {
+
+			var name = parseNullTerminatedString( buffer, offset );
+			var pixelType = parseUint32( buffer, offset ); // TODO: Cast this to UINT, HALF or FLOAT
+			var pLinear = parseUint8( buffer, offset );
+			offset.value += 3; // reserved, three chars
+			var xSampling = parseUint32( buffer, offset );
+			var ySampling = parseUint32( buffer, offset );
+
+			channels.push( {
+				name: name,
+				pixelType: pixelType,
+				pLinear: pLinear,
+				xSampling: xSampling,
+				ySampling: ySampling
+			} );
+
+		}
+
+		offset.value += 1;
+
+		return channels;
+
+	}
+
+	var parseChromaticities = function( buffer, offset ) {
+
+		var redX = parseFloat32( buffer, offset );
+		var redY = parseFloat32( buffer, offset );
+		var greenX = parseFloat32( buffer, offset );
+		var greenY = parseFloat32( buffer, offset );
+		var blueX = parseFloat32( buffer, offset );
+		var blueY = parseFloat32( buffer, offset );
+		var whiteX = parseFloat32( buffer, offset );
+		var whiteY = parseFloat32( buffer, offset );
+
+		return { redX: redX, redY: redY, greenX, greenY, blueX, blueY, whiteX, whiteY };
+
+	}
+
+	var parseCompression = function( buffer, offset ) {
+
+		var compressionCodes = [
+			'NO_COMPRESSION',
+			'PIZ_COMPRESSION'
+		];
+
+		var compression = parseUint8( buffer, offset );
+
+		return compressionCodes[ compression ];
+
+	}
+
+	var parseBox2i = function( buffer, offset ) {
+
+		var xMin = parseUint32( buffer, offset );
+		var yMin = parseUint32( buffer, offset );
+		var xMax = parseUint32( buffer, offset );
+		var yMax = parseUint32( buffer, offset );
+
+		return { xMin: xMin, yMin: yMin, xMax: xMax, yMax: yMax };
+
+	}
+
+	var parseLineOrder = function( buffer, offset ) {
+
+		var lineOrders = [
+			'INCREASING_Y'
+		];
+
+		var lineOrder = parseUint8( buffer, offset );
+
+		return lineOrders[ lineOrder ];
+
+	}
+
+	var parseV2f = function( buffer, offset ) {
+
+		var x = parseFloat32( buffer, offset );
+		var y = parseFloat32( buffer, offset );
+
+		return [ x, y ];
+
+	}
+
+	var parseValue = function( buffer, offset, type, size ) {
+
+		if ( type == 'string' || type == 'iccProfile' ) {
+
+			return parseFixedLengthString( buffer, offset, size );
+
+		} else if ( type == 'chlist' ) {
+
+			return parseChlist( buffer, offset, size );
+
+		} else if ( type == 'chromaticities' ) {
+
+			return parseChromaticities( buffer, offset );
+
+		} else if ( type == 'compression' ) {
+
+			return parseCompression( buffer, offset );
+
+		} else if ( type == 'box2i' ) {
+
+			return parseBox2i( buffer, offset );
+
+		} else if ( type == 'lineOrder' ) {
+
+			return parseLineOrder( buffer, offset );
+
+		} else if ( type == 'float' ) {
+
+			return parseFloat32( buffer, offset );
+
+		} else if ( type == 'v2f' ) {
+
+			return parseV2f( buffer, offset );
+
+		} else {
+
+			throw 'Cannot parse value for unsupported type: ' + type;
+
+		}
+
+	}
+
+	var EXRHeader = {};
+
+	var magic = new DataView( buffer ).getUint32( 0, true );
+	var versionByteZero = new DataView( buffer ).getUint8( 4, true );
+	var fullMask = new DataView( buffer ).getUint8( 5, true );
+
+	// start of header
+
+	var offset = { value: 8 }; // start at 8, after magic stuff
+
+	var keepReading = true;
+
+	while ( keepReading ) {
+
+		var attributeName = parseNullTerminatedString( buffer, offset );
+
+		if ( attributeName == 0 ) {
+
+			keepReading = false;
+
+		} else {
+
+			var attributeType = parseNullTerminatedString( buffer, offset );
+			var attributeSize = parseUint32( buffer, offset );
+			var attributeValue = parseValue( buffer, offset, attributeType, attributeSize );
+
+			EXRHeader[ attributeName ] = attributeValue;
+
+		}
+
+	}
+
+	// offsets
+
+	var dataWindowHeight = EXRHeader.dataWindow.yMax + 1;
+	var scanlineBlockSize = 1; // 1 for no compression, 32 for PIZ
+	var numBlocks = dataWindowHeight / scanlineBlockSize;
+
+	for ( var i = 0; i < numBlocks; i ++ ) {
+
+		var scanlineOffset = parseUlong( buffer, offset );
+
+	}
+
+	// we should be passed the scanline offset table, start reading pixel data
+
+	var width = EXRHeader.dataWindow.xMax - EXRHeader.dataWindow.xMin + 1;
+	var height = EXRHeader.dataWindow.yMax - EXRHeader.dataWindow.yMin + 1;
+	var numChannels = EXRHeader.channels.length;
+
+	var byteArray = new Float32Array( width * height * numChannels );
+
+	var channelOffsets = {
+		R: 0,
+		G: 1,
+		B: 2,
+		A: 3
+	};
+
+	for ( var y = 0; y < height; y ++ ) {
+
+		var y_scanline = parseUint32( buffer, offset );
+		var dataSize = parseUint32( buffer, offset );
+
+		for ( var channelID = 0; channelID < EXRHeader.channels.length; channelID ++ ) {
+			if ( EXRHeader.channels[ channelID ].pixelType == 1 ) {
+			 // HALF
+				for ( var x = 0; x < width; x ++ ) {
+
+					var val = parseFloat16( buffer, offset );
+					var cOff = channelOffsets[ EXRHeader.channels[ channelID ].name ];
+
+					byteArray[ ( ( ( width - y_scanline ) * ( height * numChannels ) ) + ( x * numChannels ) ) + cOff ] = val;
+
+				}
+
+			} else {
+
+				throw 'Only supported pixel format is HALF';
+
+			}
+
+		}
+
+	}
+
+	return {
+		header: EXRHeader,
+		width: width,
+		height: height,
+		data: byteArray,
+		format: THREE.RGBAFormat,
+		type: THREE.FloatType
+	};
+
+};

BIN
examples/textures/uncompressed.exr


+ 194 - 0
examples/webgl_materials_texture_exr.html

@@ -0,0 +1,194 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - materials - EXR texture loader</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				color: #fff;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+				font-weight: bold;
+
+				background-color: #000;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				color:#fff;
+				position: absolute;
+				top: 0px; width: 100%;
+				padding: 5px;
+
+			}
+
+			a { color: red; }
+
+		</style>
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info">
+			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl EXR texture loader example
+		</div>
+
+		<script src="../build/three.js"></script>
+		<script src="js/loaders/EXRLoader.js"></script>
+
+		<script src="js/libs/dat.gui.min.js"></script>
+
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+
+		<!-- HDR fragment shader -->
+
+		<script id="fs-hdr" type="x-shader/x-fragment">
+
+			uniform sampler2D   tDiffuse;
+			uniform float       exposure;
+			uniform float       brightMax;
+
+			varying vec2  vUv;
+
+			void main()	{
+
+				vec4 color = texture2D( tDiffuse, vUv );
+
+				// Perform tone-mapping
+				float Y = dot(vec4(0.30, 0.59, 0.11, 0.0), color);
+				float YD = exposure * (exposure / brightMax + 1.0) / (exposure + 1.0);
+				color *= YD;
+
+				gl_FragColor = vec4( color.xyz, 1.0 );
+
+			}
+
+		</script>
+
+		<!-- HDR vertex shader -->
+
+		<script id="vs-hdr" type="x-shader/x-vertex">
+
+			varying vec2 vUv;
+
+			void main()	{
+
+				vUv  = uv;
+				gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+
+			}
+
+		</script>
+
+
+		<script>
+
+			var params = {
+				exposure: 1.0,
+			};
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			var container, stats;
+
+			var camera, scene, renderer;
+			var materialHDR, quad, gamma, exposure;
+
+			init();
+
+			function init() {
+
+				container = document.getElementById( 'container' );
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 10000 );
+				camera.position.z = 900;
+
+				scene = new THREE.Scene();
+
+				var loader = new THREE.EXRLoader();
+
+				var texture = loader.load( "textures/uncompressed.exr", function( texture, textureData ){
+					console.log( textureData.header ); // exr header
+
+					texture.minFilter = THREE.NearestFilter;
+					texture.magFilter = THREE.NearestFilter;
+
+					materialHDR = new THREE.ShaderMaterial( {
+
+						uniforms: {
+							tDiffuse:  { value: texture },
+							exposure:  { value: 1.0 },
+							brightMax: { value: 18.0 }
+							},
+						vertexShader: getText( 'vs-hdr' ),
+						fragmentShader: getText( 'fs-hdr' )
+
+					} );
+
+					quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( textureData.width, textureData.height ), materialHDR );
+					quad.position.z = -100;
+					scene.add( quad );
+					animate();
+				} );
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.gammaOutput = true;
+				container.appendChild( renderer.domElement );
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+				var gui = new dat.GUI();
+
+				gui.add( params, 'exposure', 0.1, 5 );
+				gui.open();
+
+			}
+
+
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function getText( id ) {
+
+				return document.getElementById( id ).textContent;
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				materialHDR.uniforms.exposure.value = params.exposure;
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>