Browse Source

RaytracerRenderer: @alteredq's improvements ^^

Mr.doob 11 years ago
parent
commit
e88fcee431
1 changed files with 365 additions and 59 deletions
  1. 365 59
      examples/js/renderers/RaytracingRenderer.js

+ 365 - 59
examples/js/renderers/RaytracingRenderer.js

@@ -13,6 +13,8 @@ THREE.RaytracingRenderer = function ( parameters ) {
 		alpha: parameters.alpha === true
 	} );
 
+	var maxRecursionDepth = 3;
+
 	var canvasWidth, canvasHeight;
 	var canvasWidthHalf, canvasHeightHalf;
 
@@ -21,6 +23,8 @@ THREE.RaytracingRenderer = function ( parameters ) {
 	var origin = new THREE.Vector3();
 	var direction = new THREE.Vector3();
 
+	var cameraPosition = new THREE.Vector3();
+
 	var raycaster = new THREE.Raycaster( origin, direction );
 	var raycasterLight = new THREE.Raycaster();
 
@@ -63,102 +67,379 @@ THREE.RaytracingRenderer = function ( parameters ) {
 
 	};
 
-	var renderBlock = ( function () {
+	//
 
-		var blockSize = 32;
+	var spawnRay = ( function () {
 
-		var canvasBlock = document.createElement( 'canvas' );
-		canvasBlock.width = blockSize;
-		canvasBlock.height = blockSize;
+		var diffuseColor = new THREE.Color();
+		var specularColor = new THREE.Color();
+		var lightColor = new THREE.Color();
+		var schlick = new THREE.Color();
 
-		var contextBlock = canvasBlock.getContext( '2d', {
-			alpha: parameters.alpha === true
-		} );
+		var lightContribution = new THREE.Color();
 
-		var imagedata = contextBlock.getImageData( 0, 0, blockSize, blockSize );
-		var data = imagedata.data;
+		var eyeVector = new THREE.Vector3();
+		var lightVector = new THREE.Vector3();
+		var normalVector = new THREE.Vector3();
+		var halfVector = new THREE.Vector3();
 
-		var color = new THREE.Color();
-		var lightColor = new THREE.Color();
+		var localPoint = new THREE.Vector3();
+		var reflectionVector = new THREE.Vector3();
 
-		return function ( blockX, blockY ) {
+		var tmpVec = new THREE.Vector3();
 
-			var index = 0;
+		var tmpColor = [];
 
-			for ( var y = 0; y < blockSize; y ++ ) {
+		for ( var i = 0; i < maxRecursionDepth; i ++ ) {
 
-				for ( var x = 0; x < blockSize; x ++, index += 4 ) {
+			tmpColor[ i ] = new THREE.Color();
 
-					direction.set( x + blockX - canvasWidthHalf, - ( y + blockY - canvasHeightHalf ), - perspective );
-					direction.applyMatrix3( cameraNormalMatrix ).normalize();
+		}
+
+		return function ( rayOrigin, rayDirection, outputColor, recursionDepth ) {
+
+			var ray = raycaster.ray;
+
+			ray.origin = rayOrigin;
+			ray.direction = rayDirection;
+
+			//
+
+			var rayLight = raycasterLight.ray;
+
+			//
+
+			outputColor.setRGB( 0, 0, 0 );
+
+			//
+
+			var intersections = raycaster.intersectObjects( objects, true );
+
+			// ray didn't find anything
+			// (here should come setting of background color?)
+
+			if ( intersections.length === 0 ) {
+
+				return;
+
+			}
+
+			// ray hit
+
+			var intersection = intersections[ 0 ];
+
+			var point = intersection.point;
+			var object = intersection.object;
+			var material = object.material;
+			var face = intersection.face;
+
+			var vertices = object.geometry.vertices;
+
+			//
+
+			localPoint.copy( point ).applyMatrix4( object._inverseMatrix );
+			eyeVector.subVectors( raycaster.ray.origin, point ).normalize();
+
+			// resolve pixel diffuse color
+
+			if ( material instanceof THREE.MeshLambertMaterial ||
+				 material instanceof THREE.MeshPhongMaterial ||
+				 material instanceof THREE.MeshBasicMaterial ) {
+
+				diffuseColor.copyGammaToLinear( material.color );
+
+			} else {
+
+				diffuseColor.setRGB( 1, 1, 1 );
+
+			}
+
+			if ( material.vertexColors === THREE.FaceColors ) {
+
+				diffuseColor.multiply( face.color );
+
+			}
+
+			// compute light shading
+
+			rayLight.origin.copy( point );
+
+			if ( material instanceof THREE.MeshBasicMaterial ) {
+
+				for ( var i = 0, l = lights.length; i < l; i ++ ) {
+
+					var light = lights[ i ];
+
+					lightVector.setFromMatrixPosition( light.matrixWorld );
+					lightVector.sub( point );
+
+					rayLight.direction.copy( lightVector ).normalize();
+
+					var intersections = raycasterLight.intersectObjects( objects, true );
+
+					// point in shadow
+
+					if ( intersections.length > 0 ) continue;
+
+					// point visible
+
+					outputColor.add( diffuseColor );
+
+				}
+
+			} else if ( material instanceof THREE.MeshLambertMaterial ||
+						material instanceof THREE.MeshPhongMaterial ) {
+
+				var normalComputed = false;
+
+				for ( var i = 0, l = lights.length; i < l; i ++ ) {
+
+					var light = lights[ i ];
+
+					lightColor.copyGammaToLinear( light.color );
+
+					lightVector.setFromMatrixPosition( light.matrixWorld );
+					lightVector.sub( point );
+
+					rayLight.direction.copy( lightVector ).normalize();
+
+					var intersections = raycasterLight.intersectObjects( objects, true );
+
+					// point in shadow
+
+					if ( intersections.length > 0 ) continue;
+
+					// point lit
+
+					if ( ! normalComputed ) {
+
+						// the same normal can be reused for all lights
+						// (should be possible to cache even more)
+
+						computePixelNormal( normalVector, localPoint, material.shading, face, vertices );
+						normalVector.applyMatrix3( object._normalMatrix ).normalize();
+
+						normalComputed = true;
+
+					}
+
+					// compute attenuation
+
+					var attenuation = 1.0;
+
+					if ( light.physicalAttenuation ) {
+
+						attenuation = lightVector.length();
+						attenuation = 1.0 / ( attenuation * attenuation );
+
+					}
+
+					lightVector.normalize();
+
+					// compute diffuse
+
+					var dot = Math.max( normalVector.dot( lightVector ), 0 );
+					var diffuseIntensity = dot * light.intensity;
+
+					lightContribution.copy( diffuseColor );
+					lightContribution.multiply( lightColor );
+					lightContribution.multiplyScalar( diffuseIntensity * attenuation );
 
-					var intersections = raycaster.intersectObjects( objects, true );
+					outputColor.add( lightContribution );
 
-					if ( intersections.length > 0 ) {
+					// compute specular
 
-						var intersection = intersections[ 0 ];
+					if ( material instanceof THREE.MeshPhongMaterial ) {
 
-						var point = intersection.point;
-						var object = intersection.object;
-						var material = object.material;
-						var face = intersection.face;
+						halfVector.addVectors( lightVector, eyeVector ).normalize();
 
-						if ( material.vertexColors === THREE.NoColors ) {
+						var dotNormalHalf = Math.max( normalVector.dot( halfVector ), 0.0 );
+						var specularIntensity = Math.max( Math.pow( dotNormalHalf, material.shininess ), 0.0 ) * diffuseIntensity;
 
-							color.copy( material.color );
+						var specularNormalization = ( material.shininess + 2.0 ) / 8.0;
 
-						} else if ( material.vertexColors === THREE.FaceColors ) {
+						specularColor.copyGammaToLinear( material.specular );
 
-							color.copy( face.color );
+						var alpha = Math.pow( Math.max( 1.0 - lightVector.dot( halfVector ), 0.0 ), 5.0 );
 
-						}
+						schlick.r = specularColor.r + ( 1.0 - specularColor.r ) * alpha;
+						schlick.g = specularColor.g + ( 1.0 - specularColor.g ) * alpha;
+						schlick.b = specularColor.b + ( 1.0 - specularColor.b ) * alpha;
 
-						if ( material instanceof THREE.MeshLambertMaterial ||
-							 material instanceof THREE.MeshPhongMaterial ) {
+						lightContribution.copy( schlick );
 
-							lightColor.set( 0, 0, 0 );
+						lightContribution.multiply( lightColor );
+						lightContribution.multiplyScalar( specularNormalization * specularIntensity * attenuation );
+						outputColor.add( lightContribution );
 
-							raycasterLight.ray.origin.copy( point );
+					}
 
-							for ( var i = 0, l = lights.length; i < l; i ++ ) {
+				}
 
-								var light = lights[ i ];
+			}
 
-								raycasterLight.ray.direction.copy( light.position ).sub( point ).normalize();
+			// reflection / refraction
 
-								var intersections = raycasterLight.intersectObjects( objects, true );
+			var reflectivity = material.reflectivity;
 
-								if ( intersections.length === 0 ) {
+			if ( ( material.mirror || material.glass ) && reflectivity > 0 && recursionDepth < maxRecursionDepth ) {
 
-									var dot = Math.max( face.normal.dot( raycasterLight.ray.direction ), 0 );
-									var intensity = dot * light.intensity;
+				if ( material.mirror ) {
 
-									lightColor.r += light.color.r * intensity;
-									lightColor.g += light.color.g * intensity;
-									lightColor.b += light.color.b * intensity;
+					reflectionVector.copy( rayDirection );
+					reflectionVector.reflect( normalVector );
 
-								}
+				} else if ( material.glass ) {
 
-							}
+					var eta = material.refractionRatio;
 
-							color.multiply( lightColor );
+					var dotNI = rayDirection.dot( normalVector )
+					var k = 1.0 - eta * eta * ( 1.0 - dotNI * dotNI );
 
-						}
+					if ( k < 0.0 ) {
 
-						data[ index ] = color.r * 255;
-						data[ index + 1 ] = color.g * 255;
-						data[ index + 2 ] = color.b * 255;
+						reflectionVector.set( 0, 0, 0 );
 
 					} else {
 
-						data[ index ] = 0;
-						data[ index + 1 ] = 0;
-						data[ index + 2 ] = 0;
+						reflectionVector.copy( rayDirection );
+						reflectionVector.multiplyScalar( eta );
+
+						var alpha = eta * dotNI + Math.sqrt( k );
+						tmpVec.copy( normalVector );
+						tmpVec.multiplyScalar( alpha );
+						reflectionVector.sub( tmpVec );
 
 					}
 
 				}
 
+				var theta = Math.max( eyeVector.dot( normalVector ), 0.0 );
+				var rf0 = reflectivity;
+				var fresnel = rf0 + ( 1.0 - rf0 ) * Math.pow( ( 1.0 - theta ), 5.0 );
+
+				var weight = fresnel;
+
+				var zColor = tmpColor[ recursionDepth ];
+
+				spawnRay( point, reflectionVector, zColor, recursionDepth + 1 );
+
+				if ( material.specular !== undefined ) {
+
+					zColor.multiply( material.specular );
+
+				}
+
+				zColor.multiplyScalar( weight );
+				outputColor.multiplyScalar( 1 - weight );
+				outputColor.add( zColor );
+
+			}
+
+		};
+
+	}() );
+
+	var computePixelNormal = ( function () {
+
+		var tmpVec1 = new THREE.Vector3();
+		var tmpVec2 = new THREE.Vector3();
+		var tmpVec3 = new THREE.Vector3();
+
+		return function ( outputVector, point, shading, face, vertices ) {
+
+			var faceNormal = face.normal;
+			var vertexNormals = face.vertexNormals;
+
+			if ( shading === THREE.FlatShading ) {
+
+				outputVector.copy( faceNormal );
+
+			} else if ( shading === THREE.SmoothShading ) {
+
+				// compute barycentric coordinates
+
+				var vA = vertices[ face.a ];
+				var vB = vertices[ face.b ];
+				var vC = vertices[ face.c ];
+
+				tmpVec3.crossVectors( tmpVec1.subVectors( vB, vA ), tmpVec2.subVectors( vC, vA ) );
+				var areaABC = faceNormal.dot( tmpVec3 );
+
+				tmpVec3.crossVectors( tmpVec1.subVectors( vB, point ), tmpVec2.subVectors( vC, point ) );
+				var areaPBC = faceNormal.dot( tmpVec3 );
+				var a = areaPBC / areaABC;
+
+				tmpVec3.crossVectors( tmpVec1.subVectors( vC, point ), tmpVec2.subVectors( vA, point ) );
+				var areaPCA = faceNormal.dot( tmpVec3 );
+				var b = areaPCA / areaABC;
+
+				var c = 1.0 - a - b;
+
+				// compute interpolated vertex normal
+
+				tmpVec1.copy( vertexNormals[ 0 ] );
+				tmpVec1.multiplyScalar( a );
+
+				tmpVec2.copy( vertexNormals[ 1 ] );
+				tmpVec2.multiplyScalar( b );
+
+				tmpVec3.copy( vertexNormals[ 2 ] );
+				tmpVec3.multiplyScalar( c );
+
+				outputVector.addVectors( tmpVec1, tmpVec2 );
+				outputVector.add( tmpVec3 );
+
+			}
+
+		};
+
+	}() );
+
+	var renderBlock = ( function () {
+
+		var blockSize = 64;
+
+		var canvasBlock = document.createElement( 'canvas' );
+		canvasBlock.width = blockSize;
+		canvasBlock.height = blockSize;
+
+		var contextBlock = canvasBlock.getContext( '2d', {
+
+			alpha: parameters.alpha === true
+
+		} );
+
+		var imagedata = contextBlock.getImageData( 0, 0, blockSize, blockSize );
+		var data = imagedata.data;
+
+		var pixelColor = new THREE.Color();
+
+		return function ( blockX, blockY ) {
+
+			var index = 0;
+
+			for ( var y = 0; y < blockSize; y ++ ) {
+
+				for ( var x = 0; x < blockSize; x ++, index += 4 ) {
+
+					// spawn primary ray at pixel position
+
+					origin.copy( cameraPosition );
+
+					direction.set( x + blockX - canvasWidthHalf, - ( y + blockY - canvasHeightHalf ), - perspective );
+					direction.applyMatrix3( cameraNormalMatrix ).normalize();
+
+					spawnRay( origin, direction, pixelColor, 0 );
+
+					// convert from linear to gamma
+
+					data[ index ]     = Math.sqrt( pixelColor.r ) * 255;
+					data[ index + 1 ] = Math.sqrt( pixelColor.g ) * 255;
+					data[ index + 2 ] = Math.sqrt( pixelColor.b ) * 255;
+
+				}
+
 			}
 
 			context.putImageData( imagedata, blockX, blockY );
@@ -192,14 +473,27 @@ THREE.RaytracingRenderer = function ( parameters ) {
 
 		cancelAnimationFrame( animationFrameId );
 
+		// update scene graph
+
+		if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+
+		// update camera matrices
+
+		if ( camera.parent === undefined ) camera.updateMatrixWorld();
+
+		camera.matrixWorldInverse.getInverse( camera.matrixWorld );
+		cameraPosition.setFromMatrixPosition( camera.matrixWorld );
+
+		//
+
 		cameraNormalMatrix.getNormalMatrix( camera.matrixWorld );
-		origin.copy( camera.position );
+		origin.copy( cameraPosition );
 
 		perspective = 0.5 / Math.tan( THREE.Math.degToRad( camera.fov * 0.5 ) ) * canvasHeight;
 
 		objects = scene.children;
 
-		// collect lights
+		// collect lights and set up object matrices
 
 		lights.length = 0;
 
@@ -211,10 +505,22 @@ THREE.RaytracingRenderer = function ( parameters ) {
 
 			}
 
-		} )
+			if ( object._modelViewMatrix === undefined ) {
+
+				object._modelViewMatrix = new THREE.Matrix4();
+				object._normalMatrix = new THREE.Matrix3();
+				object._inverseMatrix = new THREE.Matrix4();
+
+			}
+
+			object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
+			object._normalMatrix.getNormalMatrix( object._modelViewMatrix );
+			object._inverseMatrix.getInverse( object.matrixWorld );
+
+		} );
 
 		renderBlock( 0, 0 );
 
-	}
+	};
 
-};
+};