osgXR exposes various preprocessor definitions and macros to GLSL shaders and the application to allow them to correctly handle the various multiview rendering modes which osgXR supports.
The documentation & examples below will use the osgXR definitions & macros directly in order to demonstrate their use, however it is likely that the cleanest approach to using them is to contain them in a GLSL file for each shader stage, and abstract the macros as functions for use by other shaders, along with fallback implementations. This should avoid most of the ifdefs in normal shader code, as well as allowing common GLSL code to use the appropriate macros depending on mode. Examples of these can be found here:
Shared view space
: A space near to and oriented similarly to all the OpenXR views, used by
cameras representing multiple OpenXR views by one of the multi-pass rendering
modes described below. Transform a model space coordinate to this space by
multiplying it by osg_ModelViewMatrix.
Per-view eye space : The view space for each OpenXR view. May be both rotated and offset from the shared view space.
Per-view projection space : The projection space for each OpenXR view. May be asymmetrical (FOV angle differs in opposite directions, e.g. FOV to left of centre is likely to be greater than to right of centre for left eye views),
The swapchain textures which are passed to OpenXR for display in a VR headset can use a number of different layouts, depending on the mode of multiview rendering.
Intermediate frame buffers may also have to follow a similar layout, in which case shaders which sample from them may need modification.
Per-view intermediate buffers with fixed dimensions (rather than scaling with the VR view dimensions) also need to follow a similar layout, but without the possibility of differing sizes per view.
This swapchain mode uses separate textures to store the frame buffer of each OpenXR view. It is only possible with the Slave Cameras multiview rendering mode, as that is the only mode using fully separate passes for each view.
In this mode, each osgXR view is expected to use its own simple intermediate buffers, or to reuse the same frame buffer multiple times.
To indicate that multiple swapchains mode is supported by the application and
its shaders, and to allow this swapchain mode to be chosen, set it as an allowed
swapchain mode in osgXR::Settings, e.g. from a derived class of
osgXR::Manager you can do this:
_settings->allowSwapchainMode(osgXR::Settings::SWAPCHAIN_MULTIPLE);This also prevents osgXR from defaulting to allowing both Multiple and Single (side-by-side) swapchain modes for backwards compatibility when no other swapchain modes are explicitly allowed.
This swapchain mode uses a single texture to store multiple frame buffers, with each view being rendered to a viewport in the texture, usually stereo with left and right frame buffers placed side-by-side.
This is the required swapchain layout for the SceneView multiview rendering mode (which requires stereo views), but is also usable with Slave Cameras and Geometry Shaders.
Care must be taken to transform texture coordinates into the appropriate
viewport before sampling intermediate buffers, either in the vertex & geometry
shader (see OSGXR_VERT_MVR_TEXCOORD & OSGXR_GEOM_MVR_TEXCOORD for frame
buffers and OSGXR_VERT_MVB_TEXCOORD & OSGXR_GEOM_MVB_TEXCOORD for fixed
buffers), or in the fragment shader (see OSGXR_FRAG_MVR_TEXCOORD for frame
buffers and OSGXR_FRAG_MVB_TEXCOORD for fixed buffers).
To indicate that side-by-side swapchains are supported by the application and
its shaders, and to allow this swapchain mode to be chosen, set it as an allowed
swapchain mode in osgXR::Settings, e.g. from a derived class of
osgXR::Manager you can do this:
_settings->allowSwapchainMode(osgXR::Settings::SWAPCHAIN_SINGLE);This also prevents osgXR from defaulting to allowing both Multiple and Single (side-by-side) swapchain modes for backwards compatibility when no other swapchain modes are explicitly allowed.
This swapchain mode uses a 2D array texture to store multiple frame buffers, with each view being rendered to a viewport in a single layer of the texture (in case they have different resolutions).
This is the required swapchain layout for the OVR multiview rendering mode, but is also usable with Slave Cameras and Geometry Shaders.
Care must be taken to:
- Transform texture coordinates into the appropriate viewport before sampling intermediate frame buffers as for Single (side-by-side) above.
- Use a sampler2DArray when sampling these buffers when layered rendering is in use.
- Provide the Z texture coordinate from application specific defines as provided by osgXR::View.
To indicate that layered swapchains are supported by the application and its
shaders, and to allow this swapchain mode to be chosen, set it as an allowed
swapchain mode in osgXR::Settings, e.g. from a derived class of
osgXR::Manager you can do this:
_settings->allowSwapchainMode(osgXR::Settings::SWAPCHAIN_LAYERED);This also prevents osgXR from defaulting to allowing both Multiple and Single (side-by-side) swapchain modes for backwards compatibility when no other swapchain modes are explicitly allowed.
There are 4 multiview rendering modes supported by osgXR:
- Slave Cameras: Multi-pass rendering.
- SceneView: OpenSceneGraph's stereo rendering.
- Geometry Shaders: Single-pass multiview rendering.
- OVR Multiview: Hardware accelerated single-pass multiview rendering.
Geometry amplification: Multiple render passes
osgXR::View count: 1 per OpenXR view
OpenXR swapchain layout: Multiple, Single (side-by-side), or Layered
Intermediate buffer layout: Always multiple
Shader Changes:
- No shader changes are required. Each camera's view space matches the corresponding OpenXR view's eye space.
Performance:
- Slowest CPU performance.
- Due to the multiple render passes this puts more load on the CPU than the modes below.
To indicate that Slave Cameras mode is supported by the application and its
shaders, set it as an allowed VR mode in osgXR::Settings, e.g. from a derived
class of osgXR::Manager you can do this:
_settings->allowVRMode(osgXR::Settings::VRMODE_SLAVE_CAMERAS);This also prevents osgXR from defaulting to allowing both Slave Camera and SceneView modes for backwards compatibility when no other VR modes are explicitly allowed.
Geometry amplification: OpenSceneGraph (osgUtil::SceneView handles
multiple passes)
osgXR::View count: 1
OpenXR swapchain layout: Single (side-by-side)
Intermediate buffer layout: Same as swapchain - Single (side-by-side)
Shader changes:
- Transform texture coordinates used for sampling intermediate buffers.
Performance:
- Generally faster than slave cameras.
- There are still multiple passes which puts more load on the CPU than the single-pass modes below.
To indicate that SceneView mode is supported by the application and its shaders,
and to allow it to be chosen, set it as an allowed VR mode in osgXR::Settings,
e.g. from a derived class of osgXR::Manager you can do this:
_settings->allowVRMode(osgXR::Settings::VRMODE_SCENE_VIEW);This also prevents osgXR from defaulting to allowing both Slave Camera and SceneView mode for backwards compatibilitys when no other VR modes are explicitly allowed.
Texture coordinates used for sampling intermediate frame buffers and intermediate fixed-size buffers must be transformed into the appropriate half of the intermediate frame buffer texture. When sampling in the fragment shader, the transformation can be done either in the vertex shader (if the fragment shader otherwise uses the texture coordinate directly for sampling), or the fragment shader (if the texture coordinate already needs to be manipulated before sampling), but not both.
OSGXR_VERT_GLOBAL(shading passes): Used by vertex shaders to define osgXR uniforms for use byOSGXR_VERT_MVR_TEXCOORDandOSGXR_VERT_MVB_TEXCOORD.OSGXR_VERT_MVR_TEXCOORD(UV)(shading passes): Can be used by vertex shaders to transform frame buffer texture coordinates into the appropriate half of an intermediate frame buffer texture.OSGXR_VERT_MVB_TEXCOORD(UV)(shading passes): Can be used by vertex shaders to transform fixed-size buffer texture coordinates into the appropriate half of an intermediate fixed-size buffer texture.OSGXR_FRAG_GLOBAL(shading passes): Used by fragment shaders to define osgXR uniforms for use byOSGXR_FRAG_MVR_TEXCOORDandOSGXR_FRAG_MVB_TEXCOORD.OSGXR_FRAG_MVR_TEXCOORD(UV)(shading passes): Can be used by fragment shaders to transform frame buffer texture coordinates into the appropriate half of an intermediate frame buffer texture.OSGXR_FRAG_MVB_TEXCOORD(UV)(shading passes): Can be used by fragment shaders to transform fixed-size buffer texture coordinates into the appropriate half of an intermediate fixed-size buffer texture.
Any texture coordinates used for sampling intermediate frame buffers should be transformed into the appropriate half of that buffer (due to side-by-side frame buffers).
This can be done in the vertex shader with OSGXR_VERT_MVR_TEXCOORD:
// Vertex shader
...
#pragma import_defines (OSGXR_VERT_GLOBAL, OSGXR_VERT_MVR_TEXCOORD(UV))
...
#ifdef OSGXR_VERT_GLOBAL
OSGXR_VERT_GLOBAL
#endif
...
#ifdef OSGXR_VERT_MVR_TEXCOORD
texcoord = OSGXR_VERT_MVR_TEXCOORD(texcoord);
#endifA similar macro exists for fixed-size intermediate buffers
(OSGXR_VERT_MVB_TEXCOORD).
An alternative macro to OSGXR_VERT_MVR_TEXCOORD may instead be used in
fragment shaders, such as if the coordinates need processing in the fragment
shader before sampling:
// Fragment shader
...
#pragma import_defines (OSGXR_FRAG_GLOBAL, OSGXR_FRAG_MVR_TEXCOORD(UV))
...
#ifdef OSGXR_FRAG_GLOBAL
OSGXR_FRAG_GLOBAL
#endif
...
// Per-fragment calculation of texcoord prevents transformation in vertex
// or geometry stage before interpolation across primitives
texcoord = foo(...);
#ifdef OSGXR_FRAG_MVR_TEXCOORD
texcoord = OSGXR_FRAG_MVR_TEXCOORD(texcoord);
#endifA similar macro exists for fixed-size intermediate buffers
(OSGXR_FRAG_MVB_TEXCOORD).
Geometry amplification: Geometry shader instancing (single pass)
osgXR::View count: 1
OpenXR swapchain layout: Single (side-by-side) or Layered
Intermediate buffer layout: Same as swapchain - Single (side-by-side) or Layered
Shader changes:
- Transform vertex shader
gl_Positioninto shared view space. - Full use of interface blocks between vertex and fragment shaders.
- Add geometry shaders to copy the vertex data to each view.
- Transform view space vectors (e.g. normals) and view-space positions into per-view eye space in geometry shaders.
- Move any view-specific calculations from vertex shader to geometry shader.
- Transform texture coordinates used for sampling intermediate buffers.
Performance:
- Better CPU performance than multiple pass modes above.
- Reduced CPU overhead due to single pass.
- Potentially worse GPU vertex processing performance than other modes due to the use of geometry shaders, however if you are CPU bound or GPU/fragment bound (common for high VR resolutions), its usually a net win.
Caveats:
- Standard geometry shaders aren't designed to work with primitives larger than
triangles (although NVIDIA drivers implementing
NV_geometry_shader4may still support them), therefore any use ofGL_QUADS,GL_QUAD_STRIP, orGL_POLYGONprimitives must be converted to triangle based primitives to avoidINVALID_OPERATIONerrors from OpenGL draw calls.
To indicate that Geometry Shaders mode is supported by the application and its
shaders, and to allow it to be chosen, set it as an allowed VR mode in
osgXR::Settings, e.g. from a derived class of osgXR::Manager you can do
this:
_settings->allowVRMode(osgXR::Settings::VRMODE_GEOMETRY_SHADERS);This also prevents osgXR from defaulting to allowing both Slave Camera and SceneView modes for backwards compatibility when no other VR modes are explicitly allowed.
Texture coordinates used for sampling intermediate frame buffers and intermediate fixed-size buffers must be transformed into the appropriate viewport of the intermediate frame buffer texture. When sampling in the fragment shader, the transformation can be done either in the geometry shader (if the fragment shader otherwise uses the texture coordinate directly for sampling), or the fragment shader (if the texture coordinate already needs to be manipulated before sampling), but not both.
OSGXR_GEOM: Indicates that geometry shaders should be used. Geometry shaders may be conditional upon this, and vertex shaders may use it to conditionalise outputs which depend on the specific view (calculating these outputs in the geometry shader instead).OSGXR_VERT_TRANSFORM(POS)(MVR passes): TransformPOSfrom model space to the shared view space, which should then be assigned togl_Position. The vertex shader should fall back to normal behaviour when undefined, e.g. other modes and non-MVR passes. Theosg_ModelViewMatrixuniform must be explicitly declared.OSGXR_GEOM_GLOBAL: Used by geometry shaders to enable extensions, set up geometry instancing and define uniforms for use by other shader definitions.OSGXR_GEOM_PREPARE_VERTEX: Used by geometry shaders to prepare a single vertex. This ensures the correct viewport (gl_ViewportIndex) and layer (gl_Layer) are used.OSGXR_GEOM_TRANSFORM(POS)(MVR passes): TransformPOSfrom shared view space to per-view projection space, which should then be assigned togl_Position.OSGXR_GEOM_VIEW_MATRIX(MVR passes): Provides amat4to transform from the shared view space to the per-view eye space, which is used by geometry shaders to transform view space position vector outputs from the vertex shader.OSGXR_GEOM_NORMAL_MATRIX(MVR passes): Provides amat3to transform from the shared view space to the per-view eye space orientation, which is used by geometry shaders to transform relative vector and normal outputs from the vertex shader.OSGXR_GEOM_MVR_TEXCOORD(UV)(Shading passes): Used by geometry shaders to transform texture coordinates for intermediate frame buffers into the appropriate viewport of the buffer (for side-by-side frame buffers and layered frame buffers with differing view resolutions).OSGXR_GEOM_MVB_TEXCOORD(UV)(Shading passes & Single (side-by-side)): Used by geometry shaders to transform texture coordinates for intermediate fixed-size buffers into the appropriate viewport of the buffer (for side-by-side buffers).OSGXR_FRAG_GLOBAL(shading passes): Used by fragment shaders to define osgXR uniforms for use byOSGXR_FRAG_MVR_TEXCOORDandOSGXR_FRAG_MVB_TEXCOORD.OSGXR_FRAG_MVR_TEXCOORD(UV)(Shading passes): Used by fragment shaders to transform texture coordinates for intermediate frame buffers into the appropriate viewport of the buffer (for side-by-side frame buffers and layered frame buffers with differing view resolutions).OSGXR_FRAG_MVB_TEXCOORD(UV)(Shading passes & Single (side-by-side)): Used by fragment shaders to transform texture coordinates for intermediate fixed-size buffers into the appropriate viewport of the buffer (for side-by-side buffers).
In order for a geometry shader to be optionally inserted between the vertex and fragment stages without modification of the variable names in the vertex & fragment shaders, all vertex shader outputs (and fragment shader inputs) must be contained in interface blocks. For example:
// Vertex shader
out VS_OUT {
vec2 texcoord;
...
};
...
texcoord = foo();
...// Fragment shader
in VS_OUT {
vec2 texcoord;
...
};
...
... = texcoord;
...Where different data is used by different vertex and fragment shader files, it may make sense to split the interface blocks accordingly.
The vertex shader is invoked only once per osgXR view (which represents
multiple OpenXR views), so it should output a gl_Position in shared view
space to the geometry shader (instead of projected into eye space directly to
the fragment shader). Instead of multiplying by osg_ModelViewProjectionMatrix
as you normally might:
// Vertex shader
uniform mat4 osg_ModelViewProjectionMatrix;
...
gl_Position = osg_ModelViewProjectionMatrix * pos;You can instead use the OSGXR_VERT_TRANSFORM macro, which does the right thing
for any of the multiview rendering modes supported by osgXR, or is left
undefined when the default behaviour of transforming into projection space is
required:
// Vertex shader
#pragma import_defines (OSGXR_VERT_GLOBAL, OSGXR_VERT_TRANSFORM(POS))
// Declares the necessary uniforms etc.
#ifdef OSGXR_VERT_GLOBAL
OSGXR_VERT_GLOBAL
#endif
uniform mat4 osg_ModelViewProjectionMatrix;
// May be required by OSGXR_VERT_TRANSFORM or app code, so its up to the app
// code to declare it
uniform mat4 osg_ModelViewMatrix;
...
#ifdef OSGXR_VERT_TRANSFORM
// Transform pos into the necessary space (shared view space for geometry
// shaders, view projection space for OVR_multiview).
gl_Position = OSGXR_VERT_TRANSFORM(pos);
#else
// Otherwise: Transform pos into eye projection space
gl_Position = osg_ModelViewProjectionMatrix * pos;
#endifSince neither the projected eye space coordinates, nor the exact view space
coordinates will be available to the vertex shader, any vertex shader outputs
which depend on these should be handled by the geometry shader instead. The
vertex shader should omit such calculations/data when OSGXR_GEOM is defined:
// Vertex shader
#pragma import_defines (OSGXR_GEOM)
...
out VS_OUT {
#ifndef OSGXR_GEOM
float pos_dependent;
#endif
...
};
...
#ifndef OSGXR_GEOM
pos_dependent = foo(gl_Position);
#endifAnd instead perform them in the geometry shader. Note that it is unfortunately
awkward to share shader code between the vertex/fragment and optional geometry
stages, since any common shader file cannot depend on OSGXR_GEOM and so would
invoke the geometry stader stage even when not desired resulting in an
incomplete geometry shader.
In this mode all geometry should be rendered with an instanced geometry shader active to amplify the geometry into each OpenXR view. A custom geometry shader will be required for each different set of data to be transferred from the vertex shader to the fragment shader.
In order to avoid the overhead of geometry shading for non-VR and other
multiview rendering modes, you can make OpenSceneGraph skip the geometry shader
unless OSGXR_GEOM is defined by adding the following line near the top of the
shader:
// Geometry shader
#pragma requires (OSGXR_GEOM)Various global boilerplate code is required to define uniforms, set the number
of geometry shader invocations, and declare certain extensions as required. This
is done using the OSGXR_GEOM_GLOBAL macro:
// Geometry shader
...
#pragma import_defines (OSGXR_GEOM_GLOBAL)
...
OSGXR_GEOM_GLOBAL
...Since the gl_Position output by the vertex shader is in shared view space, the
geometry shader must perform the final tranformation into projected eye space
(for all MVR passes). This is performed using the OSGXR_GEOM_TRANSFORM macro.
Additionally the OSGXR_GEOM_PREPARE_VERTEX macro performs a number of other
operations, such as:
- Set
gl_Layerto set the appropriate output layer depending on swapchain mode and invocation id. - Set
gl_ViewportIndexto set the appropriate output viewport depending on swapchain mode and invocation id.
// Geometry shader
...
#pragma import_defines (OSGXR_GEOM_TRANSFORM(POS), OSGXR_GEOM_PREPARE_VERTEX)
...
void main(void)
{
for (int i = 0; i < gl_in.length(); ++i) {
gl_Position = gl_in[i].gl_Position;
// Project gl_Position into eye space
// Note: If this code is also used for non-MVR (e.g. shading only)
// passes, OSGXR_GEOM_TRANSFORM may not be needed or defined
gl_Position = OSGXR_GEOM_TRANSFORM(gl_Position);
// Set up destination layer/viewport
OSGXR_GEOM_PREPARE_VERTEX;
// Copy vertex data
...
EmitVertex();
}
EndPrimitive();
}As mentioned above, the use of optional geometry shaders requires the use of interface blocks for all vertex shader outputs. This allows the geometry shader to refer to both inputs (from the vertex shader) and outputs (to the fragment shader) by the same name, like so:
// Geometry shader
// Inputs from the vertex shader
in VS_OUT {
vec2 texcoord;
...
// no pos_dependent, calculation delegated to geometry shader
} gs_in[];
// Outputs to the fragment shader
out VS_OUT {
vec2 texcoord;
...
float pos_dependent;
...
} gs_out;
...
for (int i = 0; i < gl_in.length(); ++i) {
...
// Copy vertex data
gs_out.texcoord... = gs_in[i].texcoord;
...
// Calculate eye space dependent data
// normally calculated by vertex shader
pos_dependent = foo(gl_Position);
...
EmitVertex();
}
...Any view space vectors (both position vectors and normal vectors) that need
passing to the fragment stage will need transforming into the per-view eye
space, as osg_NormalMatrix and osg_ModelViewMatrix only transform to the
shared view space. The OSGXR_GEOM_VIEW_MATRIX and OSGXR_GEOM_NORMAL_MATRIX
definitions can be used for this in MVR passes:
// Geometry shader
...
#pragma import_defines (OSGXR_GEOM_GLOBAL)
#pragma import_defines (OSGXR_GEOM_NORMAL_MATRIX, OSGXR_GEOM_VIEW_MATRIX)
...
OSGXR_GEOM_GLOBAL
...
// Copy vertex data
gs_out.vertex_normal = OSGXR_GEOM_NORMAL_MATRIX * gs_in[i].vertex_normal;
gs_out.view_vector = vec3(OSGXR_GEOM_VIEW_MATRIX * vec4(gs_in[i].view_vector, 1.0));Any texture coordinates used for sampling intermediate frame buffers should be transformed into the appropriate viewport of that buffer (for side-by-side frame buffers and layered frame buffers with differing view resolutions).
This can be done in the geometry shader with OSGXR_GEOM_MVR_TEXCOORD:
// Geometry shader
...
#pragma import_defines (OSGXR_GEOM_GLOBAL, OSGXR_GEOM_MVR_TEXCOORD(UV))
...
OSGXR_GEOM_GLOBAL
...
// Copy vertex data
gs_out.texcoord = OSGXR_GEOM_MVR_TEXCOORD(gs_in[i].texcoord);A similar macro exists for fixed-size intermediate buffers
(OSGXR_GEOM_MVB_TEXCOORD).
An alternative macro to OSGXR_GEOM_MVR_TEXCOORD may instead be used in
fragment shaders, such as if the coordinates need processing in the fragment
shader before sampling:
// Fragment shader
...
#pragma import_defines (OSGXR_FRAG_GLOBAL, OSGXR_FRAG_MVR_TEXCOORD(UV))
...
#ifdef OSGXR_FRAG_GLOBAL
OSGXR_FRAG_GLOBAL
#endif
...
// Per-fragment calculation of texcoord prevents transformation in vertex
// or geometry stage before interpolation across primitives
texcoord = foo(...);
#ifdef OSGXR_FRAG_MVR_TEXCOORD
texcoord = OSGXR_FRAG_MVR_TEXCOORD(texcoord);
#endifA similar macro exists for fixed-size intermediate buffers
(OSGXR_FRAG_MVB_TEXCOORD).
Geometry amplification: GL_OVR_multiview2 instances vertex shader (single
pass)
osgXR::View count: 1
OpenXR swapchain layout: Layered
Intermediate buffer layout: Same as swapchain - Layered
Shader changes:
- Not as extensive as geometry shaders, but changes are required due to multiple views handled in a single pass, and vertex shaders need modification.
- Transform vertex shader
gl_Positioninto eye projection space. - Transform view space vectors (e.g. normals) and view-space positions into per-view eye space in vertex shaders.
- Transform texture coordinates used for sampling intermediate buffers.
Performance:
- Better CPU performance than multiple pass modes above.
- Reduced overhead due to single pass.
- Hardware accelerated multiview vertex processing avoids overhead of geometry shaders.
Availability:
- Not as widely available as geometry shaders.
- On Linux, currently only available for NVIDIA GPUs.
Caveats:
- A driver bug
(NVIDIA)
may prevent the use of display lists with the
GL_OVR_multiviewextension.- The environment variable
OSG_VERTEX_BUFFER_HINT=VBOcan be used to tell OpenSceneGraph to use vertex buffer objects instead of display lists. - The application can do the same thing with
osg::DisplaySettings::setVertexBufferHint(osg::DisplaySettings::VertexBufferHint::VERTEX_BUFFER_OBJECT), however comments in OpenMW-VR suggest this must be done beforerealize()is called on the viewer, i.e. without access to a graphics context which would let us determine whetherGL_OVR_multiview2is even supported.
- The environment variable
GL_OVR_multiviewexplicitly does not support tessellation or geometry shaders during multiview rendering, therefore any use of these must be avoided to support this mode.
To indicate that OVR Multiview mode is supported by the application and its
shaders, and to allow it to be chosen, set it as an allowed VR mode in
osgXR::Settings, e.g. from a derived class of osgXR::Manager you can do
this:
_settings->allowVRMode(osgXR::Settings::VRMODE_OVR_MULTIVIEW);This also prevents osgXR from defaulting to allowing both Slave Camera and SceneView modes for backwards compatibility when no other VR modes are explicitly allowed.
OSGXR_VERT_GLOBAL: Used by vertex shaders to enable extensions, set up the number of views for instancing and define uniforms for use by other shader definitions.OSGXR_VERT_PREPARE_VERTEX: Used by vertex shaders to set up the appropriate viewport (gl_ViewportIndex) to use.OSGXR_VERT_TRANSFORM(POS)(MVR passes): TransformPOSfrom model space to the per-view projection space, which should then be assigned togl_Position. The vertex shader should fall back to normal behaviour when undefined, e.g. other modes and non-MVR passes. Theosg_ModelViewMatrixuniform must be explicitly declared.OSGXR_VERT_VIEW_MATRIX(MVR passes): Provides amat4to transform from the shared view space to the eye space, which is used by vertex shaders to transform view space position vector outputs.OSGXR_VERT_NORMAL_MATRIX(MVR passes): Provides amat3to transform from the shared view space to the eye space orientation, which is used by vertex shaders to transform relative vector and normal outputs.OSGXR_VERT_MVR_TEXCOORD(UV)(shading passes): Used by vertex shaders to transform texture coordinates for intermediate frame buffers into the appropriate viewport of the buffer (for differing view resolutions).OSGXR_FRAG_GLOBAL(shading passes): Used by fragment shaders to define osgXR uniforms for use byOSGXR_FRAG_MVR_TEXCOORD.OSGXR_FRAG_MVR_TEXCOORD(UV)(Shading passes): Used by fragment shaders to transform texture coordinates for intermediate frame buffers into the appropriate viewport of the buffer (for differing view resolutions).
Note that since only layered intermediate buffer layouts are supported by this
mode, fixed-size intermediate buffers do not technically need any special
texture coordinate transformation, however OSGXR_VERT_MVB_TEXCOORD and
OSGXR_FRAG_MVB_TEXCOORD may still be required to support other modes.
Various global boilerplate code is required to define uniforms, set the number
of vertex shader multiview invocations, and declare certain extensions as
required. This is done using the OSGXR_VERT_GLOBAL macro:
// Vertex shader
...
#pragma import_defines (OSGXR_VERT_GLOBAL)
...
#ifdef OSGXR_VERT_GLOBAL
OSGXR_VERT_GLOBAL
#endif
...The vertex shader is invoked for every OpenXR view, so it should output a
gl_Position transformed into the per-view projection space. Instead of
multiplying by osg_ModelViewProjectionMatrix as you normally might:
// Vertex shader
uniform mat4 osg_ModelViewProjectionMatrix;
...
gl_Position = osg_ModelViewProjectionMatrix * pos;You can instead use the OSGXR_VERT_TRANSFORM macro, which does the right thing
for any of the multiview rendering modes supported by osgXR, or is left
undefined when the default behaviour of transforming into projection space is
required:
// Vertex shader
#pragma import_defines (OSGXR_VERT_GLOBAL, OSGXR_VERT_TRANSFORM(POS))
// Declares the necessary uniforms etc.
#ifdef OSGXR_VERT_GLOBAL
OSGXR_VERT_GLOBAL
#endif
uniform mat4 osg_ModelViewProjectionMatrix;
// May be required by OSGXR_VERT_TRANSFORM or app code, so its up to the app
// code to declare it
uniform mat4 osg_ModelViewMatrix;
...
#ifdef OSGXR_VERT_TRANSFORM
// Transform pos into the necessary space (shared view space for geometry
// shaders, view projection space for OVR_multiview).
gl_Position = OSGXR_VERT_TRANSFORM(pos);
#else
// Otherwise: Transform pos into eye projection space
gl_Position = osg_ModelViewProjectionMatrix * pos;
#endifNote: For shaders which generate gl_Position procedurally and identically
between views (e.g. full screen quads for shading), a transform may not be
required.
OSGXR_VERT_PREPARE_VERTEX should always be used to set the appropriate
viewport (gl_ViewportIndex) to use:
// Vertex shader
#pragma import_defines (OSGXR_VERT_GLOBAL, OSGXR_VERT_PREPARE_VERTEX)
...
#ifdef OSGXR_VERT_PREPARE_VERTEX
OSGXR_VERT_PREPARE_VERTEX;
#endifAny view space vectors (both position vectors and normal vectors) that need
passing to the fragment stage will need transforming into the per-view eye
space, as osg_NormalMatrix and osg_ModelViewMatrix only transform to the
shared view space. The OSGXR_VERT_VIEW_MATRIX and OSGXR_VERT_NORMAL_MATRIX
definitions can be used for this:
// Vertex shader
...
#pragma import_defines (OSGXR_VERT_GLOBAL)
#pragma import_defines (OSGXR_VERT_NORMAL_MATRIX, OSGXR_VERT_VIEW_MATRIX)
...
#idef OSGXR_VERT_GLOBAL
OSGXR_VERT_GLOBAL
#endif
...
vertex_normal = osg_NormalMatrix * N;
#ifdef OSGXR_VERT_NORMAL_MATRIX
vertex_normal = OSGXR_VERT_NORMAL_MATRIX * vertex_normal;
#endif
...
view_vector = osg_ModelViewMatrix * vec4(position, 1.0)).xyz;
#ifdef OSGXR_VERT_VIEW_MATRIX
view_vector = vec3(OSGXR_VERT_VIEW_MATRIX * vec4(view_vector, 1.0));
#endifAny texture coordinates used for sampling intermediate frame buffers should be transformed into the appropriate viewport of that buffer (for frame buffers with differing view resolutions).
This can be done in the vertex shader with OSGXR_VERT_MVR_TEXCOORD:
// Vertex shader
...
#pragma import_defines (OSGXR_VERT_GLOBAL, OSGXR_VERT_MVR_TEXCOORD(UV))
...
#ifdef OSGXR_VERT_GLOBAL
OSGXR_VERT_GLOBAL
#endif
...
texcoord = multitexcoord0.st;
#ifdef OSGXR_VERT_MVR_TEXCOORD
texcoord = OSGXR_VERT_MVR_TEXCOORD(texcoord);
#endifNote that since only layered intermediate buffer layouts are supported by this
mode, fixed-size intermediate buffers do not technically need any special
texture coordinate transformation, however OSGXR_VERT_MVB_TEXCOORD may still
be required to support other modes (SceneView).
An alternative macro to OSGXR_VERT_MVR_TEXCOORD may instead be used in
fragment shaders, such as if the coordinates need processing in the fragment
shader before sampling:
// Fragment shader
...
#pragma import_defines (OSGXR_FRAG_GLOBAL, OSGXR_FRAG_MVR_TEXCOORD(UV))
...
#ifdef OSGXR_FRAG_GLOBAL
OSGXR_FRAG_GLOBAL
#endif
...
// Per-fragment calculation of texcoord prevents transformation in vertex
// or geometry stage before interpolation across primitives
texcoord = foo(...);
#ifdef OSGXR_FRAG_MVR_TEXCOORD
texcoord = OSGXR_FRAG_MVR_TEXCOORD(texcoord);
#endifNote that since only layered intermediate buffer layouts are supported by this
mode, fixed-size intermediate buffers do not technically need any special
texture coordinate transformation, however OSGXR_FRAG_MVB_TEXCOORD may still
be required to support other modes (SceneView & Geometry Shaders).