--- /dev/null
+#include <cmath>
+#include "meshbuilder.h"
+#include "oculusriftcombiner.h"
+#include "texture2d.h"
+
+using namespace std;
+
+namespace {
+
+const char vs_source[] =
+ "uniform float offset;\n"
+ "uniform vec2 lens_center;\n"
+ "uniform vec3 scale;\n"
+ "varying vec2 texcoord;\n"
+ "void main()\n"
+ "{\n"
+ " gl_Position = vec4(gl_Vertex.x*0.5+offset, gl_Vertex.yzw);\n"
+ " texcoord = (gl_Vertex.xy*0.5+0.5-lens_center)*scale.xy;\n"
+ "}\n";
+
+const char fs_source[] =
+ "uniform sampler2D texture;\n"
+ "uniform vec4 distortion;\n"
+ "uniform vec2 eye_center;\n"
+ "uniform vec3 scale;\n"
+ "varying vec2 texcoord;\n"
+ "vec2 distort(vec2 coord)\n"
+ "{\n"
+ " float r_sq = dot(coord, coord);\n"
+ " return coord*dot(distortion, vec4(1.0, r_sq, r_sq*r_sq, r_sq*r_sq*r_sq));\n"
+ "}\n"
+ "void main()\n"
+ "{\n"
+ " vec2 dtc = (distort(texcoord)-eye_center)/(scale.xy*scale.z)+0.5;\n"
+ " if(dtc.x<0.0 || dtc.y<0.0 || dtc.x>1.0 || dtc.y>1.0)\n"
+ " gl_FragColor = vec4(0.0);\n"
+ " else\n"
+ " gl_FragColor = texture2D(texture, dtc);\n"
+ "}\n";
+
+}
+
+namespace Msp {
+namespace GL {
+
+OculusRiftCombiner::OculusRiftCombiner():
+ mesh(VERTEX2),
+ shprog(vs_source, fs_source),
+ // Default values copied from the SDK
+ view_distance(0.438f),
+ lens_separation(0.424f),
+ eye_separation(0.42735f),
+ fill_factor(0.95f)
+{
+ width_div = 2;
+
+ left_shdata.uniform("texture", 0);
+ left_shdata.uniform("offset", -0.5f);
+ right_shdata.uniform("texture", 0);
+ right_shdata.uniform("offset", 0.5f);
+
+ // This will also call update_parameters
+ set_distortion(1.0f, 0.22f, 0.24f);
+
+ MeshBuilder bld(mesh);
+ bld.begin(TRIANGLE_STRIP);
+ bld.vertex(-1, 1);
+ bld.vertex(-1, -1);
+ bld.vertex(1, 1);
+ bld.vertex(1, -1);
+ bld.end();
+}
+
+void OculusRiftCombiner::set_view_distance(float d)
+{
+ view_distance = d;
+ update_parameters();
+}
+
+void OculusRiftCombiner::set_lens_separation(float s)
+{
+ lens_separation = s;
+ update_parameters();
+}
+
+void OculusRiftCombiner::set_eye_separation(float s)
+{
+ eye_separation = s;
+ update_parameters();
+}
+
+void OculusRiftCombiner::set_distortion(float d0, float d1, float d2, float d3)
+{
+ distortion[0] = d0;
+ distortion[1] = d1;
+ distortion[2] = d2;
+ distortion[3] = d3;
+
+ update_parameters();
+}
+
+void OculusRiftCombiner::set_fill_factor(float f)
+{
+ fill_factor = f;
+ update_parameters();
+}
+
+void OculusRiftCombiner::update_parameters()
+{
+ left_shdata.uniform4("distortion", distortion);
+ right_shdata.uniform4("distortion", distortion);
+
+ // Set lens center positions, in output texture coordinates
+ left_shdata.uniform("lens_center", 1.0f-lens_separation, 0.5);
+ right_shdata.uniform("lens_center", lens_separation, 0.5);
+
+ /* Compute distance between eye and lens centers, in sampling texture
+ coordinates. */
+ float eye_offset = distort((eye_separation-lens_separation)*2);
+ left_shdata.uniform("eye_center", -eye_offset, 0.0f);
+ right_shdata.uniform("eye_center", eye_offset, 0.0f);
+
+ /* Determine the necessary scaling factor to avoid quality degradation in
+ the center of the screen. */
+ float horiz_oversize = distort((fill_factor-lens_separation)*2)-eye_offset;
+ float vert_oversize = distort(1.25f*fill_factor)/(1.25f*fill_factor);
+ oversize = min(horiz_oversize, vert_oversize);
+
+ left_shdata.uniform("scale", 2.0f, 2.5f, oversize);
+ right_shdata.uniform("scale", 2.0f, 2.5f, oversize);
+
+ fov = Geometry::atan(oversize*0.625f/view_distance)*2.0f;
+}
+
+float OculusRiftCombiner::distort(float r) const
+{
+ float r_sq = r*r;
+ return r*(distortion[0]+(distortion[1]+(distortion[2]+distortion[3]*r_sq)*r_sq)*r_sq);
+}
+
+float OculusRiftCombiner::undistort(float r) const
+{
+ float x = r;
+ while(1)
+ {
+ float y = distort(x);
+ if(abs(r-y)<1e-5)
+ return x;
+
+ float x_sq = x*x;
+ float d = distortion[0]+(3*distortion[1]+(5*distortion[2]+7*distortion[3]*x_sq)*x_sq)*x_sq;
+ x -= (y-r)/d;
+ }
+}
+
+void OculusRiftCombiner::render(const Texture2D &left, const Texture2D &right) const
+{
+ Bind bind_shprog(shprog);
+
+ Bind bind_tex(left);
+ left_shdata.apply();
+ mesh.draw();
+
+ right.bind();
+ right_shdata.apply();
+ mesh.draw();
+}
+
+} // namespace GL
+} // namespace Msp
--- /dev/null
+#ifndef MSP_GL_OCULUSRIFTCOMBINER_H_
+#define MSP_GL_OCULUSRIFTCOMBINER_H_
+
+#include "mesh.h"
+#include "program.h"
+#include "programdata.h"
+#include "stereocombiner.h"
+
+namespace Msp {
+namespace GL {
+
+/**
+Presents a stereo view in a way suitable for an Oculus Rift HMD. All distances
+are specified in multiples of the screen width.
+*/
+class OculusRiftCombiner: public StereoCombiner
+{
+private:
+ Mesh mesh;
+ Program shprog;
+ ProgramData left_shdata;
+ ProgramData right_shdata;
+ float view_distance;
+ float lens_separation;
+ float eye_separation;
+ float distortion[4];
+ float fill_factor;
+
+public:
+ OculusRiftCombiner();
+
+ void set_view_distance(float);
+ void set_lens_separation(float);
+ void set_eye_separation(float);
+ void set_distortion(float = 1.0f, float = 0.0f, float = 0.0f, float = 0.0f);
+ void set_fill_factor(float);
+private:
+ void update_parameters();
+
+ float distort(float) const;
+ float undistort(float) const;
+
+public:
+ virtual void render(const Texture2D &, const Texture2D &) const;
+};
+
+} // namespace GL
+} // namespace Msp
+
+#endif