/** * @author troffmo5 / http://github.com/troffmo5 * * Effect to render the scene in stereo 3d side by side with lens distortion. * It is written to be used with the Oculus Rift (http://www.oculusvr.com/) but * it works also with other HMD using the same technology */ THREE.OculusRiftEffect = function ( renderer, options ) { // worldFactor indicates how many units is 1 meter var worldFactor = (options && options.worldFactor) ? options.worldFactor: 1.0; // Specific HMD parameters var HMD = (options && options.HMD) ? options.HMD: { // DK1 /* hResolution: 1280, vResolution: 800, hScreenSize: 0.14976, vScreenSize: 0.0936, interpupillaryDistance: 0.064, lensSeparationDistance: 0.064, eyeToScreenDistance: 0.041, distortionK : [1.0, 0.22, 0.24, 0.0], chromaAbParameter: [ 0.996, -0.004, 1.014, 0.0] */ // DK2 hResolution: 1920, vResolution: 1080, hScreenSize: 0.12576, vScreenSize: 0.07074, interpupillaryDistance: 0.0635, lensSeparationDistance: 0.0635, eyeToScreenDistance: 0.041, distortionK : [1.0, 0.22, 0.24, 0.0], chromaAbParameter: [ 0.996, -0.004, 1.014, 0.0] }; this.HMD = HMD; // Perspective camera var pCamera = new THREE.PerspectiveCamera(); pCamera.matrixAutoUpdate = false; pCamera.target = new THREE.Vector3(); // Orthographic camera var oCamera = new THREE.OrthographicCamera( -1, 1, 1, -1, 1, 1000 ); oCamera.position.z = 1; // pre-render hooks this.preLeftRender = function() {}; this.preRightRender = function() {}; renderer.autoClear = false; var emptyColor = new THREE.Color("black"); // Render target var RTParams = { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat }; var renderTarget = new THREE.WebGLRenderTarget( 640, 800, RTParams ); var RTMaterial = new THREE.ShaderMaterial( { uniforms: { "texid": { type: "t", value: renderTarget }, "scale": { type: "v2", value: new THREE.Vector2(1.0,1.0) }, "scaleIn": { type: "v2", value: new THREE.Vector2(1.0,1.0) }, "lensCenter": { type: "v2", value: new THREE.Vector2(0.0,0.0) }, "hmdWarpParam": { type: "v4", value: new THREE.Vector4(1.0,0.0,0.0,0.0) }, "chromAbParam": { type: "v4", value: new THREE.Vector4(1.0,0.0,0.0,0.0) } }, vertexShader: [ "varying vec2 vUv;", "void main() {", " vUv = uv;", " gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", "}" ].join("\n"), fragmentShader: [ "uniform vec2 scale;", "uniform vec2 scaleIn;", "uniform vec2 lensCenter;", "uniform vec4 hmdWarpParam;", 'uniform vec4 chromAbParam;', "uniform sampler2D texid;", "varying vec2 vUv;", "void main()", "{", " vec2 uv = (vUv*2.0)-1.0;", // range from [0,1] to [-1,1] " vec2 theta = (uv-lensCenter)*scaleIn;", " float rSq = theta.x*theta.x + theta.y*theta.y;", " vec2 rvector = theta*(hmdWarpParam.x + hmdWarpParam.y*rSq + hmdWarpParam.z*rSq*rSq + hmdWarpParam.w*rSq*rSq*rSq);", ' vec2 rBlue = rvector * (chromAbParam.z + chromAbParam.w * rSq);', " vec2 tcBlue = (lensCenter + scale * rBlue);", " tcBlue = (tcBlue+1.0)/2.0;", // range from [-1,1] to [0,1] " if (any(bvec2(clamp(tcBlue, vec2(0.0,0.0), vec2(1.0,1.0))-tcBlue))) {", " gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);", " return;}", " vec2 tcGreen = lensCenter + scale * rvector;", " tcGreen = (tcGreen+1.0)/2.0;", // range from [-1,1] to [0,1] " vec2 rRed = rvector * (chromAbParam.x + chromAbParam.y * rSq);", " vec2 tcRed = lensCenter + scale * rRed;", " tcRed = (tcRed+1.0)/2.0;", // range from [-1,1] to [0,1] " gl_FragColor = vec4(texture2D(texid, tcRed).r, texture2D(texid, tcGreen).g, texture2D(texid, tcBlue).b, 1);", "}" ].join("\n") } ); var mesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), RTMaterial ); // Final scene var finalScene = new THREE.Scene(); finalScene.add( oCamera ); finalScene.add( mesh ); var left = {}, right = {}; var distScale = 1.0; this.setHMD = function(v) { HMD = v; // Compute aspect ratio and FOV var aspect = HMD.hResolution / (2*HMD.vResolution); // Fov is normally computed with: // THREE.Math.radToDeg( 2*Math.atan2(HMD.vScreenSize,2*HMD.eyeToScreenDistance) ); // But with lens distortion it is increased (see Oculus SDK Documentation) var r = -1.0 - (4 * (HMD.hScreenSize/4 - HMD.lensSeparationDistance/2) / HMD.hScreenSize); distScale = (HMD.distortionK[0] + HMD.distortionK[1] * Math.pow(r,2) + HMD.distortionK[2] * Math.pow(r,4) + HMD.distortionK[3] * Math.pow(r,6)); var fov = THREE.Math.radToDeg(2*Math.atan2(HMD.vScreenSize*distScale, 2*HMD.eyeToScreenDistance)); // Compute camera projection matrices var proj = (new THREE.Matrix4()).makePerspective( fov, aspect, 0.3, 10000 ); var h = 4 * (HMD.hScreenSize/4 - HMD.interpupillaryDistance/2) / HMD.hScreenSize; left.proj = ((new THREE.Matrix4()).makeTranslation( h, 0.0, 0.0 )).multiply(proj); right.proj = ((new THREE.Matrix4()).makeTranslation( -h, 0.0, 0.0 )).multiply(proj); // Compute camera transformation matrices left.tranform = (new THREE.Matrix4()).makeTranslation( -worldFactor * HMD.interpupillaryDistance/2, 0.0, 0.0 ); right.tranform = (new THREE.Matrix4()).makeTranslation( worldFactor * HMD.interpupillaryDistance/2, 0.0, 0.0 ); // Compute Viewport left.viewport = [0, 0, HMD.hResolution/2, HMD.vResolution]; right.viewport = [HMD.hResolution/2, 0, HMD.hResolution/2, HMD.vResolution]; // Distortion shader parameters var lensShift = 4 * (HMD.hScreenSize/4 - HMD.lensSeparationDistance/2) / HMD.hScreenSize; left.lensCenter = new THREE.Vector2(lensShift, 0.0); right.lensCenter = new THREE.Vector2(-lensShift, 0.0); RTMaterial.uniforms['hmdWarpParam'].value = new THREE.Vector4(HMD.distortionK[0], HMD.distortionK[1], HMD.distortionK[2], HMD.distortionK[3]); RTMaterial.uniforms['chromAbParam'].value = new THREE.Vector4(HMD.chromaAbParameter[0], HMD.chromaAbParameter[1], HMD.chromaAbParameter[2], HMD.chromaAbParameter[3]); RTMaterial.uniforms['scaleIn'].value = new THREE.Vector2(1.0,1.0/aspect); RTMaterial.uniforms['scale'].value = new THREE.Vector2(1.0/distScale, 1.0*aspect/distScale); // Create render target if ( renderTarget ) renderTarget.dispose(); renderTarget = new THREE.WebGLRenderTarget( ( HMD.hResolution * distScale / 2 ) * renderer.devicePixelRatio, ( HMD.vResolution * distScale ) * renderer.devicePixelRatio, RTParams ); RTMaterial.uniforms[ "texid" ].value = renderTarget; } this.getHMD = function() {return HMD}; this.setHMD(HMD); this.setSize = function ( width, height ) { left.viewport = [width/2 - HMD.hResolution/2, height/2 - HMD.vResolution/2, HMD.hResolution/2, HMD.vResolution]; right.viewport = [width/2, height/2 - HMD.vResolution/2, HMD.hResolution/2, HMD.vResolution]; renderer.setSize( width, height ); }; this.render = function ( scene, camera ) { var cc = renderer.getClearColor().clone(); // Clear renderer.setClearColor(emptyColor); renderer.clear(); renderer.setClearColor(cc); // camera parameters if (camera.matrixAutoUpdate) camera.updateMatrix(); // Render left this.preLeftRender(); pCamera.projectionMatrix.copy(left.proj); pCamera.matrix.copy(camera.matrix).multiply(left.tranform); pCamera.matrixWorldNeedsUpdate = true; renderer.setViewport(left.viewport[0], left.viewport[1], left.viewport[2], left.viewport[3]); RTMaterial.uniforms['lensCenter'].value = left.lensCenter; renderer.render( scene, pCamera, renderTarget, true ); renderer.render( finalScene, oCamera ); // Render right this.preRightRender(); pCamera.projectionMatrix.copy(right.proj); pCamera.matrix.copy(camera.matrix).multiply(right.tranform); pCamera.matrixWorldNeedsUpdate = true; renderer.setViewport(right.viewport[0], right.viewport[1], right.viewport[2], right.viewport[3]); RTMaterial.uniforms['lensCenter'].value = right.lensCenter; renderer.render( scene, pCamera, renderTarget, true ); renderer.render( finalScene, oCamera ); }; this.dispose = function() { if ( RTMaterial ) { RTMaterial.dispose(); } if ( renderTarget ) { renderTarget.dispose(); } }; };