Spaces:
Running
Running
| var BLOCK = 128; | |
| var startX, startY; | |
| var scene, camera, renderer, loader, sceneId; | |
| importScripts( '../../../build/three.js' ); | |
| self.onmessage = function ( e ) { | |
| var data = e.data; | |
| if ( ! data ) return; | |
| if ( data.init ) { | |
| var | |
| width = data.init[ 0 ], | |
| height = data.init[ 1 ]; | |
| 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; | |
| } | |
| if ( data.scene ) { | |
| scene = loader.parse( data.scene ); | |
| camera = loader.parse( data.camera ); | |
| var meta = data.annex; | |
| scene.traverse( function ( o ) { | |
| if ( o.isPointLight ) { | |
| 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 () { | |
| console.log( 'THREE.RaytracingRendererWorker', THREE.REVISION ); | |
| 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 ray = raycaster.ray; | |
| var raycasterLight = new THREE.Raycaster(); | |
| var rayLight = raycasterLight.ray; | |
| var perspective; | |
| var cameraNormalMatrix = new THREE.Matrix3(); | |
| var objects; | |
| var lights = []; | |
| var cache = {}; | |
| 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 ) { | |
| outputColor.setRGB( 0, 0, 0 ); | |
| // | |
| ray.origin = rayOrigin; | |
| ray.direction = rayDirection; | |
| 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 geometry = object.geometry; | |
| // | |
| var _object = cache[ object.id ]; | |
| eyeVector.subVectors( ray.origin, point ).normalize(); | |
| // resolve pixel diffuse color | |
| if ( material.isMeshLambertMaterial || | |
| material.isMeshPhongMaterial || | |
| material.isMeshBasicMaterial ) { | |
| 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.isMeshBasicMaterial ) { | |
| 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.isMeshLambertMaterial || material.isMeshPhongMaterial ) { | |
| var normalComputed = false; | |
| 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 lit | |
| if ( normalComputed === false ) { | |
| // the same normal can be reused for all lights | |
| // (should be possible to cache even more) | |
| localPoint.copy( point ).applyMatrix4( _object.inverseMatrix ); | |
| computePixelNormal( normalVector, localPoint, material.flatShading, face, geometry ); | |
| normalVector.applyMatrix3( _object.normalMatrix ).normalize(); | |
| normalComputed = true; | |
| } | |
| lightColor.copyGammaToLinear( light.color ); | |
| // 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.isMeshPhongMaterial ) { | |
| 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 vA = new THREE.Vector3(); | |
| var vB = new THREE.Vector3(); | |
| var vC = new THREE.Vector3(); | |
| var tmpVec1 = new THREE.Vector3(); | |
| var tmpVec2 = new THREE.Vector3(); | |
| var tmpVec3 = new THREE.Vector3(); | |
| return function computePixelNormal( outputVector, point, flatShading, face, geometry ) { | |
| var faceNormal = face.normal; | |
| if ( flatShading === true ) { | |
| outputVector.copy( faceNormal ); | |
| } else { | |
| var positions = geometry.attributes.position; | |
| var normals = geometry.attributes.normal; | |
| vA.fromBufferAttribute( positions, face.a ); | |
| vB.fromBufferAttribute( positions, face.b ); | |
| vC.fromBufferAttribute( positions, face.c ); | |
| // compute barycentric coordinates | |
| 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.fromBufferAttribute( normals, face.a ); | |
| tmpVec2.fromBufferAttribute( normals, face.b ); | |
| tmpVec3.fromBufferAttribute( normals, face.c ); | |
| tmpVec1.multiplyScalar( a ); | |
| tmpVec2.multiplyScalar( b ); | |
| 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 + 0 ] = 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(), // time for this renderer | |
| }, [ data.buffer ] ); | |
| data = new Uint8ClampedArray( blockSize * blockSize * 4 ); | |
| }; | |
| }() ); | |
| this.render = function ( scene, camera ) { | |
| // update scene graph | |
| if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); | |
| // update camera matrices | |
| if ( camera.parent === null ) camera.updateMatrixWorld(); | |
| cameraPosition.setFromMatrixPosition( camera.matrixWorld ); | |
| // | |
| cameraNormalMatrix.getNormalMatrix( camera.matrixWorld ); | |
| 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.isPointLight ) { | |
| lights.push( object ); | |
| } | |
| if ( cache[ object.id ] === undefined ) { | |
| cache[ object.id ] = { | |
| normalMatrix: new THREE.Matrix3(), | |
| inverseMatrix: new THREE.Matrix4() | |
| }; | |
| } | |
| var _object = cache[ object.id ]; | |
| _object.normalMatrix.getNormalMatrix( object.matrixWorld ); | |
| _object.inverseMatrix.getInverse( object.matrixWorld ); | |
| } ); | |
| renderBlock( startX, startY ); | |
| }; | |
| }; | |
| Object.assign( THREE.RaytracingRendererWorker.prototype, THREE.EventDispatcher.prototype ); | |