diff --git a/examples/js/renderers/RaytracingRenderer.js b/examples/js/renderers/RaytracingRenderer.js index cbc7ad29eaea0c..3fb3d4e3871c4f 100644 --- a/examples/js/renderers/RaytracingRenderer.js +++ b/examples/js/renderers/RaytracingRenderer.js @@ -1,6 +1,9 @@ /** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ + * RaytracingRenderer renders by raytracing it's scene. However, it does not + * compute the pixels itself but it hands off and coordinates the taks for workers. + * The workers compute the pixel values and this renderer simply paints it to the Canvas. + * + * @author zz85 / http://github.com/zz85 */ THREE.RaytracingRenderer = function ( parameters ) { @@ -10,6 +13,8 @@ THREE.RaytracingRenderer = function ( parameters ) { parameters = parameters || {}; var scope = this; + var pool = []; + var renderering = false; var canvas = document.createElement( 'canvas' ); var context = canvas.getContext( '2d', { @@ -23,469 +28,200 @@ THREE.RaytracingRenderer = function ( parameters ) { var clearColor = new THREE.Color( 0x000000 ); - 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(); - - var perspective; - var modelViewMatrix = new THREE.Matrix4(); - var cameraNormalMatrix = new THREE.Matrix3(); - - var objects; - var lights = []; - var cache = {}; - - var animationFrameId = null; - this.domElement = canvas; this.autoClear = true; - this.setClearColor = function ( color, alpha ) { - - clearColor.set( color ); - - }; - - this.setPixelRatio = function () {}; - - this.setSize = function ( width, height ) { - - canvas.width = width; - canvas.height = height; - - canvasWidth = canvas.width; - canvasHeight = canvas.height; - - canvasWidthHalf = Math.floor( canvasWidth / 2 ); - canvasHeightHalf = Math.floor( canvasHeight / 2 ); - - context.fillStyle = 'white'; - - }; - - this.setSize( canvas.width, canvas.height ); - - this.clear = function () { - - }; - - // - - var spawnRay = ( function () { - - var diffuseColor = new THREE.Color(); - var specularColor = new THREE.Color(); - var lightColor = new THREE.Color(); - var schlick = new THREE.Color(); - - var lightContribution = new THREE.Color(); - - var eyeVector = new THREE.Vector3(); - var lightVector = new THREE.Vector3(); - var normalVector = new THREE.Vector3(); - var halfVector = new THREE.Vector3(); - - var localPoint = new THREE.Vector3(); - var reflectionVector = new THREE.Vector3(); - - var tmpVec = new THREE.Vector3(); - - var tmpColor = []; - - for ( var i = 0; i < maxRecursionDepth; i ++ ) { - - tmpColor[ i ] = new THREE.Color(); - - } - - return function spawnRay( 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; - - // - - var _object = cache[ object.id ]; - - localPoint.copy( point ).applyMatrix4( _object.inverseMatrix ); - eyeVector.subVectors( raycaster.ray.origin, point ).normalize(); - - // resolve pixel diffuse color + var workers = parameters.workers; + var blockSize = parameters.blockSize || 64; + this.randomize = parameters.randomize; - 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 ); - - } + var toRender = [], workerId = 0, sceneId = 0; - } else if ( material instanceof THREE.MeshLambertMaterial || - material instanceof THREE.MeshPhongMaterial ) { + console.log( '%cSpinning off ' + workers + ' Workers ', 'font-size: 20px; background: black; color: white; font-family: monospace;' ); - var normalComputed = false; + this.setWorkers = function( w ) { - for ( var i = 0, l = lights.length; i < l; i ++ ) { + workers = w || navigator.hardwareConcurrency || 4; - var light = lights[ i ]; + while ( pool.length < workers ) { + var worker = new Worker( parameters.workerPath ); + worker.id = workerId++; - lightColor.copyGammaToLinear( light.color ); + worker.onmessage = function( e ) { - lightVector.setFromMatrixPosition( light.matrixWorld ); - lightVector.sub( point ); + var data = e.data; - rayLight.direction.copy( lightVector ).normalize(); + if ( ! data ) return; - var intersections = raycasterLight.intersectObjects( objects, true ); + if ( data.blockSize && sceneId == data.sceneId ) { // we match sceneId here to be sure - // point in shadow + var imagedata = new ImageData( new Uint8ClampedArray( data.data ), data.blockSize, data.blockSize ); + context.putImageData( imagedata, data.blockX, data.blockY ); - if ( intersections.length > 0 ) continue; + // completed - // point lit + console.log( 'Worker ' + this.id, data.time / 1000, ( Date.now() - reallyThen ) / 1000 + ' s' ); - if ( normalComputed === false ) { + if ( pool.length > workers ) { - // 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 === true ) { - - attenuation = lightVector.length(); - attenuation = 1.0 / ( attenuation * attenuation ); + pool.splice( pool.indexOf( this ), 1 ); + return this.terminate(); } - 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 ); - - outputColor.add( lightContribution ); - - // compute specular - - if ( material instanceof THREE.MeshPhongMaterial ) { - - halfVector.addVectors( lightVector, eyeVector ).normalize(); - - var dotNormalHalf = Math.max( normalVector.dot( halfVector ), 0.0 ); - var specularIntensity = Math.max( Math.pow( dotNormalHalf, material.shininess ), 0.0 ) * diffuseIntensity; - - var specularNormalization = ( material.shininess + 2.0 ) / 8.0; - - specularColor.copyGammaToLinear( material.specular ); - - 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; - - lightContribution.copy( schlick ); - - lightContribution.multiply( lightColor ); - lightContribution.multiplyScalar( specularNormalization * specularIntensity * attenuation ); - outputColor.add( lightContribution ); - - } + renderNext( this ); } } - // reflection / refraction - - var reflectivity = material.reflectivity; - - if ( ( material.mirror || material.glass ) && reflectivity > 0 && recursionDepth < maxRecursionDepth ) { - - if ( material.mirror ) { - - reflectionVector.copy( rayDirection ); - reflectionVector.reflect( normalVector ); - - } else if ( material.glass ) { + worker.color = new THREE.Color().setHSL( Math.random() , 0.8, 0.8 ).getHexString(); + pool.push( worker ); - var eta = material.refractionRatio; + if ( renderering ) { - var dotNI = rayDirection.dot( normalVector ); - var k = 1.0 - eta * eta * ( 1.0 - dotNI * dotNI ); + updateSettings( worker ); - if ( k < 0.0 ) { + worker.postMessage( { + scene: sceneJSON, + camera: cameraJSON, + annex: materials, + sceneId: sceneId + } ); - reflectionVector.set( 0, 0, 0 ); - - } else { - - 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 ); + renderNext( worker ); } - }; - - }() ); + } - var computePixelNormal = ( function () { + if ( ! renderering ) { - var tmpVec1 = new THREE.Vector3(); - var tmpVec2 = new THREE.Vector3(); - var tmpVec3 = new THREE.Vector3(); + while ( pool.length > workers ) { - return function computePixelNormal( outputVector, point, shading, face, vertices ) { + pool.pop().terminate(); - var faceNormal = face.normal; - var vertexNormals = face.vertexNormals; + } - if ( shading === THREE.FlatShading ) { + } - outputVector.copy( faceNormal ); + }; - } else if ( shading === THREE.SmoothShading ) { + this.setWorkers( workers ); - // compute barycentric coordinates + this.setClearColor = function ( color, alpha ) { - var vA = vertices[ face.a ]; - var vB = vertices[ face.b ]; - var vC = vertices[ face.c ]; + clearColor.set( color ); - 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; + this.setPixelRatio = function () {}; - tmpVec3.crossVectors( tmpVec1.subVectors( vC, point ), tmpVec2.subVectors( vA, point ) ); - var areaPCA = faceNormal.dot( tmpVec3 ); - var b = areaPCA / areaABC; + this.setSize = function ( width, height ) { - var c = 1.0 - a - b; + canvas.width = width; + canvas.height = height; - // compute interpolated vertex normal + canvasWidth = canvas.width; + canvasHeight = canvas.height; - tmpVec1.copy( vertexNormals[ 0 ] ); - tmpVec1.multiplyScalar( a ); + canvasWidthHalf = Math.floor( canvasWidth / 2 ); + canvasHeightHalf = Math.floor( canvasHeight / 2 ); - tmpVec2.copy( vertexNormals[ 1 ] ); - tmpVec2.multiplyScalar( b ); + context.fillStyle = 'white'; - tmpVec3.copy( vertexNormals[ 2 ] ); - tmpVec3.multiplyScalar( c ); + pool.forEach( updateSettings ); - outputVector.addVectors( tmpVec1, tmpVec2 ); - outputVector.add( tmpVec3 ); + }; - } + this.setSize( canvas.width, canvas.height ); - }; + this.clear = function () { - }() ); + }; - var renderBlock = ( function () { + // - var blockSize = 64; + var totalBlocks, xblocks, yblocks; - var canvasBlock = document.createElement( 'canvas' ); - canvasBlock.width = blockSize; - canvasBlock.height = blockSize; + function updateSettings( worker ) { - var contextBlock = canvasBlock.getContext( '2d', { + worker.postMessage( { - alpha: parameters.alpha === true + init: [ canvasWidth, canvasHeight ], + worker: worker.id, + // workers: pool.length, + blockSize: blockSize } ); - var imagedata = contextBlock.getImageData( 0, 0, blockSize, blockSize ); - var data = imagedata.data; - - var pixelColor = new THREE.Color(); + } - return function renderBlock( blockX, blockY ) { + function renderNext( worker ) { + if ( ! toRender.length ) { - var index = 0; + renderering = false; + return scope.dispatchEvent( { type: "complete" } ); - 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 ); + var current = toRender.pop(); - direction.set( x + blockX - canvasWidthHalf, - ( y + blockY - canvasHeightHalf ), - perspective ); - direction.applyMatrix3( cameraNormalMatrix ).normalize(); + var blockX = ( current % xblocks ) * blockSize; + var blockY = ( current / xblocks | 0 ) * blockSize; - spawnRay( origin, direction, pixelColor, 0 ); + worker.postMessage( { + render: true, + x: blockX, + y: blockY, + sceneId: sceneId + } ); - // convert from linear to gamma + context.fillStyle = '#' + worker.color; - data[ index ] = Math.sqrt( pixelColor.r ) * 255; - data[ index + 1 ] = Math.sqrt( pixelColor.g ) * 255; - data[ index + 2 ] = Math.sqrt( pixelColor.b ) * 255; + context.fillRect( blockX, blockY, blockSize, blockSize ); - } + } - } + var materials = {}; - context.putImageData( imagedata, blockX, blockY ); + var sceneJSON, cameraJSON, reallyThen; - blockX += blockSize; + // additional properties that were not serialize automatically - if ( blockX >= canvasWidth ) { + var _annex = { - blockX = 0; - blockY += blockSize; + mirror: 1, + reflectivity: 1, + refractionRatio: 1, + glass: 1, - if ( blockY >= canvasHeight ) { + }; - scope.dispatchEvent( { type: "complete" } ); - return; + function serializeObject( o ) { - } + var mat = o.material; - } + if ( ! mat || mat.uuid in materials ) return; - context.fillRect( blockX, blockY, blockSize, blockSize ); + var props = {}; + for ( var m in _annex ) { - animationFrameId = requestAnimationFrame( function () { + if ( mat[ m ] !== undefined ) { - renderBlock( blockX, blockY ); + props[ m ] = mat[ m ]; - } ); + } - }; + } - }() ); + materials[ mat.uuid ] = props; + } this.render = function ( scene, camera ) { - if ( this.autoClear === true ) this.clear(); - - cancelAnimationFrame( animationFrameId ); + renderering = true; // update scene graph @@ -495,49 +231,56 @@ THREE.RaytracingRenderer = function ( parameters ) { if ( camera.parent === null ) camera.updateMatrixWorld(); - camera.matrixWorldInverse.getInverse( camera.matrixWorld ); - cameraPosition.setFromMatrixPosition( camera.matrixWorld ); - // + sceneJSON = scene.toJSON(); + cameraJSON = camera.toJSON(); + ++ sceneId; - cameraNormalMatrix.getNormalMatrix( camera.matrixWorld ); - origin.copy( cameraPosition ); + scene.traverse( serializeObject ); - perspective = 0.5 / Math.tan( THREE.Math.degToRad( camera.fov * 0.5 ) ) * canvasHeight; + pool.forEach( function( worker ) { - objects = scene.children; + worker.postMessage( { + scene: sceneJSON, + camera: cameraJSON, + annex: materials, + sceneId: sceneId + } ); + } ); - // collect lights and set up object matrices + context.clearRect( 0, 0, canvasWidth, canvasHeight ); + reallyThen = Date.now(); - lights.length = 0; + xblocks = Math.ceil( canvasWidth / blockSize ); + yblocks = Math.ceil( canvasHeight / blockSize ); + totalBlocks = xblocks * yblocks; - scene.traverse( function ( object ) { + toRender = []; - if ( object instanceof THREE.Light ) { + for ( var i = 0; i < totalBlocks; i ++ ) { - lights.push( object ); + toRender.push( i ); - } + } - if ( cache[ object.id ] === undefined ) { - cache[ object.id ] = { - normalMatrix: new THREE.Matrix3(), - inverseMatrix: new THREE.Matrix4() - }; + // Randomize painting :) - } + if ( scope.randomize ) { - modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + for ( var i = 0; i < totalBlocks; i ++ ) { - var _object = cache[ object.id ]; + var swap = Math.random() * totalBlocks | 0; + var tmp = toRender[ swap ]; + toRender[ swap ] = toRender[ i ]; + toRender[ i ] = tmp; - _object.normalMatrix.getNormalMatrix( modelViewMatrix ); - _object.inverseMatrix.getInverse( object.matrixWorld ); + } + + } - } ); - renderBlock( 0, 0 ); + pool.forEach( renderNext ); }; diff --git a/examples/js/renderers/RaytracingWorker.js b/examples/js/renderers/RaytracingWorker.js new file mode 100644 index 00000000000000..e7b714880096a0 --- /dev/null +++ b/examples/js/renderers/RaytracingWorker.js @@ -0,0 +1,571 @@ +var worker; +var BLOCK = 128; +var startX, startY, division, completed = 0; + +var scene, camera, renderer, loader, sceneId; + +importScripts( '../../../build/three.min.js' ); + + +self.onmessage = function( e ) { + + var data = e.data; + if ( ! data ) return; + + if ( data.init ) { + + var + width = data.init[ 0 ], + height = data.init[ 1 ]; + + worker = data.worker; + BLOCK = data.blockSize; + + if ( ! renderer ) renderer = new THREE.RaytracingRendererWorker(); + if ( ! loader ) loader = new THREE.ObjectLoader(); + + renderer.setSize( width, height ); + + // TODO fix passing maxRecursionDepth as parameter. + // if (data.maxRecursionDepth) maxRecursionDepth = data.maxRecursionDepth; + + completed = 0; + + } + + if ( data.scene ) { + + scene = loader.parse( data.scene ); + camera = loader.parse( data.camera ); + + var meta = data.annex; + scene.traverse( function( o ) { + + if ( o instanceof THREE.PointLight ) { + + o.physicalAttenuation = true; + + } + + var mat = o.material; + + if (!mat) return; + + var material = meta[ mat.uuid ]; + for (var m in material) { + + mat[ m ] = material[ m ]; + + } + + } ); + + sceneId = data.sceneId; + } + + if ( data.render && scene && camera ) { + + startX = data.x; + startY = data.y; + renderer.render( scene, camera ); + + } + +} + +/** + * DOM-less version of Raytracing Renderer + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author zz95 / http://github.com/zz85 + */ + +THREE.RaytracingRendererWorker = function ( parameters ) { + + console.log( 'THREE.RaytracingRendererWorker', THREE.REVISION ); + + parameters = parameters || {}; + + var scope = this; + + var maxRecursionDepth = 3; + + var canvasWidth, canvasHeight; + var canvasWidthHalf, canvasHeightHalf; + 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(); + + var perspective; + var modelViewMatrix = new THREE.Matrix4(); + var cameraNormalMatrix = new THREE.Matrix3(); + + var objects; + var lights = []; + var cache = {}; + + var animationFrameId = null; + + this.setSize = function ( width, height ) { + + canvasWidth = width; + canvasHeight = height; + + canvasWidthHalf = Math.floor( canvasWidth / 2 ); + canvasHeightHalf = Math.floor( canvasHeight / 2 ); + + }; + + // + + var spawnRay = ( function () { + + var diffuseColor = new THREE.Color(); + var specularColor = new THREE.Color(); + var lightColor = new THREE.Color(); + var schlick = new THREE.Color(); + + var lightContribution = new THREE.Color(); + + var eyeVector = new THREE.Vector3(); + var lightVector = new THREE.Vector3(); + var normalVector = new THREE.Vector3(); + var halfVector = new THREE.Vector3(); + + var localPoint = new THREE.Vector3(); + var reflectionVector = new THREE.Vector3(); + + var tmpVec = new THREE.Vector3(); + + var tmpColor = []; + + for ( var i = 0; i < maxRecursionDepth; i ++ ) { + + tmpColor[ i ] = new THREE.Color(); + + } + + return function spawnRay( 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; + + // + + var _object = cache[ object.id ]; + + 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 === false ) { + + // 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 === true ) { + + 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 ); + + outputColor.add( lightContribution ); + + // compute specular + + if ( material instanceof THREE.MeshPhongMaterial ) { + + halfVector.addVectors( lightVector, eyeVector ).normalize(); + + var dotNormalHalf = Math.max( normalVector.dot( halfVector ), 0.0 ); + var specularIntensity = Math.max( Math.pow( dotNormalHalf, material.shininess ), 0.0 ) * diffuseIntensity; + + var specularNormalization = ( material.shininess + 2.0 ) / 8.0; + + specularColor.copyGammaToLinear( material.specular ); + + 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; + + lightContribution.copy( schlick ); + + lightContribution.multiply( lightColor ); + lightContribution.multiplyScalar( specularNormalization * specularIntensity * attenuation ); + outputColor.add( lightContribution ); + + } + + } + + } + + // reflection / refraction + + var reflectivity = material.reflectivity; + + if ( ( material.mirror || material.glass ) && reflectivity > 0 && recursionDepth < maxRecursionDepth ) { + + if ( material.mirror ) { + + reflectionVector.copy( rayDirection ); + reflectionVector.reflect( normalVector ); + + } else if ( material.glass ) { + + var eta = material.refractionRatio; + + var dotNI = rayDirection.dot( normalVector ); + var k = 1.0 - eta * eta * ( 1.0 - dotNI * dotNI ); + + if ( k < 0.0 ) { + + reflectionVector.set( 0, 0, 0 ); + + } else { + + 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 computePixelNormal( 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 = BLOCK; + + var data = new Uint8ClampedArray( blockSize * blockSize * 4 ); + + var pixelColor = new THREE.Color(); + + return function renderBlock( 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; + data[ index + 3 ] = 255; + + } + + } + + // Use transferable objects! :) + self.postMessage( { + data: data.buffer, + blockX: blockX, + blockY: blockY, + blockSize: blockSize, + sceneId: sceneId, + time: Date.now() - reallyThen, // time for this renderer + }, [ data.buffer ] ); + + data = new Uint8ClampedArray( blockSize * blockSize * 4 ); + + // OK Done! + completed ++; + + }; + + }() ); + + this.render = function ( scene, camera ) { + + reallyThen = Date.now() + + cancelAnimationFrame( animationFrameId ); + + // update scene graph + + if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); + + // update camera matrices + + if ( camera.parent === null ) camera.updateMatrixWorld(); + + camera.matrixWorldInverse.getInverse( camera.matrixWorld ); + cameraPosition.setFromMatrixPosition( camera.matrixWorld ); + + // + + cameraNormalMatrix.getNormalMatrix( camera.matrixWorld ); + origin.copy( cameraPosition ); + + perspective = 0.5 / Math.tan( THREE.Math.degToRad( camera.fov * 0.5 ) ) * canvasHeight; + + objects = scene.children; + + // collect lights and set up object matrices + + lights.length = 0; + + scene.traverse( function ( object ) { + + if ( object instanceof THREE.Light ) { + + lights.push( object ); + + } + + if ( cache[ object.id ] === undefined ) { + + cache[ object.id ] = { + normalMatrix: new THREE.Matrix3(), + inverseMatrix: new THREE.Matrix4() + }; + + } + + modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + + var _object = cache[ object.id ]; + + _object.normalMatrix.getNormalMatrix( modelViewMatrix ); + _object.inverseMatrix.getInverse( object.matrixWorld ); + + } ); + + renderBlock( startX, startY ); + + }; + +}; + +THREE.EventDispatcher.prototype.apply( THREE.RaytracingRendererWorker.prototype ); diff --git a/examples/raytracing_sandbox.html b/examples/raytracing_sandbox.html index 3c4dad3bcae2c4..d15caa68847585 100644 --- a/examples/raytracing_sandbox.html +++ b/examples/raytracing_sandbox.html @@ -1,7 +1,7 @@
-