├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── build.js ├── build.sh ├── dev.js ├── dev.sh ├── dist ├── Capturer.js └── Capturer.min.js ├── docs ├── css │ ├── index.css │ └── normalize.css ├── favicon.ico ├── images │ ├── logo.png │ ├── share.jpg │ └── vignette.png ├── index.html ├── js │ ├── Capturer.js │ ├── OBJLoader.js │ ├── OrbitControls.js │ ├── oui.min.js │ └── three.r79.min.js └── models │ ├── suzanne.jpg │ └── suzanne.obj ├── media ├── equirectangular_kuva.jpg ├── equirectangular_suzanne.jpg ├── ods_kuva.jpg └── ods_suzanne.jpg ├── package.json └── src ├── Capturer.js ├── helpers ├── fboHelper.js ├── quad.frag └── quad.vert └── methods ├── Method.js ├── MethodEquirectangular.js ├── MethodOds.js ├── cubeToEquirectangular.frag └── perspectiveToEquirect.frag /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.pyc 3 | *.pyo 4 | ======= 5 | # Compiled source # 6 | ################### 7 | *.com 8 | *.class 9 | *.dll 10 | *.exe 11 | *.o 12 | *.so 13 | .sass-cache 14 | 15 | 16 | # Packages # 17 | ############ 18 | # it's better to unpack these files and commit the raw source 19 | # git has its own built in compression methods 20 | *.7z 21 | *.dmg 22 | *.gz 23 | *.iso 24 | *.jar 25 | *.rar 26 | *.tar 27 | # Logs and databases # 28 | ###################### 29 | *.log 30 | #*.sql 31 | *.sqlite 32 | 33 | # OS generated files # 34 | ###################### 35 | .DS_Store 36 | .DS_Store? 37 | ._* 38 | .Spotlight-V100 39 | .Trashes 40 | ehthumbs.db 41 | Thumbs.db 42 | 43 | *.sublime-project 44 | *.sublime-workspace 45 | 46 | /node_modules 47 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 50, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : false, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : false, // true: Require {} for every new block or scope 11 | "eqeqeq" : false, // true: Require triple equals (===) for comparison 12 | "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. 14 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 15 | "indent" : 4, // {int} Number of spaces to use for indentation 16 | "latedef" : false, // true: Require variables/functions to be defined before being used 17 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 18 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 19 | "noempty" : true, // true: Prohibit use of empty blocks 20 | "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. 21 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 22 | "plusplus" : false, // true: Prohibit use of `++` & `--` 23 | "quotmark" : false, // Quotation mark consistency: 24 | // false : do nothing (default) 25 | // true : ensure whatever is used is consistent 26 | // "single" : require single quotes 27 | // "double" : require double quotes 28 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 29 | "unused" : true, // Unused variables: 30 | // true : all variables, last function parameter 31 | // "vars" : all variables only 32 | // "strict" : all variables, all function parameters 33 | "strict" : false, // true: Requires all functions run in ES5 Strict Mode 34 | "maxparams" : false, // {int} Max number of formal params allowed per function 35 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 36 | "maxstatements" : false, // {int} Max number statements per function 37 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 38 | "maxlen" : false, // {int} Max number of characters per line 39 | 40 | // Relaxing 41 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 42 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 43 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 44 | "eqnull" : false, // true: Tolerate use of `== null` 45 | "es5" : true, // true: Allow ES5 syntax (ex: getters and setters) 46 | "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) 47 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 48 | // (ex: `for each`, multiple try/catch, function expression…) 49 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 50 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 51 | "funcscope" : false, // true: Tolerate defining variables inside control statements 52 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 53 | "iterator" : false, // true: Tolerate using the `__iterator__` property 54 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 55 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 56 | "laxcomma" : false, // true: Tolerate comma-first style coding 57 | "loopfunc" : false, // true: Tolerate functions being defined in loops 58 | "multistr" : false, // true: Tolerate multi-line strings 59 | "noyield" : false, // true: Tolerate generator functions with no yield statement in them. 60 | "notypeof" : false, // true: Tolerate invalid typeof operator values 61 | "proto" : false, // true: Tolerate using the `__proto__` property 62 | "scripturl" : false, // true: Tolerate script-targeted URLs 63 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 64 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 65 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 66 | "validthis" : true, // true: Tolerate using this in a non-constructor function 67 | 68 | // Environments 69 | "browser" : true, // Web Browser (window, document, etc) 70 | "browserify" : true, // Browserify (node.js code in the browser) 71 | "couch" : false, // CouchDB 72 | "devel" : true, // Development/debugging (alert, confirm, etc) 73 | "dojo" : false, // Dojo Toolkit 74 | "jasmine" : false, // Jasmine 75 | "jquery" : false, // jQuery 76 | "mocha" : true, // Mocha 77 | "mootools" : false, // MooTools 78 | "node" : false, // Node.js 79 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 80 | "phantom" : false, // PhantomJS 81 | "prototypejs" : false, // Prototype and Scriptaculous 82 | "qunit" : false, // QUnit 83 | "rhino" : false, // Rhino 84 | "shelljs" : false, // ShellJS 85 | "typed" : false, // Globals for typed array constructions 86 | "worker" : false, // Web Workers 87 | "wsh" : false, // Windows Scripting Host 88 | "yui" : false, // Yahoo User Interface 89 | 90 | // Custom Globals 91 | "predef" : [ 92 | ] // additional predefined global variables 93 | } 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Kuva Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## THREE.Capturer 2 | **THREE.Capturer** is a simple helper created by **[KUVA](https://kuva.io/)** to export **[THREE.JS](threejs.org)** content into Equirectangular format and **[ODS(VR) format](https://developers.google.com/cardboard/jump/rendering-ods-content.pdf)** image. 3 | 4 | Check out the **[live demo here](https://kuva.io/THREE.Capturer/)** 5 | 6 | If you have a cardboard device, you can check out the still frame ODS export from the demo site on **[Youtube here](https://www.youtube.com/watch?v=CBII29aayqc)**. 7 | 8 | ### Gallery 9 | 10 | Equirectangular result of the KUVA.io homepage: 11 | ![](./media/equirectangular_kuva.jpg) 12 | 13 | ODS result of the demo page: 14 | ![](./media/ods_suzanne.jpg) 15 | 16 | ### Example 17 | 18 | To use this helper, you need to include the THREE.JS library before the helper. 19 | 20 | var renderer = THREE.WebGLRenderer(); 21 | var scene = THREE.Scene(); 22 | var camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 500 ); 23 | 24 | var capturer = new THREE.Capturer( { 25 | 26 | renderer : renderer, 27 | camera : camera, 28 | scene : scene, 29 | 30 | // default filename 31 | fileName: 'capture', 32 | 33 | // file type can be jpg or png 34 | fileType: 'png' 35 | } ); 36 | 37 | var equirectangularMethod = new THREE.Capturer.methods.Equirectangular( { 38 | 39 | // image size - w: 4096 h: 2048 40 | size: 4096, 41 | 42 | // cube camera renderTarget size 43 | cubeSize: 2048 44 | 45 | } ); 46 | 47 | var odsMethod = new THREE.Capturer.methods.ODS( { 48 | 49 | // image size - w: 4096 h: 4096 50 | size: 4096, 51 | 52 | // Interpupillary distance - distance in meter between the center of the pupils of the two eyes 53 | ipd: 0.064 54 | 55 | } ); 56 | 57 | // assign the capturer method you want to use 58 | capturer.method = odsMethod; 59 | 60 | // render the capturer result on to the canvas. For advanced usage, you can pass the renderTarget to the function to render the result onto a renderTarget. 61 | capturer.render(); 62 | 63 | // save the canvas result as an image file 64 | capturer.capture( function() { 65 | 66 | console.log( 'complete' ); 67 | 68 | } ); 69 | 70 | 71 | 72 | ### TODO 73 | - Fix the half pixel offset issue in ODS 74 | - ~~Mobile support for the demo page~~ 75 | 76 | ### Development and deployment 77 | - dev: `node dev` 78 | - deploy: `node build` 79 | 80 | ### License 81 | 82 | The MIT License (MIT) 83 | 84 | Copyright (c) 2016 Kuva, https://kuva.io/ 85 | 86 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 87 | 88 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 89 | 90 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 91 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | global.Promise = require('pinkie-promise'); 2 | const browserify = require('browserify'); 3 | const fs = require('fs'); 4 | const UglifyJS = require('uglify-js'); 5 | 6 | Promise.all(['Capturer.js'].map(runBuild)).catch(function (err) { 7 | console.error(err); 8 | }).then(function () { 9 | console.log('Finished'); 10 | }); 11 | 12 | function runBuild (f) { 13 | return new Promise(function (resolve, reject) { 14 | console.log('Bundling', f); 15 | var b = browserify('src/' + f, { 16 | debug: false, 17 | // noparse: [ 'three' ] 18 | }); 19 | // b.transform(require('babelify').configure({ presets: 'es2015' })); 20 | b.plugin(require('bundle-collapser/plugin')); 21 | var transforms = [['glslify', { global: true, transform: ['glslify-hex'] }]]; 22 | transforms.forEach(function (t) { 23 | b.transform(t); 24 | }); 25 | b.bundle(function (err, src) { 26 | if (err) return reject(err); 27 | console.log('Compressing', f); 28 | src = src.toString(); 29 | fs.writeFileSync('dist/' + f, src); 30 | fs.writeFileSync('docs/js/' + f, src); 31 | var result = UglifyJS.minify(src, { fromString: true }); 32 | console.log('Writing', f); 33 | fs.writeFileSync('dist/' + f.replace('.js','.min.js'), result.code); 34 | resolve(); 35 | }); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | npm run build 2 | -------------------------------------------------------------------------------- /dev.js: -------------------------------------------------------------------------------- 1 | const budo = require('budo'); 2 | const path = require('path'); 3 | const opn = require('opn'); 4 | const fs = require('fs'); 5 | const simpleHtml = require('simple-html-index'); 6 | 7 | var entryPath = path.resolve('src', 'Capturer.js'); 8 | budo(entryPath, { 9 | serve: 'js/Capturer.js', 10 | live: true, 11 | dir: __dirname + '/docs', 12 | stream: process.stdout, 13 | defaultIndex: function (opt) { 14 | var html = 'index.html'; 15 | if (!fs.existsSync(html)) return simpleHtml(opt); 16 | return fs.createReadStream(html); 17 | }, 18 | browserify: { 19 | transform: [ 20 | ['installify', { save: true }], 21 | ['glslify', { global: true, transform: ['glslify-hex'] }] 22 | ] 23 | } 24 | }).on('connect', function(ev) { 25 | const uri = ev.uri + 'index.html'; 26 | opn(uri); 27 | }); 28 | -------------------------------------------------------------------------------- /dev.sh: -------------------------------------------------------------------------------- 1 | npm run dev 2 | -------------------------------------------------------------------------------- /dist/Capturer.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o> 1; 402 | this.cubeRenderTarget = new THREE.WebGLRenderTargetCube(this.cubeSize, this.cubeSize, { 403 | format: THREE.RGBFormat, magFilter: THREE.LinearFilter, minFilter: THREE.LinearFilter 404 | }); 405 | 406 | this.cubeSideCameras = {}; 407 | var cubeSideCameraSetting, cubeSideCamera; 408 | for(var cubeSideCameraId in CUBE_CAMERA_SETTINGS) { 409 | cubeSideCameraSetting = CUBE_CAMERA_SETTINGS[cubeSideCameraId]; 410 | cubeSideCamera = this.cubeSideCameras[cubeSideCameraId] = new THREE.PerspectiveCamera( 90, 1 ); 411 | cubeSideCamera.cubeIndex = cubeSideCameraSetting.index; 412 | cubeSideCamera.up.fromArray(cubeSideCameraSetting.up); 413 | cubeSideCamera.lookAt((new THREE.Vector3()).fromArray(cubeSideCameraSetting.target)); 414 | cubeSideCamera.updateMatrix(); 415 | this.add(cubeSideCamera); 416 | } 417 | 418 | this.cubeToEqurectangularMaterial = new THREE.RawShaderMaterial({ 419 | uniforms: { 420 | u_texture: { type: 't', value: this.cubeRenderTarget } 421 | }, 422 | vertexShader: fboHelper.rawShaderPrefix + fboHelper.vertexShader, 423 | fragmentShader: fboHelper.rawShaderPrefix + "#define GLSLIFY 1\nuniform samplerCube u_texture;\n\nvarying vec2 v_uv;\n\nvoid main() {\n vec2 thetaphi = ((v_uv * 2.0) - vec2(1.0)) * vec2(3.1415926535897932384626433832795, 1.5707963267948966192313216916398);\n vec3 rayDirection = vec3(cos(thetaphi.y) * cos(thetaphi.x), sin(thetaphi.y), cos(thetaphi.y) * sin(thetaphi.x));\n gl_FragColor = textureCube(u_texture, rayDirection);\n\n}\n", 424 | }); 425 | 426 | this.rotation.y = Math.PI / 2; 427 | } 428 | 429 | module.exports = MethodCube; 430 | var _super = Method.prototype; 431 | var _p = MethodCube.prototype = Object.create(_super); 432 | _p.constructor = MethodCube; 433 | 434 | _p.render = render; 435 | 436 | function render( renderer, scene, camera, renderTarget, forceClear ) { 437 | 438 | renderer.clearTarget(this.cubeRenderTarget, true, true, true); 439 | 440 | var autoUpdate = scene.autoUpdate; 441 | var cubeSideCamera; 442 | this.updateMatrixWorld(true); 443 | for(var cameraId in CUBE_CAMERA_SETTINGS) { 444 | cubeSideCamera = this.cubeSideCameras[cameraId]; 445 | this._updateCameraNearFar(cubeSideCamera, camera); 446 | this.cubeRenderTarget.activeCubeFace = cubeSideCamera.cubeIndex; 447 | renderer.render(scene, cubeSideCamera, this.cubeRenderTarget); 448 | scene.autoUpdate = false; 449 | } 450 | scene.autoUpdate = autoUpdate; 451 | 452 | if(renderTarget) { 453 | renderTarget.setSize(this.width, this.height); 454 | } else { 455 | renderer.setSize(this.width, this.height); 456 | } 457 | fboHelper.render(this.cubeToEqurectangularMaterial, renderTarget, forceClear); 458 | 459 | } 460 | 461 | },{"4":4,"6":6,"7":7}],9:[function(require,module,exports){ 462 | var Method = require(7); 463 | var fboHelper = require(6); 464 | 465 | var mixIn = require(4); 466 | 467 | 468 | function MethodOds(cfg) { 469 | 470 | _super.constructor.call(this, mixIn({ 471 | method: 'ODS', 472 | size: 4096, 473 | ipd: 0.064 474 | }, cfg)); 475 | this.width = this.size; 476 | this.height = this.size; 477 | this.viewWidth = this.width; 478 | this.viewHeight = this.height >> 1; 479 | this.pixelWidthRenderTarget = fboHelper.createRenderTarget(1, 1); 480 | this.pixelWidthRenderTarget.depthBuffer = true; 481 | this.pixelWidthRenderTarget.stencilBuffer = true; 482 | this.eyeRenderTarget = fboHelper.createRenderTarget(this.viewWidth, this.viewHeight); 483 | this.outRenderTarget = fboHelper.createRenderTarget(this.width, this.height); 484 | 485 | // vertical fov 90deg, hoziontal fov = 360 / width, FOV_h = 2*atan((x/y)tan(FOV_v/2)) 486 | this.pixelWidthCamera = new THREE.PerspectiveCamera(90, Math.atan(Math.PI / this.width)); 487 | this.add(this.pixelWidthCamera); 488 | this.lookAtTarget = new THREE.Vector3(); 489 | 490 | this.perspectiveToEquirectMaterial = new THREE.RawShaderMaterial({ 491 | uniforms: { 492 | u_texture: { type: 't', value: this.eyeRenderTarget } 493 | }, 494 | depthTest: false, 495 | depthWrite: false, 496 | vertexShader: fboHelper.vertexShader, 497 | fragmentShader: fboHelper.rawShaderPrefix + "#define GLSLIFY 1\nuniform sampler2D u_texture;\nvarying vec2 v_uv;\n\nvoid main() {\n\n float y = fract(v_uv.y * 2.0);\n\n float phi = y * 3.1415926535897932384626433832795 / 2.0 - 3.1415926535897932384626433832795 / 4.0;\n\n gl_FragColor = texture2D(u_texture, vec2(v_uv.x, (tan(phi) * 0.25 + mix(0.25, 0.75, step(0.5, v_uv.y)))));\n\n}\n" 498 | }); 499 | 500 | } 501 | 502 | module.exports = MethodOds; 503 | var _super = Method.prototype; 504 | var _p = MethodOds.prototype = Object.create(_super); 505 | _p.constructor = MethodOds; 506 | 507 | _p.render = render; 508 | _p.renderEye = renderEye; 509 | 510 | function render( renderer, scene, camera, renderTarget, forceClear ) { 511 | var autoUpdate = scene.autoUpdate; 512 | this.renderEye(-1, renderer, scene, camera); 513 | this.renderEye(1, renderer, scene, camera); 514 | scene.autoUpdate = autoUpdate; 515 | 516 | if(renderTarget) { 517 | renderTarget.setSize(this.width, this.height); 518 | } else { 519 | renderer.setSize(this.width, this.height); 520 | } 521 | fboHelper.copy(this.outRenderTarget, renderTarget, forceClear); 522 | } 523 | 524 | function renderEye(side, renderer, scene, camera) { 525 | 526 | var viewWidth = this.viewWidth; 527 | var viewHeight = this.viewHeight; 528 | var viewHalfHeight = viewHeight / 2; 529 | 530 | var pixelWidthCamera = this.pixelWidthCamera; 531 | var lookAtTarget = this.lookAtTarget; 532 | var eyeRenderTarget = this.eyeRenderTarget; 533 | var pixelWidthRenderTarget = this.pixelWidthRenderTarget; 534 | var origin = pixelWidthCamera.position; 535 | 536 | var eyeOffset = this.ipd / 2 * side; 537 | 538 | var x, theta, phi, sinTheta, cosTheta, sinPhi, cosPhi; 539 | 540 | eyeRenderTarget.scissorTest = true; 541 | renderer.clearTarget(eyeRenderTarget, true, true, true); 542 | 543 | this.updateMatrixWorld(true); 544 | this._updateCameraNearFar(pixelWidthCamera, camera); 545 | pixelWidthRenderTarget.setSize(1, viewHalfHeight); 546 | 547 | for(var i = 0; i < viewWidth; i++) { 548 | 549 | x = (i + 0.5) / viewWidth; // add 0.5 to sample the center of the pixel 550 | 551 | theta = (x * 2 - 1) * Math.PI; 552 | sinTheta = Math.sin(theta); 553 | cosTheta = Math.cos(theta); 554 | 555 | // top 90 deg 556 | phi = Math.PI / 2 - 0.25 * Math.PI; 557 | sinPhi = Math.sin(phi); 558 | cosPhi = Math.cos(phi); 559 | 560 | origin.set(cosTheta * eyeOffset, 0 , sinTheta * eyeOffset); 561 | lookAtTarget.set(sinTheta * cosPhi, sinPhi, -cosTheta * cosPhi).add(origin); 562 | pixelWidthCamera.lookAt(lookAtTarget); 563 | pixelWidthCamera.updateMatrixWorld(true); 564 | 565 | renderer.render(scene, pixelWidthCamera, pixelWidthRenderTarget); 566 | eyeRenderTarget.viewport.set(i, viewHalfHeight, 1, viewHalfHeight); 567 | eyeRenderTarget.scissor.set(i, viewHalfHeight, 1, viewHalfHeight); 568 | fboHelper.copy(pixelWidthRenderTarget, eyeRenderTarget); 569 | 570 | scene.autoUpdate = false; 571 | 572 | // bottom 90 deg 573 | phi = Math.PI / 2 - 0.75 * Math.PI; 574 | sinPhi = Math.sin(phi); 575 | cosPhi = Math.cos(phi); 576 | 577 | lookAtTarget.set(sinTheta * cosPhi, sinPhi, -cosTheta * cosPhi).add(origin); 578 | pixelWidthCamera.lookAt(lookAtTarget); 579 | pixelWidthCamera.updateMatrixWorld(true); 580 | 581 | // postprocessing.render(deltaTime, true); 582 | renderer.render(scene, pixelWidthCamera, pixelWidthRenderTarget); 583 | eyeRenderTarget.viewport.set(i, 0, 1, viewHalfHeight); 584 | eyeRenderTarget.scissor.set(i, 0, 1, viewHalfHeight); 585 | fboHelper.copy(pixelWidthRenderTarget, eyeRenderTarget); 586 | 587 | } 588 | 589 | this.outRenderTarget.scissorTest = true; 590 | this.outRenderTarget.viewport.set(0, side < 0 ? this.viewHeight : 0, this.width, this.viewHeight); 591 | this.outRenderTarget.scissor.set(0, side < 0 ? this.viewHeight : 0, this.width, this.viewHeight); 592 | fboHelper.render(this.perspectiveToEquirectMaterial, this.outRenderTarget); 593 | this.outRenderTarget.scissorTest = false; 594 | 595 | 596 | } 597 | 598 | },{"4":4,"6":6,"7":7}]},{},[5]); 599 | -------------------------------------------------------------------------------- /dist/Capturer.min.js: -------------------------------------------------------------------------------- 1 | !function e(t,r,i){function a(o,s){if(!r[o]){if(!t[o]){var h="function"==typeof require&&require;if(!s&&h)return h(o,!0);if(n)return n(o,!0);var u=new Error("Cannot find module '"+o+"'");throw u.code="MODULE_NOT_FOUND",u}var d=r[o]={exports:{}};t[o][0].call(d.exports,function(e){var r=t[o][1][e];return a(r?r:e)},d,d.exports,e,t,r,i)}return r[o].exports}for(var n="function"==typeof require&&require,o=0;o>1,this.cubeRenderTarget=new THREE.WebGLRenderTargetCube(this.cubeSize,this.cubeSize,{format:THREE.RGBFormat,magFilter:THREE.LinearFilter,minFilter:THREE.LinearFilter}),this.cubeSideCameras={};var t,r;for(var i in h)t=h[i],r=this.cubeSideCameras[i]=new THREE.PerspectiveCamera(90,1),r.cubeIndex=t.index,r.up.fromArray(t.up),r.lookAt((new THREE.Vector3).fromArray(t.target)),r.updateMatrix(),this.add(r);this.cubeToEqurectangularMaterial=new THREE.RawShaderMaterial({uniforms:{u_texture:{type:"t",value:this.cubeRenderTarget}},vertexShader:o.rawShaderPrefix+o.vertexShader,fragmentShader:o.rawShaderPrefix+"#define GLSLIFY 1\nuniform samplerCube u_texture;\n\nvarying vec2 v_uv;\n\nvoid main() {\n vec2 thetaphi = ((v_uv * 2.0) - vec2(1.0)) * vec2(3.1415926535897932384626433832795, 1.5707963267948966192313216916398);\n vec3 rayDirection = vec3(cos(thetaphi.y) * cos(thetaphi.x), sin(thetaphi.y), cos(thetaphi.y) * sin(thetaphi.x));\n gl_FragColor = textureCube(u_texture, rayDirection);\n\n}\n"}),this.rotation.y=Math.PI/2}function a(e,t,r,i,a){e.clearTarget(this.cubeRenderTarget,!0,!0,!0);var n,s=t.autoUpdate;this.updateMatrixWorld(!0);for(var u in h)n=this.cubeSideCameras[u],this._updateCameraNearFar(n,r),this.cubeRenderTarget.activeCubeFace=n.cubeIndex,e.render(t,n,this.cubeRenderTarget),t.autoUpdate=!1;t.autoUpdate=s,i?i.setSize(this.width,this.height):e.setSize(this.width,this.height),o.render(this.cubeToEqurectangularMaterial,i,a)}var n=e(7),o=e(6),s=e(4),h={PX:{index:0,up:[0,-1,0],target:[1,0,0]},NX:{index:1,up:[0,-1,0],target:[-1,0,0]},PY:{index:2,up:[0,0,1],target:[0,1,0]},NY:{index:3,up:[0,0,-1],target:[0,-1,0]},PZ:{index:4,up:[0,-1,0],target:[0,0,1]},NZ:{index:5,up:[0,-1,0],target:[0,0,-1]}};t.exports=i;var u=n.prototype,d=i.prototype=Object.create(u);d.constructor=i,d.render=a},{4:4,6:6,7:7}],9:[function(e,t,r){function i(e){u.constructor.call(this,h({method:"ODS",size:4096,ipd:.064},e)),this.width=this.size,this.height=this.size,this.viewWidth=this.width,this.viewHeight=this.height>>1,this.pixelWidthRenderTarget=s.createRenderTarget(1,1),this.pixelWidthRenderTarget.depthBuffer=!0,this.pixelWidthRenderTarget.stencilBuffer=!0,this.eyeRenderTarget=s.createRenderTarget(this.viewWidth,this.viewHeight),this.outRenderTarget=s.createRenderTarget(this.width,this.height),this.pixelWidthCamera=new THREE.PerspectiveCamera(90,Math.atan(Math.PI/this.width)),this.add(this.pixelWidthCamera),this.lookAtTarget=new THREE.Vector3,this.perspectiveToEquirectMaterial=new THREE.RawShaderMaterial({uniforms:{u_texture:{type:"t",value:this.eyeRenderTarget}},depthTest:!1,depthWrite:!1,vertexShader:s.vertexShader,fragmentShader:s.rawShaderPrefix+"#define GLSLIFY 1\nuniform sampler2D u_texture;\nvarying vec2 v_uv;\n\nvoid main() {\n\n float y = fract(v_uv.y * 2.0);\n\n float phi = y * 3.1415926535897932384626433832795 / 2.0 - 3.1415926535897932384626433832795 / 4.0;\n\n gl_FragColor = texture2D(u_texture, vec2(v_uv.x, (tan(phi) * 0.25 + mix(0.25, 0.75, step(0.5, v_uv.y)))));\n\n}\n"})}function a(e,t,r,i,a){var n=t.autoUpdate;this.renderEye(-1,e,t,r),this.renderEye(1,e,t,r),t.autoUpdate=n,i?i.setSize(this.width,this.height):e.setSize(this.width,this.height),s.copy(this.outRenderTarget,i,a)}function n(e,t,r,i){var a,n,o,h,u,d,c,l=this.viewWidth,p=this.viewHeight,f=p/2,v=this.pixelWidthCamera,g=this.lookAtTarget,T=this.eyeRenderTarget,m=this.pixelWidthRenderTarget,y=v.position,x=this.ipd/2*e;T.scissorTest=!0,t.clearTarget(T,!0,!0,!0),this.updateMatrixWorld(!0),this._updateCameraNearFar(v,i),m.setSize(1,f);for(var E=0;E