oculus1

annotate libovr/Src/Util/Util_Render_Stereo.cpp @ 17:cfe4979ab3eb

ops, minor error in the last commit
author John Tsiombikas <nuclear@member.fsf.org>
date Sat, 21 Sep 2013 07:09:48 +0300
parents e2f9e4603129
children
rev   line source
nuclear@3 1 /************************************************************************************
nuclear@3 2
nuclear@3 3 Filename : Util_Render_Stereo.cpp
nuclear@3 4 Content : Stereo rendering configuration implementation
nuclear@3 5 Created : October 22, 2012
nuclear@3 6 Authors : Michael Antonov, Andrew Reisse
nuclear@3 7
nuclear@3 8 Copyright : Copyright 2012 Oculus, Inc. All Rights reserved.
nuclear@3 9
nuclear@3 10 Use of this software is subject to the terms of the Oculus Inc license
nuclear@3 11 agreement provided at the time of installation or download, or which
nuclear@3 12 otherwise accompanies this software in either electronic or hard copy form.
nuclear@3 13
nuclear@3 14 *************************************************************************************/
nuclear@3 15
nuclear@3 16 #include "Util_Render_Stereo.h"
nuclear@3 17
nuclear@3 18 namespace OVR { namespace Util { namespace Render {
nuclear@3 19
nuclear@3 20
nuclear@3 21 //-----------------------------------------------------------------------------------
nuclear@3 22
nuclear@3 23 // DistortionFnInverse computes the inverse of the distortion function on an argument.
nuclear@3 24 float DistortionConfig::DistortionFnInverse(float r)
nuclear@3 25 {
nuclear@3 26 OVR_ASSERT((r <= 10.0f));
nuclear@3 27
nuclear@3 28 float s, d;
nuclear@3 29 float delta = r * 0.25f;
nuclear@3 30
nuclear@3 31 s = r * 0.5f;
nuclear@3 32 d = fabs(r - DistortionFn(s));
nuclear@3 33
nuclear@3 34 for (int i = 0; i < 20; i++)
nuclear@3 35 {
nuclear@3 36 float sUp = s + delta;
nuclear@3 37 float sDown = s - delta;
nuclear@3 38 float dUp = fabs(r - DistortionFn(sUp));
nuclear@3 39 float dDown = fabs(r - DistortionFn(sDown));
nuclear@3 40
nuclear@3 41 if (dUp < d)
nuclear@3 42 {
nuclear@3 43 s = sUp;
nuclear@3 44 d = dUp;
nuclear@3 45 }
nuclear@3 46 else if (dDown < d)
nuclear@3 47 {
nuclear@3 48 s = sDown;
nuclear@3 49 d = dDown;
nuclear@3 50 }
nuclear@3 51 else
nuclear@3 52 {
nuclear@3 53 delta *= 0.5f;
nuclear@3 54 }
nuclear@3 55 }
nuclear@3 56
nuclear@3 57 return s;
nuclear@3 58 }
nuclear@3 59
nuclear@3 60
nuclear@3 61 //-----------------------------------------------------------------------------------
nuclear@3 62 // **** StereoConfig Implementation
nuclear@3 63
nuclear@3 64 StereoConfig::StereoConfig(StereoMode mode, const Viewport& vp)
nuclear@3 65 : Mode(mode),
nuclear@3 66 InterpupillaryDistance(0.064f), AspectMultiplier(1.0f),
nuclear@3 67 FullView(vp), DirtyFlag(true), IPDOverride(false),
nuclear@3 68 YFov(0), Aspect(vp.w / float(vp.h)), ProjectionCenterOffset(0),
nuclear@3 69 OrthoPixelOffset(0)
nuclear@3 70 {
nuclear@3 71 // And default distortion for it.
nuclear@3 72 Distortion.SetCoefficients(1.0f, 0.22f, 0.24f);
nuclear@3 73 Distortion.Scale = 1.0f; // Will be computed later.
nuclear@3 74
nuclear@3 75 // Fit left of the image.
nuclear@3 76 DistortionFitX = -1.0f;
nuclear@3 77 DistortionFitY = 0.0f;
nuclear@3 78
nuclear@3 79 // Initialize "fake" default HMD values for testing without HMD plugged in.
nuclear@3 80 // These default values match those returned by the HMD.
nuclear@3 81 HMD.HResolution = 1280;
nuclear@3 82 HMD.VResolution = 800;
nuclear@3 83 HMD.HScreenSize = 0.14976f;
nuclear@3 84 HMD.VScreenSize = HMD.HScreenSize / (1280.0f / 800.0f);
nuclear@3 85 HMD.InterpupillaryDistance = InterpupillaryDistance;
nuclear@3 86 HMD.LensSeparationDistance = 0.0635f;
nuclear@3 87 HMD.EyeToScreenDistance = 0.041f;
nuclear@3 88 HMD.DistortionK[0] = Distortion.K[0];
nuclear@3 89 HMD.DistortionK[1] = Distortion.K[1];
nuclear@3 90 HMD.DistortionK[2] = Distortion.K[2];
nuclear@3 91 HMD.DistortionK[3] = 0;
nuclear@3 92
nuclear@3 93 Set2DAreaFov(DegreeToRad(85.0f));
nuclear@3 94 }
nuclear@3 95
nuclear@3 96 void StereoConfig::SetFullViewport(const Viewport& vp)
nuclear@3 97 {
nuclear@3 98 if (vp != FullView)
nuclear@3 99 {
nuclear@3 100 FullView = vp;
nuclear@3 101 DirtyFlag = true;
nuclear@3 102 }
nuclear@3 103 }
nuclear@3 104
nuclear@3 105 void StereoConfig::SetHMDInfo(const HMDInfo& hmd)
nuclear@3 106 {
nuclear@3 107 HMD = hmd;
nuclear@3 108 Distortion.K[0] = hmd.DistortionK[0];
nuclear@3 109 Distortion.K[1] = hmd.DistortionK[1];
nuclear@3 110 Distortion.K[2] = hmd.DistortionK[2];
nuclear@3 111 Distortion.K[3] = hmd.DistortionK[3];
nuclear@3 112
nuclear@3 113 Distortion.SetChromaticAberration(hmd.ChromaAbCorrection[0], hmd.ChromaAbCorrection[1],
nuclear@3 114 hmd.ChromaAbCorrection[2], hmd.ChromaAbCorrection[3]);
nuclear@3 115
nuclear@3 116 if (!IPDOverride)
nuclear@3 117 InterpupillaryDistance = HMD.InterpupillaryDistance;
nuclear@3 118
nuclear@3 119 DirtyFlag = true;
nuclear@3 120 }
nuclear@3 121
nuclear@3 122 void StereoConfig::SetDistortionFitPointVP(float x, float y)
nuclear@3 123 {
nuclear@3 124 DistortionFitX = x;
nuclear@3 125 DistortionFitY = y;
nuclear@3 126 DirtyFlag = true;
nuclear@3 127 }
nuclear@3 128
nuclear@3 129 void StereoConfig::SetDistortionFitPointPixels(float x, float y)
nuclear@3 130 {
nuclear@3 131 DistortionFitX = (4 * x / float(FullView.w)) - 1.0f;
nuclear@3 132 DistortionFitY = (2 * y / float(FullView.h)) - 1.0f;
nuclear@3 133 DirtyFlag = true;
nuclear@3 134 }
nuclear@3 135
nuclear@3 136 void StereoConfig::Set2DAreaFov(float fovRadians)
nuclear@3 137 {
nuclear@3 138 Area2DFov = fovRadians;
nuclear@3 139 DirtyFlag = true;
nuclear@3 140 }
nuclear@3 141
nuclear@3 142
nuclear@3 143 const StereoEyeParams& StereoConfig::GetEyeRenderParams(StereoEye eye)
nuclear@3 144 {
nuclear@3 145 static const UByte eyeParamIndices[3] = { 0, 0, 1 };
nuclear@3 146
nuclear@3 147 updateIfDirty();
nuclear@3 148 OVR_ASSERT(eye < sizeof(eyeParamIndices));
nuclear@3 149 return EyeRenderParams[eyeParamIndices[eye]];
nuclear@3 150 }
nuclear@3 151
nuclear@3 152
nuclear@3 153 void StereoConfig::updateComputedState()
nuclear@3 154 {
nuclear@3 155 // Need to compute all of the following:
nuclear@3 156 // - Aspect Ratio
nuclear@3 157 // - FOV
nuclear@3 158 // - Projection offsets for 3D
nuclear@3 159 // - Distortion XCenterOffset
nuclear@3 160 // - Update 2D
nuclear@3 161 // - Initialize EyeRenderParams
nuclear@3 162
nuclear@3 163 // Compute aspect ratio. Stereo mode cuts width in half.
nuclear@3 164 Aspect = float(FullView.w) / float(FullView.h);
nuclear@3 165 Aspect *= (Mode == Stereo_None) ? 1.0f : 0.5f;
nuclear@3 166 Aspect *= AspectMultiplier;
nuclear@3 167
nuclear@3 168 updateDistortionOffsetAndScale();
nuclear@3 169
nuclear@3 170 // Compute Vertical FOV based on distance, distortion, etc.
nuclear@3 171 // Distance from vertical center to render vertical edge perceived through the lens.
nuclear@3 172 // This will be larger then normal screen size due to magnification & distortion.
nuclear@3 173 //
nuclear@3 174 // This percievedHalfRTDistance equation should hold as long as the render target
nuclear@3 175 // and display have the same aspect ratios. What we'd like to know is where the edge
nuclear@3 176 // of the render target will on the perceived screen surface. With NO LENS,
nuclear@3 177 // the answer would be:
nuclear@3 178 //
nuclear@3 179 // halfRTDistance = (VScreenSize / 2) * aspect *
nuclear@3 180 // DistortionFn_Inverse( DistortionScale / aspect )
nuclear@3 181 //
nuclear@3 182 // To model the optical lens we eliminates DistortionFn_Inverse. Aspect ratios
nuclear@3 183 // cancel out, so we get:
nuclear@3 184 //
nuclear@3 185 // halfRTDistance = (VScreenSize / 2) * DistortionScale
nuclear@3 186 //
nuclear@3 187 if (Mode == Stereo_None)
nuclear@3 188 {
nuclear@3 189 YFov = DegreeToRad(80.0f);
nuclear@3 190 }
nuclear@3 191 else
nuclear@3 192 {
nuclear@3 193 float percievedHalfRTDistance = (HMD.VScreenSize / 2) * Distortion.Scale;
nuclear@3 194 YFov = 2.0f * atan(percievedHalfRTDistance/HMD.EyeToScreenDistance);
nuclear@3 195 }
nuclear@3 196
nuclear@3 197 updateProjectionOffset();
nuclear@3 198 update2D();
nuclear@3 199 updateEyeParams();
nuclear@3 200
nuclear@3 201 DirtyFlag = false;
nuclear@3 202 }
nuclear@3 203
nuclear@3 204 void StereoConfig::updateDistortionOffsetAndScale()
nuclear@3 205 {
nuclear@3 206 // Distortion center shift is stored separately, since it isn't affected
nuclear@3 207 // by the eye distance.
nuclear@3 208 float lensOffset = HMD.LensSeparationDistance * 0.5f;
nuclear@3 209 float lensShift = HMD.HScreenSize * 0.25f - lensOffset;
nuclear@3 210 float lensViewportShift = 4.0f * lensShift / HMD.HScreenSize;
nuclear@3 211 Distortion.XCenterOffset= lensViewportShift;
nuclear@3 212
nuclear@3 213 // Compute distortion scale from DistortionFitX & DistortionFitY.
nuclear@3 214 // Fit value of 0.0 means "no fit".
nuclear@3 215 if ((fabs(DistortionFitX) < 0.0001f) && (fabs(DistortionFitY) < 0.0001f))
nuclear@3 216 {
nuclear@3 217 Distortion.Scale = 1.0f;
nuclear@3 218 }
nuclear@3 219 else
nuclear@3 220 {
nuclear@3 221 // Convert fit value to distortion-centered coordinates before fit radius
nuclear@3 222 // calculation.
nuclear@3 223 float stereoAspect = 0.5f * float(FullView.w) / float(FullView.h);
nuclear@3 224 float dx = DistortionFitX - Distortion.XCenterOffset;
nuclear@3 225 float dy = DistortionFitY / stereoAspect;
nuclear@3 226 float fitRadius = sqrt(dx * dx + dy * dy);
nuclear@3 227 Distortion.Scale = Distortion.DistortionFn(fitRadius)/fitRadius;
nuclear@3 228 }
nuclear@3 229 }
nuclear@3 230
nuclear@3 231 void StereoConfig::updateProjectionOffset()
nuclear@3 232 {
nuclear@3 233 // Post-projection viewport coordinates range from (-1.0, 1.0), with the
nuclear@3 234 // center of the left viewport falling at (1/4) of horizontal screen size.
nuclear@3 235 // We need to shift this projection center to match with the lens center;
nuclear@3 236 // note that we don't use the IPD here due to collimated light property of the lens.
nuclear@3 237 // We compute this shift in physical units (meters) to
nuclear@3 238 // correct for different screen sizes and then rescale to viewport coordinates.
nuclear@3 239 float viewCenter = HMD.HScreenSize * 0.25f;
nuclear@3 240 float eyeProjectionShift = viewCenter - HMD.LensSeparationDistance*0.5f;
nuclear@3 241 ProjectionCenterOffset = 4.0f * eyeProjectionShift / HMD.HScreenSize;
nuclear@3 242 }
nuclear@3 243
nuclear@3 244 void StereoConfig::update2D()
nuclear@3 245 {
nuclear@3 246 // Orthographic projection fakes a screen at a distance of 0.8m from the
nuclear@3 247 // eye, where hmd screen projection surface is at 0.05m distance.
nuclear@3 248 // This introduces an extra off-center pixel projection shift based on eye distance.
nuclear@3 249 // This offCenterShift is the pixel offset of the other camera's center
nuclear@3 250 // in your reference camera based on surface distance.
nuclear@3 251 float metersToPixels = (HMD.HResolution / HMD.HScreenSize);
nuclear@3 252 float lensDistanceScreenPixels= metersToPixels * HMD.LensSeparationDistance;
nuclear@3 253 float eyeDistanceScreenPixels = metersToPixels * InterpupillaryDistance;
nuclear@3 254 float offCenterShiftPixels = (HMD.EyeToScreenDistance / 0.8f) * eyeDistanceScreenPixels;
nuclear@3 255 float leftPixelCenter = (HMD.HResolution / 2) - lensDistanceScreenPixels * 0.5f;
nuclear@3 256 float rightPixelCenter = lensDistanceScreenPixels * 0.5f;
nuclear@3 257 float pixelDifference = leftPixelCenter - rightPixelCenter;
nuclear@3 258
nuclear@3 259 // This computes the number of pixels that fit within specified 2D FOV (assuming
nuclear@3 260 // distortion scaling will be done).
nuclear@3 261 float percievedHalfScreenDistance = tan(Area2DFov * 0.5f) * HMD.EyeToScreenDistance;
nuclear@3 262 float vfovSize = 2.0f * percievedHalfScreenDistance / Distortion.Scale;
nuclear@3 263 FovPixels = HMD.VResolution * vfovSize / HMD.VScreenSize;
nuclear@3 264
nuclear@3 265 // Create orthographic matrix.
nuclear@3 266 Matrix4f& m = OrthoCenter;
nuclear@3 267 m.SetIdentity();
nuclear@3 268 m.M[0][0] = FovPixels / (FullView.w * 0.5f);
nuclear@3 269 m.M[1][1] = -FovPixels / FullView.h;
nuclear@3 270 m.M[0][3] = 0;
nuclear@3 271 m.M[1][3] = 0;
nuclear@3 272 m.M[2][2] = 0;
nuclear@3 273
nuclear@3 274 float orthoPixelOffset = (pixelDifference + offCenterShiftPixels/Distortion.Scale) * 0.5f;
nuclear@3 275 OrthoPixelOffset = orthoPixelOffset * 2.0f / FovPixels;
nuclear@3 276 }
nuclear@3 277
nuclear@3 278 void StereoConfig::updateEyeParams()
nuclear@3 279 {
nuclear@3 280 // Projection matrix for the center eye, which the left/right matrices are based on.
nuclear@3 281 Matrix4f projCenter = Matrix4f::PerspectiveRH(YFov, Aspect, 0.01f, 2000.0f);
nuclear@3 282
nuclear@3 283 switch(Mode)
nuclear@3 284 {
nuclear@3 285 case Stereo_None:
nuclear@3 286 {
nuclear@3 287 EyeRenderParams[0].Init(StereoEye_Center, FullView, 0, projCenter, OrthoCenter);
nuclear@3 288 }
nuclear@3 289 break;
nuclear@3 290
nuclear@3 291 case Stereo_LeftRight_Multipass:
nuclear@3 292 {
nuclear@3 293 Matrix4f projLeft = Matrix4f::Translation(ProjectionCenterOffset, 0, 0) * projCenter,
nuclear@3 294 projRight = Matrix4f::Translation(-ProjectionCenterOffset, 0, 0) * projCenter;
nuclear@3 295
nuclear@3 296 EyeRenderParams[0].Init(StereoEye_Left,
nuclear@3 297 Viewport(FullView.x, FullView.y, FullView.w/2, FullView.h),
nuclear@3 298 +InterpupillaryDistance * 0.5f, // World view shift.
nuclear@3 299 projLeft, OrthoCenter * Matrix4f::Translation(OrthoPixelOffset, 0, 0),
nuclear@3 300 &Distortion);
nuclear@3 301 EyeRenderParams[1].Init(StereoEye_Right,
nuclear@3 302 Viewport(FullView.x + FullView.w/2, FullView.y, FullView.w/2, FullView.h),
nuclear@3 303 -InterpupillaryDistance * 0.5f,
nuclear@3 304 projRight, OrthoCenter * Matrix4f::Translation(-OrthoPixelOffset, 0, 0),
nuclear@3 305 &Distortion);
nuclear@3 306 }
nuclear@3 307 break;
nuclear@3 308 }
nuclear@3 309
nuclear@3 310 }
nuclear@3 311
nuclear@3 312
nuclear@3 313 }}} // OVR::Util::Render
nuclear@3 314