From a3d248c7db9f04919ed5e4053f6e2a2f776cec47 Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Wed, 19 Jun 2013 01:11:31 +0300 Subject: [PATCH] Add a StereoCombiner subclass for the Oculus Rift --- source/oculusriftcombiner.cpp | 170 ++++++++++++++++++++++++++++++++++ source/oculusriftcombiner.h | 50 ++++++++++ source/stereocombiner.cpp | 3 +- source/stereocombiner.h | 2 + source/stereoview.cpp | 4 +- 5 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 source/oculusriftcombiner.cpp create mode 100644 source/oculusriftcombiner.h diff --git a/source/oculusriftcombiner.cpp b/source/oculusriftcombiner.cpp new file mode 100644 index 00000000..9d985d92 --- /dev/null +++ b/source/oculusriftcombiner.cpp @@ -0,0 +1,170 @@ +#include +#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 index 00000000..ede2fe91 --- /dev/null +++ b/source/oculusriftcombiner.h @@ -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 diff --git a/source/stereocombiner.cpp b/source/stereocombiner.cpp index be0b01e9..ba71818d 100644 --- a/source/stereocombiner.cpp +++ b/source/stereocombiner.cpp @@ -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 diff --git a/source/stereocombiner.h b/source/stereocombiner.h index a4b91bfb..5fb8dec7 100644 --- a/source/stereocombiner.h +++ b/source/stereocombiner.h @@ -15,6 +15,7 @@ protected: unsigned height_div; bool keep_aspect; Geometry::Angle 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 &get_field_of_view() const { return fov; } + float get_oversize() const { return oversize; } virtual void render(const Texture2D &, const Texture2D &) const = 0; }; diff --git a/source/stereoview.cpp b/source/stereoview.cpp index 419e95c5..53c6445f 100644 --- a/source/stereoview.cpp +++ b/source/stereoview.cpp @@ -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); } -- 2.45.2