Permalink
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
Cannot retrieve contributors at this time.
Cannot retrieve contributors at this time
| #ifndef AL_OMNISTEREO_H | |
| #define AL_OMNISTEREO_H | |
| #include "allocore/math/al_Constants.hpp" | |
| #include "allocore/graphics/al_Lens.hpp" | |
| #include "allocore/graphics/al_Shader.hpp" | |
| #include "allocore/graphics/al_Texture.hpp" | |
| namespace al { | |
| // Object to encapsulate rendering omni-stereo worlds via cube-maps: | |
| class OmniStereo { | |
| public: | |
| // prefix this string to every vertex shader used in rendering the scene | |
| // use it as e.g.: | |
| // gl_Position = omni_cube(gl_ModelViewMatrix * gl_Vertex); | |
| // also be sure to call omni.uniforms(shader) in the OmniStereoDrawable | |
| // callback | |
| static std::string glsl() { | |
| return R"( | |
| // @omni_eye: the eye parallax distance. | |
| // This will be zero for mono, and positive/negative for right/left | |
| // eyes. | |
| // Pass this uniform to the shader in the OmniStereoDrawable | |
| // callback | |
| uniform float omni_eye; | |
| // @omni_radius: the radius of the allosphere in OpenGL units. | |
| // This will be infinity for the original layout (we default to 1e10). | |
| uniform float omni_radius; | |
| // @omni_face: the GL_TEXTURE_CUBE_MAP face being rendered. | |
| // For a typical forward-facing view, this should == 5. | |
| // Pass this uniform to the shader in the OmniStereoDrawable | |
| // callback | |
| uniform int omni_face; | |
| // @omni_near: the near clipping plane. | |
| uniform float omni_near; | |
| // @omni_far: the far clipping plane. | |
| uniform float omni_far; | |
| // omni_render(vertex) | |
| // @vertex: the eye-space vertex to be rendered. | |
| // Typically gl_Position = omni_render(gl_ModelViewMatrix * | |
| // gl_Vertex); | |
| vec4 omni_render(in vec4 vertex) { | |
| float l = length(vertex.xz); | |
| vec3 vn = normalize(vertex.xyz); | |
| // Precise formula. | |
| float displacement = omni_eye * | |
| (omni_radius * omni_radius - | |
| sqrt(l * l * omni_radius * omni_radius + | |
| omni_eye * omni_eye * (omni_radius * omni_radius - l * l))) / | |
| (omni_radius * omni_radius - omni_eye * omni_eye); | |
| // Approximation, safe if omni_radius / omni_eye is very large, which is true for the allosphere. | |
| // float displacement = omni_eye * (1.0 - l / omni_radius); | |
| // Displace vertex. | |
| vertex.xz += vec2(displacement * vn.z, displacement * -vn.x); | |
| // convert eye-space into cubemap-space: | |
| // GL_TEXTURE_CUBE_MAP_POSITIVE_X | |
| if (omni_face == 0) { | |
| vertex.xyz = vec3(-vertex.z, -vertex.y, -vertex.x); | |
| } | |
| // GL_TEXTURE_CUBE_MAP_NEGATIVE_X | |
| else if (omni_face == 1) { | |
| vertex.xyz = vec3(vertex.z, -vertex.y, vertex.x); | |
| } | |
| // GL_TEXTURE_CUBE_MAP_POSITIVE_Y | |
| else if (omni_face == 2) { | |
| vertex.xyz = vec3(vertex.x, vertex.z, -vertex.y); | |
| } | |
| // GL_TEXTURE_CUBE_MAP_NEGATIVE_Y | |
| else if (omni_face == 3) { | |
| vertex.xyz = vec3(vertex.x, -vertex.z, vertex.y); | |
| } | |
| // GL_TEXTURE_CUBE_MAP_POSITIVE_Z | |
| else if (omni_face == 4) { | |
| vertex.xyz = vec3(vertex.x, -vertex.y, -vertex.z); | |
| } | |
| // GL_TEXTURE_CUBE_MAP_NEGATIVE_Z | |
| else { | |
| vertex.xyz = vec3(-vertex.x, -vertex.y, vertex.z); | |
| } | |
| // convert into screen-space: | |
| // simplified perspective projection since fovy = 90 and aspect = 1 | |
| vertex.zw = vec2((vertex.z * (omni_far + omni_near) + | |
| vertex.w * omni_far * omni_near * 2.) / | |
| (omni_near - omni_far), | |
| -vertex.z); | |
| return vertex; | |
| })"; | |
| } | |
| /// Abstract base class for any object that can be rendered via OmniStereo: | |
| class Drawable { | |
| public: | |
| /// Place drawing code here | |
| virtual void onDrawOmni(OmniStereo& omni) = 0; | |
| virtual ~Drawable() {} | |
| }; | |
| /// Encapsulate the trio of fractional viewport, warp & blend maps: | |
| class Projection { | |
| public: | |
| struct Parameters { | |
| float projnum; // ID of the projector | |
| float width, height; // width/height in pixels | |
| Vec3f projector_position, screen_center, normal_unit, x_vec, y_vec; | |
| float screen_radius, bridge_radius, unused0; | |
| }; | |
| Projection(); | |
| void onCreate(); | |
| // load warp/blend from disk: | |
| void readBlend(std::string path); | |
| void readWarp(std::string path); | |
| void readParameters(std::string path, bool verbose = true); | |
| void initParameters(bool verbose = true); | |
| // adjust the registration position: | |
| void registrationPosition(const Vec3d& pos); | |
| Texture& blend() { return mBlend; } | |
| Texture& warp() { return mWarp; } | |
| Viewport& viewport() { return mViewport; } | |
| void updatedWarp(); | |
| Parameters params; | |
| // derived: | |
| Vec3f x_unit, y_unit; | |
| float x_pixel, y_pixel; | |
| float x_offset, y_offset; | |
| Vec3d position; | |
| // TODO: remove this | |
| int warpwidth, warpheight; | |
| protected: | |
| Texture mBlend, mWarp; | |
| Viewport mViewport; | |
| // the position/orientation of the raw map data relative to the real world | |
| Pose mRegistration; | |
| // the raw warp data: | |
| float* t; | |
| float* u; | |
| float* v; | |
| }; | |
| /// Stereographic mode | |
| enum StereoMode { | |
| MONO = 0, | |
| SEQUENTIAL, /**< Alternate left/right eye frames */ | |
| ACTIVE, /**< Active quad-buffered stereo */ | |
| DUAL, /**< Dual side-by-side stereo */ | |
| ANAGLYPH, /**< Red (left eye) / cyan (right eye) stereo */ | |
| LEFT_EYE, /**< Left eye only */ | |
| RIGHT_EYE /**< Right eye only */ | |
| }; | |
| /// Anaglyph mode | |
| enum AnaglyphMode { | |
| RED_BLUE = 0, /**< */ | |
| RED_GREEN, /**< */ | |
| RED_CYAN, /**< */ | |
| BLUE_RED, /**< */ | |
| GREEN_RED, /**< */ | |
| CYAN_RED /**< */ | |
| }; | |
| enum WarpMode { | |
| FISHEYE, | |
| CYLINDER, | |
| RECT | |
| }; | |
| enum BlendMode { | |
| NOBLEND, | |
| SOFTEDGE | |
| }; | |
| // @resolution sets the resolution of the cube textures / render buffers: | |
| OmniStereo(unsigned resolution = 1024, bool useMipMaps = true); | |
| // @resolution should be a power of 2 | |
| OmniStereo& resolution(unsigned resolution); | |
| unsigned resolution() { return mResolution; } | |
| // configure the projections according to files | |
| OmniStereo& configure(std::string configpath, | |
| std::string configname = "default"); | |
| // configure generatively: | |
| OmniStereo& configure(WarpMode wm, float aspect = 2., float fovy = M_PI); | |
| OmniStereo& configure(BlendMode bm); | |
| // capture a scene to the cubemap textures | |
| // this is likely to be an expensive call, as it will render the scene | |
| // six (twelve for stereo) times, by calling back into @draw | |
| // @draw should render without changing the viewport, modelview or projection | |
| // matrices | |
| // @lens is used for near/far clipping planes, and eye separation | |
| // @pose sets the camera position/orientation | |
| void capture(OmniStereo::Drawable& drawable, const Lens& lens, | |
| const Pose& pose); | |
| // render the captured scene to multiple warp maps and viewports | |
| // @viewport is the pixel dimensions of the window | |
| void draw(const Lens& lens, const Pose& pose, const Viewport& vp); | |
| // typically they would be combined like this: | |
| void onFrame(OmniStereo::Drawable& drawable, const Lens& lens, | |
| const Pose& pose, const Viewport& vp) { | |
| capture(drawable, lens, pose); | |
| draw(lens, pose, vp); | |
| } | |
| // render front-view only (bypass FBO) | |
| void onFrameFront(OmniStereo::Drawable& drawable, const Lens& lens, | |
| const Pose& pose, const Viewport& vp); | |
| // send the proper uniforms to the shader: | |
| void uniforms(ShaderProgram& program) const; | |
| // enable/disable stereographic mode: | |
| OmniStereo& stereo(bool b) { | |
| mStereo = b; | |
| return *this; | |
| } | |
| bool stereo() const { return mStereo; } | |
| OmniStereo& mode(StereoMode m) { | |
| mMode = m; | |
| return *this; | |
| } | |
| StereoMode mode() { return mMode; } | |
| // returns true if configured for active stereo: | |
| bool activeStereo() { return mMode == ACTIVE; } | |
| // returns true if config file suggested fullscreen by default | |
| bool fullScreen() { return mFullScreen; } | |
| // get/set the background color | |
| Color& clearColor() { return mClearColor; } | |
| // get individual projector configurations: | |
| Projection& projection(int i) { return mProjections[i]; } | |
| int numProjections() const { return mNumProjections; } | |
| // useful accessors: | |
| GLuint texture() const { return mTex[0]; } | |
| GLuint textureLeft() const { return mTex[0]; } | |
| GLuint textureRight() const { return mTex[1]; } | |
| // the current face being rendered: | |
| int face() const { return mFace; } | |
| int currentEye() const { return mCurrentEye; } | |
| // the current eye parallax | |
| // (positive for right eye, negative for left, zero for mono): | |
| float eye() const { return mEyeParallax; } | |
| // the radius of the sphere in OpenGL units, default to 1e10, when we can ignore the effect. | |
| float sphereRadius() const { return mSphereRadius; } | |
| float sphereRadius(float value) { return mSphereRadius = value; } | |
| float near() const { return mNear; } | |
| float far() const { return mFar; } | |
| // create GPU resources: | |
| void onCreate(); | |
| void onDestroy(); | |
| // configure the warp maps | |
| void loadWarps(std::string calibrationpath, std::string hostname = "default"); | |
| // debugging: | |
| void drawWarp(const Viewport& vp); | |
| void drawBlend(const Viewport& vp); | |
| void drawSphereMap(Texture& map, const Lens& lens, const Pose& pose, | |
| const Viewport& vp); | |
| void drawDemo(const Lens& lens, const Pose& pose, const Viewport& vp); | |
| // adjust the registration position: | |
| void registrationPosition(const Vec3d& pos) { | |
| for (int i = 0; i < numProjections(); i++) { | |
| projection(i).registrationPosition(pos); | |
| } | |
| } | |
| protected: | |
| // supports up to 4 warps/viewports | |
| Projection mProjections[4]; | |
| typedef void (OmniStereo::*DrawMethod)(const Pose& pose, double eye); | |
| void drawEye(const Pose& pose, double eye); | |
| void drawQuadEye(const Pose& pose, double eye); | |
| void drawDemoEye(const Pose& pose, double eye); | |
| template <DrawMethod F> | |
| void drawStereo(const Lens& lens, const Pose& pose, const Viewport& viewport); | |
| void drawQuad(); | |
| void capture_eye(GLuint& tex, OmniStereo::Drawable& drawable); | |
| GLuint mTex[2]; // the cube map textures | |
| GLuint mFbo; | |
| GLuint mRbo; // TODO: alternative depth cube-map texture option? | |
| ShaderProgram mCubeProgram, mSphereProgram, mWarpProgram, mDemoProgram; | |
| Mesh mQuad; | |
| Graphics gl; | |
| Matrix4d mModelView; | |
| Color mClearColor; | |
| // these become shader uniforms: | |
| int mFace, mCurrentEye; | |
| float mSphereRadius; // The radius of the sphere in OpenGL units. | |
| float mEyeParallax, mNear, mFar; | |
| unsigned mResolution; | |
| unsigned mNumProjections; | |
| int mFrame; | |
| StereoMode mMode; | |
| AnaglyphMode mAnaglyphMode; | |
| bool mStereo, mMipmap, mFullScreen; | |
| }; | |
| /* inline implementation */ | |
| inline void OmniStereo::uniforms(ShaderProgram& program) const { | |
| program.uniform("omni_face", mFace); | |
| program.uniform("omni_eye", mEyeParallax); | |
| program.uniform("omni_near", mNear); | |
| program.uniform("omni_far", mFar); | |
| program.uniform("omni_radius", mSphereRadius); | |
| gl.error("sending OmniStereo uniforms"); | |
| } | |
| template <OmniStereo::DrawMethod F> | |
| inline void OmniStereo::drawStereo(const Lens& lens, const Pose& pose, | |
| const Viewport& vp) { | |
| double eye = lens.eyeSep(); | |
| switch (mMode) { | |
| case SEQUENTIAL: | |
| if (mFrame & 1) { | |
| (this->*F)(pose, eye); | |
| } else { | |
| (this->*F)(pose, -eye); | |
| } | |
| break; | |
| case ACTIVE: | |
| glDrawBuffer(GL_BACK_RIGHT); | |
| gl.error("OmniStereo drawStereo GL_BACK_RIGHT"); | |
| (this->*F)(pose, eye); | |
| glDrawBuffer(GL_BACK_LEFT); | |
| gl.error("OmniStereo drawStereo GL_BACK_LEFT"); | |
| (this->*F)(pose, -eye); | |
| glDrawBuffer(GL_BACK); | |
| gl.error("OmniStereo drawStereo GL_BACK"); | |
| break; | |
| case DUAL: | |
| gl.viewport(vp.l + vp.w * 0.5, vp.b, vp.w * 0.5, vp.h); | |
| (this->*F)(pose, eye); | |
| gl.viewport(vp.l, vp.b, vp.w * 0.5, vp.h); | |
| (this->*F)(pose, -eye); | |
| break; | |
| case ANAGLYPH: | |
| switch (mAnaglyphMode) { | |
| case RED_BLUE: | |
| case RED_GREEN: | |
| case RED_CYAN: | |
| glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE); | |
| break; | |
| case BLUE_RED: | |
| glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_TRUE); | |
| break; | |
| case GREEN_RED: | |
| glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE); | |
| break; | |
| case CYAN_RED: | |
| glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_TRUE); | |
| break; | |
| default: | |
| glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); | |
| } | |
| (this->*F)(pose, eye); | |
| switch (mAnaglyphMode) { | |
| case RED_BLUE: | |
| glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_TRUE); | |
| break; | |
| case RED_GREEN: | |
| glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE); | |
| break; | |
| case RED_CYAN: | |
| glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_TRUE); | |
| break; | |
| case BLUE_RED: | |
| case GREEN_RED: | |
| case CYAN_RED: | |
| glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE); | |
| break; | |
| default: | |
| glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); | |
| } | |
| // clear depth before this pass: | |
| gl.depthMask(1); | |
| gl.depthTesting(1); | |
| gl.clear(gl.DEPTH_BUFFER_BIT); | |
| (this->*F)(pose, -eye); | |
| break; | |
| case RIGHT_EYE: | |
| (this->*F)(pose, eye); | |
| break; | |
| case LEFT_EYE: | |
| (this->*F)(pose, -eye); | |
| break; | |
| case MONO: | |
| default: | |
| (this->*F)(pose, 0); | |
| break; | |
| } | |
| } | |
| } // al:: | |
| #endif |