Add a StereoCombiner subclass for the Oculus Rift
[libs/vr.git] / source / oculusriftcombiner.cpp
1 #include <cmath>
2 #include "meshbuilder.h"
3 #include "oculusriftcombiner.h"
4 #include "texture2d.h"
5
6 using namespace std;
7
8 namespace {
9
10 const char vs_source[] =
11         "uniform float offset;\n"
12         "uniform vec2 lens_center;\n"
13         "uniform vec3 scale;\n"
14         "varying vec2 texcoord;\n"
15         "void main()\n"
16         "{\n"
17         "       gl_Position = vec4(gl_Vertex.x*0.5+offset, gl_Vertex.yzw);\n"
18         "       texcoord = (gl_Vertex.xy*0.5+0.5-lens_center)*scale.xy;\n"
19         "}\n";
20
21 const char fs_source[] =
22         "uniform sampler2D texture;\n"
23         "uniform vec4 distortion;\n"
24         "uniform vec2 eye_center;\n"
25         "uniform vec3 scale;\n"
26         "varying vec2 texcoord;\n"
27         "vec2 distort(vec2 coord)\n"
28         "{\n"
29         "       float r_sq = dot(coord, coord);\n"
30         "       return coord*dot(distortion, vec4(1.0, r_sq, r_sq*r_sq, r_sq*r_sq*r_sq));\n"
31         "}\n"
32         "void main()\n"
33         "{\n"
34         "       vec2 dtc = (distort(texcoord)-eye_center)/(scale.xy*scale.z)+0.5;\n"
35         "       if(dtc.x<0.0 || dtc.y<0.0 || dtc.x>1.0 || dtc.y>1.0)\n"
36         "               gl_FragColor = vec4(0.0);\n"
37         "       else\n"
38         "               gl_FragColor = texture2D(texture, dtc);\n"
39         "}\n";
40
41 }
42
43 namespace Msp {
44 namespace GL {
45
46 OculusRiftCombiner::OculusRiftCombiner():
47         mesh(VERTEX2),
48         shprog(vs_source, fs_source),
49         // Default values copied from the SDK
50         view_distance(0.438f),
51         lens_separation(0.424f),
52         eye_separation(0.42735f),
53         fill_factor(0.95f)
54 {
55         width_div = 2;
56
57         left_shdata.uniform("texture", 0);
58         left_shdata.uniform("offset", -0.5f);
59         right_shdata.uniform("texture", 0);
60         right_shdata.uniform("offset", 0.5f);
61
62         // This will also call update_parameters
63         set_distortion(1.0f, 0.22f, 0.24f);
64
65         MeshBuilder bld(mesh);
66         bld.begin(TRIANGLE_STRIP);
67         bld.vertex(-1, 1);
68         bld.vertex(-1, -1);
69         bld.vertex(1, 1);
70         bld.vertex(1, -1);
71         bld.end();
72 }
73
74 void OculusRiftCombiner::set_view_distance(float d)
75 {
76         view_distance = d;
77         update_parameters();
78 }
79
80 void OculusRiftCombiner::set_lens_separation(float s)
81 {
82         lens_separation = s;
83         update_parameters();
84 }
85
86 void OculusRiftCombiner::set_eye_separation(float s)
87 {
88         eye_separation = s;
89         update_parameters();
90 }
91
92 void OculusRiftCombiner::set_distortion(float d0, float d1, float d2, float d3)
93 {
94         distortion[0] = d0;
95         distortion[1] = d1;
96         distortion[2] = d2;
97         distortion[3] = d3;
98
99         update_parameters();
100 }
101
102 void OculusRiftCombiner::set_fill_factor(float f)
103 {
104         fill_factor = f;
105         update_parameters();
106 }
107
108 void OculusRiftCombiner::update_parameters()
109 {
110         left_shdata.uniform4("distortion", distortion);
111         right_shdata.uniform4("distortion", distortion);
112
113         // Set lens center positions, in output texture coordinates
114         left_shdata.uniform("lens_center", 1.0f-lens_separation, 0.5);
115         right_shdata.uniform("lens_center", lens_separation, 0.5);
116
117         /* Compute distance between eye and lens centers, in sampling texture
118         coordinates. */
119         float eye_offset = distort((eye_separation-lens_separation)*2);
120         left_shdata.uniform("eye_center", -eye_offset, 0.0f);
121         right_shdata.uniform("eye_center", eye_offset, 0.0f);
122
123         /* Determine the necessary scaling factor to avoid quality degradation in
124         the center of the screen. */
125         float horiz_oversize = distort((fill_factor-lens_separation)*2)-eye_offset;
126         float vert_oversize = distort(1.25f*fill_factor)/(1.25f*fill_factor);
127         oversize = min(horiz_oversize, vert_oversize);
128
129         left_shdata.uniform("scale", 2.0f, 2.5f, oversize);
130         right_shdata.uniform("scale", 2.0f, 2.5f, oversize);
131
132         fov = Geometry::atan(oversize*0.625f/view_distance)*2.0f;
133 }
134
135 float OculusRiftCombiner::distort(float r) const
136 {
137         float r_sq = r*r;
138         return r*(distortion[0]+(distortion[1]+(distortion[2]+distortion[3]*r_sq)*r_sq)*r_sq);
139 }
140
141 float OculusRiftCombiner::undistort(float r) const
142 {
143         float x = r;
144         while(1)
145         {
146                 float y = distort(x);
147                 if(abs(r-y)<1e-5)
148                         return x;
149
150                 float x_sq = x*x;
151                 float d = distortion[0]+(3*distortion[1]+(5*distortion[2]+7*distortion[3]*x_sq)*x_sq)*x_sq;
152                 x -= (y-r)/d;
153         }
154 }
155
156 void OculusRiftCombiner::render(const Texture2D &left, const Texture2D &right) const
157 {
158         Bind bind_shprog(shprog);
159
160         Bind bind_tex(left);
161         left_shdata.apply();
162         mesh.draw();
163
164         right.bind();
165         right_shdata.apply();
166         mesh.draw();
167 }
168
169 } // namespace GL
170 } // namespace Msp