Add a StereoCombiner subclass for the Oculus Rift
authorMikko Rasa <tdb@tdb.fi>
Tue, 18 Jun 2013 22:11:31 +0000 (01:11 +0300)
committerMikko Rasa <tdb@tdb.fi>
Fri, 13 Sep 2013 11:36:14 +0000 (14:36 +0300)
source/oculusriftcombiner.cpp [new file with mode: 0644]
source/oculusriftcombiner.h [new file with mode: 0644]
source/stereocombiner.cpp
source/stereocombiner.h
source/stereoview.cpp

diff --git a/source/oculusriftcombiner.cpp b/source/oculusriftcombiner.cpp
new file mode 100644 (file)
index 0000000..9d985d9
--- /dev/null
@@ -0,0 +1,170 @@
+#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
diff --git a/source/oculusriftcombiner.h b/source/oculusriftcombiner.h
new file mode 100644 (file)
index 0000000..ede2fe9
--- /dev/null
@@ -0,0 +1,50 @@
+#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
index be0b01e986a76d99bd1abc25a2bade3b2e45e152..ba71818d743c56ae3d6b464942d48f5e6c5f0b2c 100644 (file)
@@ -6,7 +6,8 @@ namespace GL {
 StereoCombiner::StereoCombiner():
        width_div(1),
        height_div(1),
-       keep_aspect(false)
+       keep_aspect(false),
+       oversize(1.0f)
 { }
 
 } // namespace GL
index a4b91bfbf9edfa82c477301a5df26e74e0961911..5fb8dec7e5154fd716e2a93419ec641f2c204a6a 100644 (file)
@@ -15,6 +15,7 @@ protected:
        unsigned height_div;
        bool keep_aspect;
        Geometry::Angle<float> fov;
+       float oversize;
 
        StereoCombiner();
 public:
@@ -24,6 +25,7 @@ public:
        unsigned get_height_divisor() const { return height_div; }
        bool is_aspect_kept() const { return keep_aspect; }
        const Geometry::Angle<float> &get_field_of_view() const { return fov; }
+       float get_oversize() const { return oversize; }
 
        virtual void render(const Texture2D &, const Texture2D &) const = 0;
 };
index 419e95c5bde699a31e4bf9545298141d49cbe973..53c6445f368b28266aba2ca372ab40a52fa3a4e6 100644 (file)
@@ -22,8 +22,8 @@ void StereoView::set_combiner(const StereoCombiner &c)
 {
        combiner = &c;
 
-       unsigned w = width/combiner->get_width_divisor();
-       unsigned h = height/combiner->get_height_divisor();
+       unsigned w = width/combiner->get_width_divisor()*combiner->get_oversize();
+       unsigned h = height/combiner->get_height_divisor()*combiner->get_oversize();
        left.create_target(w, h);
        right.create_target(w, h);
 }