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
|