Browse Source

Merge pull request #8250 from bhouston/inline_tonemapping

Inline tonemapping: None, Linear, Reinhard and Uncharted2
Mr.doob 9 years ago
parent
commit
de5c4a9aa4

+ 1 - 0
examples/files.js

@@ -206,6 +206,7 @@ var files = {
 		"webgl_terrain_dynamic",
 		"webgl_test_memory",
 		"webgl_test_memory2",
+		"webgl_tonemapping",
 		"webgl_trails",
 		"webgl_video_panorama_equirectangular"
 	],

BIN
examples/textures/brick_roughness.jpg


+ 298 - 0
examples/webgl_tonemapping.html

@@ -0,0 +1,298 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>threejs webgl - materials</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;
+
+				background-color: #000;
+
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				position: absolute;
+				top: 0px; width: 100%;
+				padding: 5px;
+			}
+		</style>
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info"><a href="http://threejs.org" target="_blank">threejs</a> - Inline Tone Mapping (within a Material's fragment shader) without using a pre-processing step or float/half buffers by <a href="http://clara.io/" target="_blank">Ben Houston</a>.</div>
+
+		<script src="../build/three.min.js"></script>
+		<script src="../examples/js/controls/OrbitControls.js"></script>
+		<script src="../src/loaders/BinaryTextureLoader.js"></script>
+
+		<script src="../examples/js/Detector.js"></script>
+		<script src="../examples/js/libs/stats.min.js"></script>
+
+		<script src="../examples/js/libs/dat.gui.min.js"></script>
+		<script src="../src/loaders/BinaryTextureLoader.js"></script>
+		<script src="../examples/js/loaders/RGBELoader.js"></script>
+		<script src="../examples/js/loaders/HDRCubeMapLoader.js"></script>
+		<script src="../examples/js/Half.js"></script>
+		<script src="../examples/js/Encodings.js"></script>
+		<script src="../examples/js/pmrem/PMREMGenerator.js"></script>
+		<script src="../examples/js/pmrem/PMREMCubeUVPacker.js"></script>
+
+		<script src="../examples/js/postprocessing/EffectComposer.js"></script>
+		<script src="../examples/js/postprocessing/RenderPass.js"></script>
+		<script src="../examples/js/postprocessing/MaskPass.js"></script>
+		<script src="../examples/js/postprocessing/ShaderPass.js"></script>
+		<script src="../examples/js/shaders/CopyShader.js"></script>
+		<script src="../examples/js/shaders/FXAAShader.js"></script>
+		<script src="../examples/js/postprocessing/BloomPass.js"></script>
+		<script src="../examples/js/shaders/ConvolutionShader.js"></script>
+
+		<script>
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			var container, stats;
+			var params = {
+				opacity: 1.0,
+				roughness: 1.0,
+				bumpScale: 1.0,
+				exposure: 3.0,
+				whitePoint: 1.0,
+				toneMapping: "Cineon",
+				renderMode: "Renderer"
+			};
+
+			var toneMappingOptions = {
+				None: THREE.NoToneMapping,
+				Linear: THREE.LinearToneMapping,
+				Reinhard: THREE.ReinhardToneMapping,
+				Uncharted2: THREE.Uncharted2ToneMapping,
+				Cineon: THREE.CineonToneMapping
+			};
+
+			var camera, scene, renderer, controls, objects = [];
+			var composer;
+			var standardMaterial, standardMaterialPremultiplied, floorMaterial;
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 2000 );
+				camera.position.set( 0.0, 40, 40 * 3.5 );
+
+				scene = new THREE.Scene();
+
+				renderer = new THREE.WebGLRenderer( { antialias: false } );
+
+				standardMaterial = new THREE.MeshStandardMaterial( {
+					bumpScale: - 0.05,
+					color: 0xffffff,
+					metalness: 0.9,
+					roughness: 0.8,
+					shading: THREE.SmoothShading,
+					transparent: true,
+					blending: THREE.PremultipliedAlphaBlending
+				} );
+
+				var textureLoader = new THREE.TextureLoader();
+				textureLoader.load( "../examples/textures/brick_diffuse.jpg", function( map ) {
+					map.wrapS = THREE.RepeatWrapping;
+					map.wrapT = THREE.RepeatWrapping;
+					map.anisotropy = 4;
+					map.repeat.set( 9, 0.5 );
+					standardMaterial.map = map;
+					standardMaterial.needsUpdate = true;
+				} );
+				textureLoader.load( "../examples/textures/brick_bump.jpg", function( map ) {
+					map.wrapS = THREE.RepeatWrapping;
+					map.wrapT = THREE.RepeatWrapping;
+					map.anisotropy = 4;
+					map.repeat.set( 9, 0.5 );
+					standardMaterial.bumpMap = map;
+					standardMaterial.needsUpdate = true;
+				} );
+				textureLoader.load( "../examples/textures/brick_roughness.jpg", function( map ) {
+					map.wrapS = THREE.RepeatWrapping;
+					map.wrapT = THREE.RepeatWrapping;
+					map.anisotropy = 4;
+					map.repeat.set( 9, 0.5 );
+					standardMaterial.roughnessMap = map;
+					standardMaterial.needsUpdate = true;
+				} );
+
+				var geometry = new THREE.TorusKnotGeometry( 18, 8, 150, 20 );;
+				var torusMesh1 = new THREE.Mesh( geometry, standardMaterial );
+				torusMesh1.position.x = 0.0;
+				torusMesh1.castShadow = true;
+				torusMesh1.receiveShadow = true;
+				scene.add( torusMesh1 );
+				objects.push( torusMesh1 );
+
+				floorMaterial = new THREE.MeshStandardMaterial( {
+					map: null,
+					roughnessMap: null,
+					color: 0xffffff,
+					metalness: 0.0,
+					roughness: 0.0,
+					shading: THREE.SmoothShading
+				} );
+
+				var planeGeometry = new THREE.PlaneBufferGeometry( 200, 200 );
+				var planeMesh1 = new THREE.Mesh( planeGeometry, floorMaterial );
+				planeMesh1.position.y = - 50;
+				planeMesh1.rotation.x = - Math.PI * 0.5;
+				planeMesh1.receiveShadow = true;
+				scene.add( planeMesh1 );
+
+				// Materials
+				var hdrpath = "../examples/textures/cube/hdrPisa/";
+				var hdrformat = '.hdr';
+				var hdrurls = [
+					hdrpath + 'px' + hdrformat, hdrpath + 'nx' + hdrformat,
+					hdrpath + 'py' + hdrformat, hdrpath + 'ny' + hdrformat,
+					hdrpath + 'pz' + hdrformat, hdrpath + 'nz' + hdrformat
+				];
+
+				var hdrCubeMap = new THREE.HDRCubeMapLoader().load( THREE.UnsignedByteType, hdrurls, function ( hdrCubeMap ) {
+
+					var pmremGenerator = new THREE.PMREMGenerator( hdrCubeMap );
+					pmremGenerator.update( renderer );
+
+					var pmremCubeUVPacker = new THREE.PMREMCubeUVPacker( pmremGenerator.cubeLods );
+					pmremCubeUVPacker.update( renderer );
+
+					standardMaterial.envMap = pmremCubeUVPacker.CubeUVRenderTarget;
+					standardMaterial.needsUpdate = true;
+
+				} );
+
+				// Lights
+
+				scene.add( new THREE.AmbientLight( 0x222222 ) );
+
+				var spotLight = new THREE.SpotLight( 0xffffff );
+				spotLight.position.set( 50, 100, 50 );
+				spotLight.angle = Math.PI / 7;
+				spotLight.penumbra = 0.8
+				spotLight.castShadow = true;
+				scene.add( spotLight );
+
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.shadowMap.enabled = true;
+				container.appendChild( renderer.domElement );
+
+				renderer.gammaInput = true;
+				renderer.gammaOutput = true;
+
+				composer = new THREE.EffectComposer( renderer );
+				composer.setSize( window.innerWidth, window.innerHeight );
+
+				var renderScene = new THREE.RenderPass( scene, camera );
+				composer.addPass( renderScene );
+
+				var copyPass = new THREE.ShaderPass( THREE.CopyShader );
+				copyPass.renderToScreen = true;
+				composer.addPass( copyPass );
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+
+				container.appendChild( stats.domElement );
+
+				controls = new THREE.OrbitControls( camera, renderer.domElement );
+				controls.target.set( 0, 0, 0 );
+				controls.update();
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+				var gui = new dat.GUI();
+
+				gui.add( params, 'toneMapping', Object.keys( toneMappingOptions ) );
+				gui.add( params, 'exposure', 0, 10 );
+				gui.add( params, 'whitePoint', 0, 10 );
+				gui.add( params, 'opacity', 0, 1 );
+				gui.add( params, 'renderMode', [ 'Renderer', 'Composer'] );
+				gui.open();
+
+			}
+
+			function onWindowResize() {
+
+				var width = window.innerWidth;
+				var height = window.innerHeight;
+
+				camera.aspect = width / height;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( width, height );
+				composer.setSize( width, height );
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				if ( standardMaterial !== undefined ) {
+
+
+					standardMaterial.roughness = params.roughness;
+					standardMaterial.bumpScale = - 0.05 * params.bumpScale;
+					standardMaterial.opacity = params.opacity;
+
+				}
+				if( renderer.toneMapping !== toneMappingOptions[ params.toneMapping ] ) {
+					renderer.toneMapping = toneMappingOptions[ params.toneMapping ];
+					if( standardMaterial ) standardMaterial.needsUpdate = true;
+					if( floorMaterial ) floorMaterial.needsUpdate = true;
+				}
+				renderer.toneMappingExposure = params.exposure;
+				renderer.toneMappingWhitePoint = params.whitePoint;
+
+				var timer = Date.now() * 0.00025;
+
+				camera.lookAt( scene.position );
+
+				for ( var i = 0, l = objects.length; i < l; i ++ ) {
+
+					var object = objects[ i ];
+					object.rotation.y += 0.005;
+
+				}
+
+				if( params.renderMode === "Composer" ) {
+					composer.render();
+				}
+				else {
+					renderer.render( scene, camera );
+				}
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 8 - 0
src/Three.js

@@ -209,6 +209,14 @@ THREE.MultiplyOperation = 0;
 THREE.MixOperation = 1;
 THREE.AddOperation = 2;
 
+// Tone Mapping modes
+
+THREE.NoToneMapping = 0; // do not do any tone mapping, not even exposure (required for special purpose passes.)
+THREE.LinearToneMapping = 1; // only apply exposure.
+THREE.ReinhardToneMapping = 2;
+THREE.Uncharted2ToneMapping = 3; // John Hable
+THREE.CineonToneMapping = 4;  // optimized filmic operator by Jim Hejl and Richard Burgess-Dawson
+
 // Mapping modes
 
 THREE.UVMapping = 300;

+ 18 - 0
src/renderers/WebGLRenderer.js

@@ -55,6 +55,12 @@ THREE.WebGLRenderer = function ( parameters ) {
 	this.gammaInput = false;
 	this.gammaOutput = false;
 
+	// tone mapping
+
+	this.toneMapping = THREE.LinearToneMapping;
+	this.toneMappingExposure = 1.0;
+	this.toneMappingWhitePoint = 1.0;
+
 	// morphs
 
 	this.maxMorphTargets = 8;
@@ -1696,6 +1702,18 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 			}
 
+
+			if ( p_uniforms.toneMappingExposure !== undefined ) {
+
+				_gl.uniform1f( p_uniforms.toneMappingExposure, _this.toneMappingExposure );
+
+			}
+
+			if ( p_uniforms.toneMappingWhitePoint !== undefined ) {
+
+				_gl.uniform1f( p_uniforms.toneMappingWhitePoint, _this.toneMappingWhitePoint );
+
+			}
 		}
 
 		// skinning uniforms must be set even if material didn't change

+ 1 - 0
src/renderers/shaders/ShaderChunk/encodings_fragment.glsl

@@ -0,0 +1 @@
+  gl_FragColor = linearToOutputTexel( gl_FragColor );

+ 0 - 0
src/renderers/shaders/ShaderChunk/encodings.glsl → src/renderers/shaders/ShaderChunk/encodings_pars_fragment.glsl


+ 5 - 0
src/renderers/shaders/ShaderChunk/tonemapping_fragment.glsl

@@ -0,0 +1,5 @@
+#if defined( TONE_MAPPING )
+
+  gl_FragColor.rgb = toneMapping( gl_FragColor.rgb );
+
+#endif

+ 39 - 0
src/renderers/shaders/ShaderChunk/tonemapping_pars_fragment.glsl

@@ -0,0 +1,39 @@
+#define saturate(a) clamp( a, 0.0, 1.0 )
+
+uniform float toneMappingExposure;
+uniform float toneMappingWhitePoint;
+
+// exposure only
+vec3 LinearToneMapping( vec3 color ) {
+
+  return toneMappingExposure * color;
+
+}
+
+// source: https://www.cs.utah.edu/~reinhard/cdrom/
+vec3 ReinhardToneMapping( vec3 color ) {
+
+  color *= toneMappingExposure;
+  return saturate( color / ( vec3( 1.0 ) + color ) );
+
+}
+
+// source: http://filmicgames.com/archives/75
+#define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) )
+vec3 Uncharted2ToneMapping( vec3 color ) {
+
+  // John Hable's filmic operator from Uncharted 2 video game
+  color *= toneMappingExposure;
+  return saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( toneMappingWhitePoint ) ) );
+
+}
+
+// source: http://filmicgames.com/archives/75
+vec3 OptimizedCineonToneMapping( vec3 color ) {
+
+  // optimized filmic operator by Jim Hejl and Richard Burgess-Dawson
+  color *= toneMappingExposure;
+  color = max( vec3( 0.0 ), color - 0.004 );
+  return pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );
+
+}

+ 4 - 2
src/renderers/shaders/ShaderLib/linedashed_frag.glsl

@@ -27,9 +27,11 @@ void main() {
 
 	outgoingLight = diffuseColor.rgb; // simple shader
 
-	gl_FragColor = linearToOutputTexel( vec4( outgoingLight, diffuseColor.a ) );
+	gl_FragColor = vec4( outgoingLight, diffuseColor.a );
 
-	#include <fog_fragment>
 	#include <premultiplied_alpha_fragment>
+	#include <tonemapping_fragment>
+	#include <encodings_fragment>
+	#include <fog_fragment>
 
 }

+ 4 - 2
src/renderers/shaders/ShaderLib/meshbasic_frag.glsl

@@ -42,9 +42,11 @@ void main() {
 
 	#include <envmap_fragment>
 
-	gl_FragColor = linearToOutputTexel( vec4( outgoingLight, diffuseColor.a ) );
+	gl_FragColor = vec4( outgoingLight, diffuseColor.a );
 
-	#include <fog_fragment>
 	#include <premultiplied_alpha_fragment>
+	#include <tonemapping_fragment>
+	#include <encodings_fragment>
+	#include <fog_fragment>
 
 }

+ 4 - 2
src/renderers/shaders/ShaderLib/meshlambert_frag.glsl

@@ -69,9 +69,11 @@ void main() {
 
 	#include <envmap_fragment>
 
-	gl_FragColor = linearToOutputTexel( vec4( outgoingLight, diffuseColor.a ) );
+	gl_FragColor = vec4( outgoingLight, diffuseColor.a );
 
-	#include <fog_fragment>
 	#include <premultiplied_alpha_fragment>
+	#include <tonemapping_fragment>
+	#include <encodings_fragment>
+	#include <fog_fragment>
 
 }

+ 4 - 2
src/renderers/shaders/ShaderLib/meshphong_frag.glsl

@@ -53,9 +53,11 @@ void main() {
 
 	#include <envmap_fragment>
 
-	gl_FragColor = linearToOutputTexel( vec4( outgoingLight, diffuseColor.a ) );
+	gl_FragColor = vec4( outgoingLight, diffuseColor.a );
 
-	#include <fog_fragment>
 	#include <premultiplied_alpha_fragment>
+	#include <tonemapping_fragment>
+	#include <encodings_fragment>
+	#include <fog_fragment>
 
 }

+ 4 - 2
src/renderers/shaders/ShaderLib/meshstandard_frag.glsl

@@ -65,9 +65,11 @@ void main() {
 
 	vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveLight;
 
-	gl_FragColor = linearToOutputTexel( vec4( outgoingLight, diffuseColor.a ) );
+	gl_FragColor = vec4( outgoingLight, diffuseColor.a );
 
-	#include <fog_fragment>
 	#include <premultiplied_alpha_fragment>
+	#include <tonemapping_fragment>
+	#include <encodings_fragment>
+	#include <fog_fragment>
 
 }

+ 4 - 2
src/renderers/shaders/ShaderLib/points_frag.glsl

@@ -20,9 +20,11 @@ void main() {
 
 	outgoingLight = diffuseColor.rgb;
 
-	gl_FragColor = linearToOutputTexel( vec4( outgoingLight, diffuseColor.a ) );
+	gl_FragColor = vec4( outgoingLight, diffuseColor.a );
 
+	#include <premultiplied_alpha_fragment>
+	#include <tonemapping_fragment>
+	#include <encodings_fragment>
 	#include <fog_fragment>
-	#include <premultiply_alpha_fragment>
 
 }

+ 29 - 1
src/renderers/webgl/WebGLProgram.js

@@ -46,6 +46,31 @@ THREE.WebGLProgram = ( function () {
 
 	}
 
+	function getToneMappingFunction( functionName, toneMapping ) {
+		var toneMappingName;
+
+		switch( toneMapping ) {
+			case THREE.LinearToneMapping:
+				toneMappingName = "Linear";
+				break;
+			case THREE.ReinhardToneMapping:
+				toneMappingName = "Reinhard";
+				break;
+			case THREE.Uncharted2ToneMapping:
+				toneMappingName = "Uncharted2";
+				break;
+			case THREE.CineonToneMapping:
+				toneMappingName = "OptimizedCineon";
+				break;
+			default:
+				throw new Error( 'unsupported toneMapping: ' + toneMapping );
+
+		}
+		return "vec3 " + functionName + "( vec3 color ) { return " + toneMappingName + "ToneMapping( color ); }";
+
+	}
+
+
 	function generateExtensions( extensions, parameters, rendererExtensions ) {
 
 		extensions = extensions || {};
@@ -501,8 +526,11 @@ THREE.WebGLProgram = ( function () {
 				'uniform mat4 viewMatrix;',
 				'uniform vec3 cameraPosition;',
 
-				( parameters.outputEncoding || parameters.mapEncoding || parameters.envMapEncoding || parameters.emissiveMapEncoding ) ? THREE.ShaderChunk[ 'encodings' ] : '',
+				( parameters.toneMapping !== THREE.NoToneMapping ) ? "#define TONE_MAPPING" : '',
+				( parameters.toneMapping !== THREE.NoToneMapping ) ? THREE.ShaderChunk[ 'tonemapping_pars_fragment' ] : '',  // this code is required here because it is used by the toneMapping() function defined below
+				( parameters.toneMapping !== THREE.NoToneMapping ) ? getToneMappingFunction( "toneMapping", parameters.toneMapping ) : '',
 
+				( parameters.outputEncoding || parameters.mapEncoding || parameters.envMapEncoding || parameters.emissiveMapEncoding ) ? THREE.ShaderChunk[ 'encodings_pars_fragment' ] : '', // this code is required here because it is used by the various encoding/decoding function defined below
 				parameters.mapEncoding ? getTexelDecodingFunction( 'mapTexelToLinear', parameters.mapEncoding ) : '',
 				parameters.envMapEncoding ? getTexelDecodingFunction( 'envMapTexelToLinear', parameters.envMapEncoding ) : '',
 				parameters.emissiveMapEncoding ? getTexelDecodingFunction( 'emissiveMapTexelToLinear', parameters.emissiveMapEncoding ) : '',

+ 3 - 1
src/renderers/webgl/WebGLPrograms.js

@@ -23,7 +23,7 @@ THREE.WebGLPrograms = function ( renderer, capabilities ) {
 		"maxBones", "useVertexTexture", "morphTargets", "morphNormals",
 		"maxMorphTargets", "maxMorphNormals", "premultipliedAlpha",
 		"numDirLights", "numPointLights", "numSpotLights", "numHemiLights",
-		"shadowMapEnabled", "pointLightShadows",
+		"shadowMapEnabled", "pointLightShadows", "toneMapping",
 		"shadowMapType",
 		"alphaTest", "doubleSided", "flipSided"
 	];
@@ -175,6 +175,8 @@ THREE.WebGLPrograms = function ( renderer, capabilities ) {
 			shadowMapEnabled: renderer.shadowMap.enabled && object.receiveShadow && lights.shadows.length > 0,
 			shadowMapType: renderer.shadowMap.type,
 
+			toneMapping: renderer.toneMapping,
+
 			premultipliedAlpha: ( material.blending === THREE.PremultipliedAlphaBlending ),
 			alphaTest: material.alphaTest,
 			doubleSided: material.side === THREE.DoubleSide,

+ 4 - 1
utils/build/includes/common.json

@@ -138,7 +138,8 @@
 	"src/renderers/shaders/ShaderChunk/displacementmap_pars_vertex.glsl",
 	"src/renderers/shaders/ShaderChunk/emissivemap_fragment.glsl",
 	"src/renderers/shaders/ShaderChunk/emissivemap_pars_fragment.glsl",
-	"src/renderers/shaders/ShaderChunk/encodings.glsl",
+	"src/renderers/shaders/ShaderChunk/encodings_pars_fragment.glsl",
+	"src/renderers/shaders/ShaderChunk/encodings_fragment.glsl",
 	"src/renderers/shaders/ShaderChunk/envmap_fragment.glsl",
 	"src/renderers/shaders/ShaderChunk/envmap_pars_fragment.glsl",
 	"src/renderers/shaders/ShaderChunk/envmap_pars_vertex.glsl",
@@ -185,6 +186,8 @@
 	"src/renderers/shaders/ShaderChunk/skinnormal_vertex.glsl",
 	"src/renderers/shaders/ShaderChunk/specularmap_fragment.glsl",
 	"src/renderers/shaders/ShaderChunk/specularmap_pars_fragment.glsl",
+	"src/renderers/shaders/ShaderChunk/tonemapping_fragment.glsl",
+	"src/renderers/shaders/ShaderChunk/tonemapping_pars_fragment.glsl",
 	"src/renderers/shaders/ShaderChunk/uv2_pars_fragment.glsl",
 	"src/renderers/shaders/ShaderChunk/uv2_pars_vertex.glsl",
 	"src/renderers/shaders/ShaderChunk/uv2_vertex.glsl",