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