1
0
mirror of https://github.com/microsoft/DirectXMath synced 2024-11-08 21:50:09 +00:00

Stereo 3D prototype from 2011

This commit is contained in:
Chuck Walbourn 2017-07-13 01:28:40 -07:00
parent b282f1e624
commit f89abe64f4
2 changed files with 322 additions and 79 deletions

View File

@ -1,73 +1,282 @@
//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//// PARTICULAR PURPOSE.
////
//// Copyright (c) Microsoft Corporation. All rights reserved
//-------------------------------------------------------------------------------------
// Stereo3DMatrixHelper.h -- SIMD C++ Math helper for Stereo 3D matricies
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//-------------------------------------------------------------------------------------
#include "pch.h"
#include "Stereo3DMatrixHelper.h"
using namespace DirectX;
StereoParameters CreateDefaultStereoParameters(
float viewportWidthInches,
float viewportHeightInches,
float worldScaleInInches,
float stereoExaggeration
)
//------------------------------------------------------------------------------
void StereoCreateDefaultParameters
(
STEREO_PARAMETERS* pStereoParameters
)
{
// The default stereo parameters produced by this method are based on two assumptions:
// 1. The viewer's eyes are 24 inches from the display, and
// 2. The viewer's eyes are separated by 1.25 inches (interocular distance.)
const float DEFAULT_VIEWER_DISTANCE_IN_INCHES = 24.0f;
const float DEFAULT_INTEROCULAR_DISTANCE_IN_INCHES = 1.25f;
assert( pStereoParameters != nullptr );
StereoParameters parameters;
parameters.viewportWidth = viewportWidthInches / worldScaleInInches;
parameters.viewportHeight = viewportHeightInches / worldScaleInInches;
parameters.viewerDistance = DEFAULT_VIEWER_DISTANCE_IN_INCHES / worldScaleInInches;
parameters.interocularDistance = DEFAULT_INTEROCULAR_DISTANCE_IN_INCHES / worldScaleInInches * stereoExaggeration;
// Default assumption is 1920x1200 resolution, a 22" LCD monitor, and a 2' viewing distance
pStereoParameters->fViewerDistanceInches = 24.0f;
pStereoParameters->fPixelResolutionWidth = 1920.0f;
pStereoParameters->fPixelResolutionHeight = 1200.0f;
pStereoParameters->fDisplaySizeInches = 22.0f;
return parameters;
pStereoParameters->fStereoSeparationFactor = 1.0f;
pStereoParameters->fStereoExaggerationFactor = 1.0f;
}
DirectX::XMMATRIX StereoProjectionFieldOfViewRightHand(
const StereoParameters& parameters,
float nearZ,
float farZ,
bool rightChannel
)
//------------------------------------------------------------------------------
static inline bool StereoProjectionHelper
(
const STEREO_PARAMETERS* pStereoParameters,
_Out_ float* fVirtualProjection,
_Out_ float* zNearWidth,
_Out_ float* zNearHeight,
float FovAngleY,
float AspectHByW,
float NearZ
)
{
float yScale = 2.f * parameters.viewerDistance / parameters.viewportHeight;
float xScale = 2.f * parameters.viewerDistance / parameters.viewportWidth;
// note that most people have difficulty fusing images into 3D
// if the separation equals even just the human average. by
// reducing the separation (interocular distance) by 1/2, we
// guarantee a larger subset of people will see full 3D
float mFactor = - parameters.interocularDistance / parameters.viewportWidth;
// the conservative setting should always be used. the only problem
// with the conservative setting is that the 3D effect will be less
// impressive on smaller screens (which makes sense, since your eye
// cannot be tricked as easily based on the smaller fov). to simulate
// the effect of a larger screen, use the liberal settings (debug only)
if (!rightChannel)
// Conservative Settings: * max acuity angle: 0.8f degrees * interoc distance: 1.25 inches
// Liberal Settings: * max acuity angle: 1.6f degrees * interoc distance: 2.5f inches
// maximum visual accuity angle allowed is 3.2 degrees for
// a physical scene, and 1.6 degrees for a virtual one.
// thus we cannot allow an object to appear any closer to
// the viewer than 1.6 degrees (divided by two for most
// half-angle calculations)
static const float fMaxStereoDistance = 780; // inches (should be between 10 and 20m)
static const float fMaxVisualAcuityAngle = 1.6f * ( XM_PI / 180.0f ); // radians
static const float fInterocularDistance = 1.25f; // inches
bool ComfortableResult = true;
float fDisplayHeight, fDisplayWidth, fHalfInterocular, fHalfPixelWidth, fHalfMaximumAcuityAngle, fHalfWidth;
float fMaxSeparationAcuityAngle, fMaxSeparationDistance, fRefinedMaxStereoDistance, fFovHalfAngle;
float fRefinedMaxSeparationAcuityAngle, fPhysicalZNearDistance, fScalingFactor, fNearZSeparation, fNearZSeparation2;
fDisplayHeight = pStereoParameters->fDisplaySizeInches / sqrtf( AspectHByW * AspectHByW + 1.0f );
fDisplayWidth = fDisplayHeight * AspectHByW;
fHalfInterocular = 0.5f * fInterocularDistance * pStereoParameters->fStereoExaggerationFactor;
fHalfPixelWidth = fDisplayWidth / pStereoParameters->fPixelResolutionWidth * 0.5f;
fHalfMaximumAcuityAngle = fMaxVisualAcuityAngle * 0.5f * pStereoParameters->fStereoExaggerationFactor;
fHalfWidth = fDisplayWidth * 0.5f;
fMaxSeparationAcuityAngle = atanf( fHalfInterocular / fMaxStereoDistance );
fMaxSeparationDistance = fHalfPixelWidth / tanf ( fMaxSeparationAcuityAngle );
fRefinedMaxStereoDistance = fMaxStereoDistance - fMaxSeparationDistance;
fFovHalfAngle = FovAngleY / 2.0f;
if ( fRefinedMaxStereoDistance < 0.0f || fMaxSeparationDistance > 0.1f * fMaxStereoDistance )
{
mFactor = -mFactor;
// Pixel resolution is too low to offer a comfortable stereo experience
ComfortableResult = false;
}
float m22 = farZ / (nearZ - farZ);
fRefinedMaxSeparationAcuityAngle = atanf( fHalfInterocular / ( fRefinedMaxStereoDistance ) );
fPhysicalZNearDistance = fHalfInterocular / tanf( fHalfMaximumAcuityAngle );
fScalingFactor = fHalfMaximumAcuityAngle / atanf( fHalfInterocular / pStereoParameters->fViewerDistanceInches );
// Construct a stereo perspective projection matrix based on assumptions
// about the viewer and specified stereo parameters. Note that compared
// to a mono perspective projection matrix, there are two differences:
// - a non-zero x:z component (m20)
// - a non-zero x:w component (m30)
// The values of these two factors affect both the x-offset between the
// left and right eyes, as well as the depth at which they converge. The
// math used to arrive at these values will often need to change depending
// on the content being presented in order to ensure a comfortable viewing
// experience. For example, the factors for rendering massive exterior
// landscapes will be different than those used for rendering building
// interiors. Because of this, developers are encouraged to experiment
// with different techniques for generating these values.
return XMMATRIX(
xScale, 0, 0, 0,
0, yScale, 0, 0,
mFactor, 0, m22, -1,
parameters.viewerDistance * mFactor, 0, nearZ * m22, 0
);
fNearZSeparation = tanf( fRefinedMaxSeparationAcuityAngle ) * ( fRefinedMaxStereoDistance - fPhysicalZNearDistance );
fNearZSeparation2 = fHalfInterocular * ( fRefinedMaxStereoDistance - fPhysicalZNearDistance ) / fRefinedMaxStereoDistance;
(*zNearHeight) = cosf( fFovHalfAngle ) / sinf( fFovHalfAngle );
(*zNearWidth) = (*zNearHeight) / AspectHByW;
(*fVirtualProjection) = ( fNearZSeparation * NearZ * (*zNearWidth * 4.0f) ) / ( 2.0f * NearZ );
return ComfortableResult;
}
//------------------------------------------------------------------------------
XMMATRIX StereoProjectionFovLH
(
const STEREO_PARAMETERS* pStereoParameters,
STEREO_CHANNEL Channel,
float FovAngleY,
float AspectHByW,
float NearZ,
float FarZ,
STEREO_MODE StereoMode
)
{
float fVirtualProjection = 0.0f;
float zNearWidth = 0.0f;
float zNearHeight = 0.0f;
float fInvertedAngle;
XMMATRIX patchedProjection, proj;
STEREO_PARAMETERS DefaultParameters;
assert( Channel == STEREO_CHANNEL_LEFT || Channel == STEREO_CHANNEL_RIGHT );
assert( StereoMode == STEREO_MODE_NORMAL || StereoMode == STEREO_MODE_INVERTED );
assert(!XMScalarNearEqual(FovAngleY, 0.0f, 0.00001f * 2.0f));
assert(!XMScalarNearEqual(AspectHByW, 0.0f, 0.00001f));
assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f));
proj = XMMatrixIdentity();
if( pStereoParameters == nullptr )
{
StereoCreateDefaultParameters( &DefaultParameters );
pStereoParameters = &DefaultParameters;
}
assert( pStereoParameters->fStereoSeparationFactor >= 0.0f && pStereoParameters->fStereoSeparationFactor <= 1.0f );
assert( pStereoParameters->fStereoExaggerationFactor >= 1.0f && pStereoParameters->fStereoExaggerationFactor <= 2.0f );
StereoProjectionHelper( pStereoParameters, &fVirtualProjection, &zNearWidth, &zNearHeight, FovAngleY, AspectHByW, NearZ );
fVirtualProjection *= pStereoParameters->fStereoSeparationFactor; // incorporate developer defined bias
//
// By applying a translation, we are forcing our cameras to be parallel
//
fInvertedAngle = atanf( fVirtualProjection / ( 2.0f * NearZ ) );
proj = XMMatrixPerspectiveFovLH( FovAngleY, AspectHByW, NearZ, FarZ );
if ( Channel == STEREO_CHANNEL_LEFT )
{
if ( StereoMode > STEREO_MODE_NORMAL )
{
XMMATRIX rots, trans;
rots = XMMatrixRotationY( fInvertedAngle );
trans = XMMatrixTranslation( -fVirtualProjection, 0, 0);
patchedProjection = XMMatrixMultiply( XMMatrixMultiply( rots, trans ), proj );
}
else
{
XMMATRIX trans;
trans = XMMatrixTranslation( -fVirtualProjection, 0, 0);
patchedProjection = XMMatrixMultiply( trans, proj );
}
}
else
{
if ( StereoMode > STEREO_MODE_NORMAL )
{
XMMATRIX rots, trans;
rots = XMMatrixRotationY( -fInvertedAngle );
trans = XMMatrixTranslation( fVirtualProjection, 0, 0);
patchedProjection = XMMatrixMultiply( XMMatrixMultiply( rots, trans), proj );
}
else
{
XMMATRIX trans;
trans = XMMatrixTranslation( fVirtualProjection, 0, 0);
patchedProjection = XMMatrixMultiply( trans, proj );
}
}
return patchedProjection;
}
//------------------------------------------------------------------------------
XMMATRIX StereoProjectionFovRH
(
const STEREO_PARAMETERS* pStereoParameters,
STEREO_CHANNEL Channel,
float FovAngleY,
float AspectHByW,
float NearZ,
float FarZ,
STEREO_MODE StereoMode
)
{
float fVirtualProjection = 0.0f;
float zNearWidth = 0.0f;
float zNearHeight = 0.0f;
float fInvertedAngle;
XMMATRIX patchedProjection, proj;
STEREO_PARAMETERS DefaultParameters;
assert( Channel == STEREO_CHANNEL_LEFT || Channel == STEREO_CHANNEL_RIGHT );
assert( StereoMode == STEREO_MODE_NORMAL || StereoMode == STEREO_MODE_INVERTED );
assert(!XMScalarNearEqual(FovAngleY, 0.0f, 0.00001f * 2.0f));
assert(!XMScalarNearEqual(AspectHByW, 0.0f, 0.00001f));
assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f));
proj = XMMatrixIdentity();
if( pStereoParameters == nullptr )
{
StereoCreateDefaultParameters( &DefaultParameters );
pStereoParameters = &DefaultParameters;
}
assert( pStereoParameters->fStereoSeparationFactor >= 0.0f && pStereoParameters->fStereoSeparationFactor <= 1.0f );
assert( pStereoParameters->fStereoExaggerationFactor >= 1.0f && pStereoParameters->fStereoExaggerationFactor <= 2.0f );
StereoProjectionHelper( pStereoParameters, &fVirtualProjection, &zNearWidth, &zNearHeight, FovAngleY, AspectHByW, NearZ );
fVirtualProjection *= pStereoParameters->fStereoSeparationFactor; // incorporate developer defined bias
//
// By applying a translation, we are forcing our cameras to be parallel
//
fInvertedAngle = atanf( fVirtualProjection / ( 2.0f * NearZ ) );
proj = XMMatrixPerspectiveFovRH( FovAngleY, AspectHByW, NearZ, FarZ );
//
// By applying a translation, we are forcing our cameras to be parallel
//
if ( Channel == STEREO_CHANNEL_LEFT )
{
if ( StereoMode > STEREO_MODE_NORMAL )
{
XMMATRIX rots, trans;
rots = XMMatrixRotationY( fInvertedAngle );
trans = XMMatrixTranslation( -fVirtualProjection, 0, 0);
patchedProjection = XMMatrixMultiply( XMMatrixMultiply( rots, trans ), proj );
}
else
{
XMMATRIX trans;
trans = XMMatrixTranslation( -fVirtualProjection, 0, 0);
patchedProjection = XMMatrixMultiply( trans, proj );
}
}
else
{
if ( StereoMode > STEREO_MODE_NORMAL )
{
XMMATRIX rots, trans;
rots = XMMatrixRotationY( -fInvertedAngle );
trans = XMMatrixTranslation( fVirtualProjection, 0, 0);
patchedProjection = XMMatrixMultiply( XMMatrixMultiply( rots, trans), proj );
}
else
{
XMMATRIX trans;
trans = XMMatrixTranslation( fVirtualProjection, 0, 0);
patchedProjection = XMMatrixMultiply( trans, proj );
}
}
return patchedProjection;
}

View File

@ -1,31 +1,65 @@
//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//// PARTICULAR PURPOSE.
////
//// Copyright (c) Microsoft Corporation. All rights reserved
//-------------------------------------------------------------------------------------
// Stereo3DMatrixHelper.h -- SIMD C++ Math helper for Stereo 3D matrices
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//-------------------------------------------------------------------------------------
#ifdef _MSC_VER
#pragma once
#endif
// Stereo parameters are in the same units as the world.
struct StereoParameters
#include "DirectXMath.h"
// Enumeration for stereo channels (left and right).
enum STEREO_CHANNEL
{
float viewportWidth; // viewport width
float viewportHeight; // viewport height
float viewerDistance; // distance from viewer
float interocularDistance; // interocular distance
STEREO_CHANNEL_LEFT = 0,
STEREO_CHANNEL_RIGHT
};
StereoParameters CreateDefaultStereoParameters(
float viewportWidthInches,
float viewportHeightInches,
float worldScaleInInches,
float stereoExaggeration
);
// Enumeration for stereo mode (normal or inverted).
enum STEREO_MODE
{
STEREO_MODE_NORMAL = 0,
STEREO_MODE_INVERTED,
};
DirectX::XMMATRIX StereoProjectionFieldOfViewRightHand(
const StereoParameters& parameters,
float nearZ,
float farZ,
bool rightChannel
);
//------------------------------------------------------------------------------
//
// Stereo calibration settings
//
// * Viewer distance to the display
// * Physical display size
// * Render resolution
//
// The stereo separation factor indicates how much separation is between the left and right
// eyes. 0 is no separation, 1 is full separation. It defaults to 1.0.
//
// The debug stereo exaggeration factor indicates how much to increase the interocular spacing and
// maximum acuity angle from comfortable defaults. For retail builds, this value should always
// be 1.0, but during development, on small screens, this value can be raised to up to 2.0 in
// order to exaggerate the 3D effect. Values over 1.0 may cause discomfort on normal sized
// displays. It defaults to 1.0.
//
struct STEREO_PARAMETERS
{
float fViewerDistanceInches;
float fDisplaySizeInches;
float fPixelResolutionWidth;
float fPixelResolutionHeight;
float fStereoSeparationFactor;
float fStereoExaggerationFactor;
};
void StereoCreateDefaultParameters( _Out_ STEREO_PARAMETERS* pStereoParameters );
DirectX::XMMATRIX StereoProjectionFovLH( _In_opt_ const STEREO_PARAMETERS* pStereoParameters, STEREO_CHANNEL Channel,
float FovAngleY, float AspectHByW, float NearZ, float FarZ, STEREO_MODE StereoMode );
DirectX::XMMATRIX StereoProjectionFovRH( _In_opt_ const STEREO_PARAMETERS* pStereoParameters, STEREO_CHANNEL Channel,
float FovAngleY, float AspectHByW, float NearZ, float FarZ, STEREO_MODE StereoMode );