Spaces:
Running
Running
/** | |
* RaytracingRenderer renders by raytracing it's scene. However, it does not | |
* compute the pixels itself but it hands off and coordinates the tasks 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 ) { | |
console.log( 'THREE.RaytracingRenderer', THREE.REVISION ); | |
parameters = parameters || {}; | |
var scope = this; | |
var pool = []; | |
var renderering = false; | |
var canvas = document.createElement( 'canvas' ); | |
var context = canvas.getContext( '2d', { | |
alpha: parameters.alpha === true | |
} ); | |
var canvasWidth, canvasHeight; | |
var clearColor = new THREE.Color( 0x000000 ); | |
this.domElement = canvas; | |
this.autoClear = true; | |
var workers = parameters.workers; | |
var blockSize = parameters.blockSize || 64; | |
this.randomize = parameters.randomize; | |
var toRender = [], workerId = 0, sceneId = 0; | |
console.log( '%cSpinning off ' + workers + ' Workers ', 'font-size: 20px; background: black; color: white; font-family: monospace;' ); | |
this.setWorkers = function ( w ) { | |
workers = w || navigator.hardwareConcurrency || 4; | |
while ( pool.length < workers ) { | |
var worker = new Worker( parameters.workerPath ); | |
worker.id = workerId ++; | |
worker.onmessage = function ( e ) { | |
var data = e.data; | |
if ( ! data ) return; | |
if ( data.blockSize && sceneId == data.sceneId ) { // we match sceneId here to be sure | |
var imagedata = new ImageData( new Uint8ClampedArray( data.data ), data.blockSize, data.blockSize ); | |
context.putImageData( imagedata, data.blockX, data.blockY ); | |
// completed | |
console.log( 'Worker ' + this.id, data.time / 1000, ( Date.now() - reallyThen ) / 1000 + ' s' ); | |
if ( pool.length > workers ) { | |
pool.splice( pool.indexOf( this ), 1 ); | |
return this.terminate(); | |
} | |
renderNext( this ); | |
} | |
}; | |
worker.color = new THREE.Color().setHSL( Math.random(), 0.8, 0.8 ).getHexString(); | |
pool.push( worker ); | |
updateSettings( worker ); | |
if ( renderering ) { | |
worker.postMessage( { | |
scene: sceneJSON, | |
camera: cameraJSON, | |
annex: materials, | |
sceneId: sceneId | |
} ); | |
renderNext( worker ); | |
} | |
} | |
if ( ! renderering ) { | |
while ( pool.length > workers ) { | |
pool.pop().terminate(); | |
} | |
} | |
}; | |
this.setWorkers( workers ); | |
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; | |
context.fillStyle = 'white'; | |
pool.forEach( updateSettings ); | |
}; | |
this.setSize( canvas.width, canvas.height ); | |
this.clear = function () { | |
}; | |
// | |
var totalBlocks, xblocks, yblocks; | |
function updateSettings( worker ) { | |
worker.postMessage( { | |
init: [ canvasWidth, canvasHeight ], | |
worker: worker.id, | |
// workers: pool.length, | |
blockSize: blockSize | |
} ); | |
} | |
function renderNext( worker ) { | |
if ( ! toRender.length ) { | |
renderering = false; | |
return scope.dispatchEvent( { type: "complete" } ); | |
} | |
var current = toRender.pop(); | |
var blockX = ( current % xblocks ) * blockSize; | |
var blockY = ( current / xblocks | 0 ) * blockSize; | |
worker.postMessage( { | |
render: true, | |
x: blockX, | |
y: blockY, | |
sceneId: sceneId | |
} ); | |
context.fillStyle = '#' + worker.color; | |
context.fillRect( blockX, blockY, blockSize, blockSize ); | |
} | |
var materials = {}; | |
var sceneJSON, cameraJSON, reallyThen; | |
// additional properties that were not serialize automatically | |
var _annex = { | |
mirror: 1, | |
reflectivity: 1, | |
refractionRatio: 1, | |
glass: 1 | |
}; | |
function serializeObject( o ) { | |
var mat = o.material; | |
if ( ! mat || mat.uuid in materials ) return; | |
var props = {}; | |
for ( var m in _annex ) { | |
if ( mat[ m ] !== undefined ) { | |
props[ m ] = mat[ m ]; | |
} | |
} | |
materials[ mat.uuid ] = props; | |
} | |
this.render = function ( scene, camera ) { | |
renderering = true; | |
// update scene graph | |
if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); | |
// update camera matrices | |
if ( camera.parent === null ) camera.updateMatrixWorld(); | |
sceneJSON = scene.toJSON(); | |
cameraJSON = camera.toJSON(); | |
++ sceneId; | |
scene.traverse( serializeObject ); | |
pool.forEach( function ( worker ) { | |
worker.postMessage( { | |
scene: sceneJSON, | |
camera: cameraJSON, | |
annex: materials, | |
sceneId: sceneId | |
} ); | |
} ); | |
context.clearRect( 0, 0, canvasWidth, canvasHeight ); | |
reallyThen = Date.now(); | |
xblocks = Math.ceil( canvasWidth / blockSize ); | |
yblocks = Math.ceil( canvasHeight / blockSize ); | |
totalBlocks = xblocks * yblocks; | |
toRender = []; | |
for ( var i = 0; i < totalBlocks; i ++ ) { | |
toRender.push( i ); | |
} | |
// Randomize painting :) | |
if ( scope.randomize ) { | |
for ( var i = 0; i < totalBlocks; i ++ ) { | |
var swap = Math.random() * totalBlocks | 0; | |
var tmp = toRender[ swap ]; | |
toRender[ swap ] = toRender[ i ]; | |
toRender[ i ] = tmp; | |
} | |
} | |
pool.forEach( renderNext ); | |
}; | |
}; | |
Object.assign( THREE.RaytracingRenderer.prototype, THREE.EventDispatcher.prototype ); | |