nuclear@3: /************************************************************************************ nuclear@3: nuclear@3: Filename : Util_Render_Stereo.cpp nuclear@3: Content : Stereo rendering configuration implementation nuclear@3: Created : October 22, 2012 nuclear@3: Authors : Michael Antonov, Andrew Reisse nuclear@3: nuclear@3: Copyright : Copyright 2012 Oculus, Inc. All Rights reserved. nuclear@3: nuclear@3: Use of this software is subject to the terms of the Oculus Inc license nuclear@3: agreement provided at the time of installation or download, or which nuclear@3: otherwise accompanies this software in either electronic or hard copy form. nuclear@3: nuclear@3: *************************************************************************************/ nuclear@3: nuclear@3: #include "Util_Render_Stereo.h" nuclear@3: nuclear@3: namespace OVR { namespace Util { namespace Render { nuclear@3: nuclear@3: nuclear@3: //----------------------------------------------------------------------------------- nuclear@3: nuclear@3: // DistortionFnInverse computes the inverse of the distortion function on an argument. nuclear@3: float DistortionConfig::DistortionFnInverse(float r) nuclear@3: { nuclear@3: OVR_ASSERT((r <= 10.0f)); nuclear@3: nuclear@3: float s, d; nuclear@3: float delta = r * 0.25f; nuclear@3: nuclear@3: s = r * 0.5f; nuclear@3: d = fabs(r - DistortionFn(s)); nuclear@3: nuclear@3: for (int i = 0; i < 20; i++) nuclear@3: { nuclear@3: float sUp = s + delta; nuclear@3: float sDown = s - delta; nuclear@3: float dUp = fabs(r - DistortionFn(sUp)); nuclear@3: float dDown = fabs(r - DistortionFn(sDown)); nuclear@3: nuclear@3: if (dUp < d) nuclear@3: { nuclear@3: s = sUp; nuclear@3: d = dUp; nuclear@3: } nuclear@3: else if (dDown < d) nuclear@3: { nuclear@3: s = sDown; nuclear@3: d = dDown; nuclear@3: } nuclear@3: else nuclear@3: { nuclear@3: delta *= 0.5f; nuclear@3: } nuclear@3: } nuclear@3: nuclear@3: return s; nuclear@3: } nuclear@3: nuclear@3: nuclear@3: //----------------------------------------------------------------------------------- nuclear@3: // **** StereoConfig Implementation nuclear@3: nuclear@3: StereoConfig::StereoConfig(StereoMode mode, const Viewport& vp) nuclear@3: : Mode(mode), nuclear@3: InterpupillaryDistance(0.064f), AspectMultiplier(1.0f), nuclear@3: FullView(vp), DirtyFlag(true), IPDOverride(false), nuclear@3: YFov(0), Aspect(vp.w / float(vp.h)), ProjectionCenterOffset(0), nuclear@3: OrthoPixelOffset(0) nuclear@3: { nuclear@3: // And default distortion for it. nuclear@3: Distortion.SetCoefficients(1.0f, 0.22f, 0.24f); nuclear@3: Distortion.Scale = 1.0f; // Will be computed later. nuclear@3: nuclear@3: // Fit left of the image. nuclear@3: DistortionFitX = -1.0f; nuclear@3: DistortionFitY = 0.0f; nuclear@3: nuclear@3: // Initialize "fake" default HMD values for testing without HMD plugged in. nuclear@3: // These default values match those returned by the HMD. nuclear@3: HMD.HResolution = 1280; nuclear@3: HMD.VResolution = 800; nuclear@3: HMD.HScreenSize = 0.14976f; nuclear@3: HMD.VScreenSize = HMD.HScreenSize / (1280.0f / 800.0f); nuclear@3: HMD.InterpupillaryDistance = InterpupillaryDistance; nuclear@3: HMD.LensSeparationDistance = 0.0635f; nuclear@3: HMD.EyeToScreenDistance = 0.041f; nuclear@3: HMD.DistortionK[0] = Distortion.K[0]; nuclear@3: HMD.DistortionK[1] = Distortion.K[1]; nuclear@3: HMD.DistortionK[2] = Distortion.K[2]; nuclear@3: HMD.DistortionK[3] = 0; nuclear@3: nuclear@3: Set2DAreaFov(DegreeToRad(85.0f)); nuclear@3: } nuclear@3: nuclear@3: void StereoConfig::SetFullViewport(const Viewport& vp) nuclear@3: { nuclear@3: if (vp != FullView) nuclear@3: { nuclear@3: FullView = vp; nuclear@3: DirtyFlag = true; nuclear@3: } nuclear@3: } nuclear@3: nuclear@3: void StereoConfig::SetHMDInfo(const HMDInfo& hmd) nuclear@3: { nuclear@3: HMD = hmd; nuclear@3: Distortion.K[0] = hmd.DistortionK[0]; nuclear@3: Distortion.K[1] = hmd.DistortionK[1]; nuclear@3: Distortion.K[2] = hmd.DistortionK[2]; nuclear@3: Distortion.K[3] = hmd.DistortionK[3]; nuclear@3: nuclear@3: Distortion.SetChromaticAberration(hmd.ChromaAbCorrection[0], hmd.ChromaAbCorrection[1], nuclear@3: hmd.ChromaAbCorrection[2], hmd.ChromaAbCorrection[3]); nuclear@3: nuclear@3: if (!IPDOverride) nuclear@3: InterpupillaryDistance = HMD.InterpupillaryDistance; nuclear@3: nuclear@3: DirtyFlag = true; nuclear@3: } nuclear@3: nuclear@3: void StereoConfig::SetDistortionFitPointVP(float x, float y) nuclear@3: { nuclear@3: DistortionFitX = x; nuclear@3: DistortionFitY = y; nuclear@3: DirtyFlag = true; nuclear@3: } nuclear@3: nuclear@3: void StereoConfig::SetDistortionFitPointPixels(float x, float y) nuclear@3: { nuclear@3: DistortionFitX = (4 * x / float(FullView.w)) - 1.0f; nuclear@3: DistortionFitY = (2 * y / float(FullView.h)) - 1.0f; nuclear@3: DirtyFlag = true; nuclear@3: } nuclear@3: nuclear@3: void StereoConfig::Set2DAreaFov(float fovRadians) nuclear@3: { nuclear@3: Area2DFov = fovRadians; nuclear@3: DirtyFlag = true; nuclear@3: } nuclear@3: nuclear@3: nuclear@3: const StereoEyeParams& StereoConfig::GetEyeRenderParams(StereoEye eye) nuclear@3: { nuclear@3: static const UByte eyeParamIndices[3] = { 0, 0, 1 }; nuclear@3: nuclear@3: updateIfDirty(); nuclear@3: OVR_ASSERT(eye < sizeof(eyeParamIndices)); nuclear@3: return EyeRenderParams[eyeParamIndices[eye]]; nuclear@3: } nuclear@3: nuclear@3: nuclear@3: void StereoConfig::updateComputedState() nuclear@3: { nuclear@3: // Need to compute all of the following: nuclear@3: // - Aspect Ratio nuclear@3: // - FOV nuclear@3: // - Projection offsets for 3D nuclear@3: // - Distortion XCenterOffset nuclear@3: // - Update 2D nuclear@3: // - Initialize EyeRenderParams nuclear@3: nuclear@3: // Compute aspect ratio. Stereo mode cuts width in half. nuclear@3: Aspect = float(FullView.w) / float(FullView.h); nuclear@3: Aspect *= (Mode == Stereo_None) ? 1.0f : 0.5f; nuclear@3: Aspect *= AspectMultiplier; nuclear@3: nuclear@3: updateDistortionOffsetAndScale(); nuclear@3: nuclear@3: // Compute Vertical FOV based on distance, distortion, etc. nuclear@3: // Distance from vertical center to render vertical edge perceived through the lens. nuclear@3: // This will be larger then normal screen size due to magnification & distortion. nuclear@3: // nuclear@3: // This percievedHalfRTDistance equation should hold as long as the render target nuclear@3: // and display have the same aspect ratios. What we'd like to know is where the edge nuclear@3: // of the render target will on the perceived screen surface. With NO LENS, nuclear@3: // the answer would be: nuclear@3: // nuclear@3: // halfRTDistance = (VScreenSize / 2) * aspect * nuclear@3: // DistortionFn_Inverse( DistortionScale / aspect ) nuclear@3: // nuclear@3: // To model the optical lens we eliminates DistortionFn_Inverse. Aspect ratios nuclear@3: // cancel out, so we get: nuclear@3: // nuclear@3: // halfRTDistance = (VScreenSize / 2) * DistortionScale nuclear@3: // nuclear@3: if (Mode == Stereo_None) nuclear@3: { nuclear@3: YFov = DegreeToRad(80.0f); nuclear@3: } nuclear@3: else nuclear@3: { nuclear@3: float percievedHalfRTDistance = (HMD.VScreenSize / 2) * Distortion.Scale; nuclear@3: YFov = 2.0f * atan(percievedHalfRTDistance/HMD.EyeToScreenDistance); nuclear@3: } nuclear@3: nuclear@3: updateProjectionOffset(); nuclear@3: update2D(); nuclear@3: updateEyeParams(); nuclear@3: nuclear@3: DirtyFlag = false; nuclear@3: } nuclear@3: nuclear@3: void StereoConfig::updateDistortionOffsetAndScale() nuclear@3: { nuclear@3: // Distortion center shift is stored separately, since it isn't affected nuclear@3: // by the eye distance. nuclear@3: float lensOffset = HMD.LensSeparationDistance * 0.5f; nuclear@3: float lensShift = HMD.HScreenSize * 0.25f - lensOffset; nuclear@3: float lensViewportShift = 4.0f * lensShift / HMD.HScreenSize; nuclear@3: Distortion.XCenterOffset= lensViewportShift; nuclear@3: nuclear@3: // Compute distortion scale from DistortionFitX & DistortionFitY. nuclear@3: // Fit value of 0.0 means "no fit". nuclear@3: if ((fabs(DistortionFitX) < 0.0001f) && (fabs(DistortionFitY) < 0.0001f)) nuclear@3: { nuclear@3: Distortion.Scale = 1.0f; nuclear@3: } nuclear@3: else nuclear@3: { nuclear@3: // Convert fit value to distortion-centered coordinates before fit radius nuclear@3: // calculation. nuclear@3: float stereoAspect = 0.5f * float(FullView.w) / float(FullView.h); nuclear@3: float dx = DistortionFitX - Distortion.XCenterOffset; nuclear@3: float dy = DistortionFitY / stereoAspect; nuclear@3: float fitRadius = sqrt(dx * dx + dy * dy); nuclear@3: Distortion.Scale = Distortion.DistortionFn(fitRadius)/fitRadius; nuclear@3: } nuclear@3: } nuclear@3: nuclear@3: void StereoConfig::updateProjectionOffset() nuclear@3: { nuclear@3: // Post-projection viewport coordinates range from (-1.0, 1.0), with the nuclear@3: // center of the left viewport falling at (1/4) of horizontal screen size. nuclear@3: // We need to shift this projection center to match with the lens center; nuclear@3: // note that we don't use the IPD here due to collimated light property of the lens. nuclear@3: // We compute this shift in physical units (meters) to nuclear@3: // correct for different screen sizes and then rescale to viewport coordinates. nuclear@3: float viewCenter = HMD.HScreenSize * 0.25f; nuclear@3: float eyeProjectionShift = viewCenter - HMD.LensSeparationDistance*0.5f; nuclear@3: ProjectionCenterOffset = 4.0f * eyeProjectionShift / HMD.HScreenSize; nuclear@3: } nuclear@3: nuclear@3: void StereoConfig::update2D() nuclear@3: { nuclear@3: // Orthographic projection fakes a screen at a distance of 0.8m from the nuclear@3: // eye, where hmd screen projection surface is at 0.05m distance. nuclear@3: // This introduces an extra off-center pixel projection shift based on eye distance. nuclear@3: // This offCenterShift is the pixel offset of the other camera's center nuclear@3: // in your reference camera based on surface distance. nuclear@3: float metersToPixels = (HMD.HResolution / HMD.HScreenSize); nuclear@3: float lensDistanceScreenPixels= metersToPixels * HMD.LensSeparationDistance; nuclear@3: float eyeDistanceScreenPixels = metersToPixels * InterpupillaryDistance; nuclear@3: float offCenterShiftPixels = (HMD.EyeToScreenDistance / 0.8f) * eyeDistanceScreenPixels; nuclear@3: float leftPixelCenter = (HMD.HResolution / 2) - lensDistanceScreenPixels * 0.5f; nuclear@3: float rightPixelCenter = lensDistanceScreenPixels * 0.5f; nuclear@3: float pixelDifference = leftPixelCenter - rightPixelCenter; nuclear@3: nuclear@3: // This computes the number of pixels that fit within specified 2D FOV (assuming nuclear@3: // distortion scaling will be done). nuclear@3: float percievedHalfScreenDistance = tan(Area2DFov * 0.5f) * HMD.EyeToScreenDistance; nuclear@3: float vfovSize = 2.0f * percievedHalfScreenDistance / Distortion.Scale; nuclear@3: FovPixels = HMD.VResolution * vfovSize / HMD.VScreenSize; nuclear@3: nuclear@3: // Create orthographic matrix. nuclear@3: Matrix4f& m = OrthoCenter; nuclear@3: m.SetIdentity(); nuclear@3: m.M[0][0] = FovPixels / (FullView.w * 0.5f); nuclear@3: m.M[1][1] = -FovPixels / FullView.h; nuclear@3: m.M[0][3] = 0; nuclear@3: m.M[1][3] = 0; nuclear@3: m.M[2][2] = 0; nuclear@3: nuclear@3: float orthoPixelOffset = (pixelDifference + offCenterShiftPixels/Distortion.Scale) * 0.5f; nuclear@3: OrthoPixelOffset = orthoPixelOffset * 2.0f / FovPixels; nuclear@3: } nuclear@3: nuclear@3: void StereoConfig::updateEyeParams() nuclear@3: { nuclear@3: // Projection matrix for the center eye, which the left/right matrices are based on. nuclear@3: Matrix4f projCenter = Matrix4f::PerspectiveRH(YFov, Aspect, 0.01f, 2000.0f); nuclear@3: nuclear@3: switch(Mode) nuclear@3: { nuclear@3: case Stereo_None: nuclear@3: { nuclear@3: EyeRenderParams[0].Init(StereoEye_Center, FullView, 0, projCenter, OrthoCenter); nuclear@3: } nuclear@3: break; nuclear@3: nuclear@3: case Stereo_LeftRight_Multipass: nuclear@3: { nuclear@3: Matrix4f projLeft = Matrix4f::Translation(ProjectionCenterOffset, 0, 0) * projCenter, nuclear@3: projRight = Matrix4f::Translation(-ProjectionCenterOffset, 0, 0) * projCenter; nuclear@3: nuclear@3: EyeRenderParams[0].Init(StereoEye_Left, nuclear@3: Viewport(FullView.x, FullView.y, FullView.w/2, FullView.h), nuclear@3: +InterpupillaryDistance * 0.5f, // World view shift. nuclear@3: projLeft, OrthoCenter * Matrix4f::Translation(OrthoPixelOffset, 0, 0), nuclear@3: &Distortion); nuclear@3: EyeRenderParams[1].Init(StereoEye_Right, nuclear@3: Viewport(FullView.x + FullView.w/2, FullView.y, FullView.w/2, FullView.h), nuclear@3: -InterpupillaryDistance * 0.5f, nuclear@3: projRight, OrthoCenter * Matrix4f::Translation(-OrthoPixelOffset, 0, 0), nuclear@3: &Distortion); nuclear@3: } nuclear@3: break; nuclear@3: } nuclear@3: nuclear@3: } nuclear@3: nuclear@3: nuclear@3: }}} // OVR::Util::Render nuclear@3: