oculus1

annotate libovr/Src/Util/Util_Render_Stereo.cpp @ 1:e2f9e4603129

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