//------------------------------------------------------------------------------------- // Stereo3DMatrixHelper.cpp -- SIMD C++ Math helper for Stereo 3D matricies // // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. //------------------------------------------------------------------------------------- #include "Stereo3DMatrixHelper.h" using namespace DirectX; namespace { inline bool StereoProjectionHelper ( const STEREO_PARAMETERS& stereoParameters, _Out_ float* fVirtualProjection, _Out_ float* zNearWidth, _Out_ float* zNearHeight, float FovAngleY, float AspectRatio, float NearZ ) { // 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 // 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) // 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 float fDisplayHeight = stereoParameters.fDisplaySizeInches / sqrtf(AspectRatio * AspectRatio + 1.0f); float fDisplayWidth = fDisplayHeight * AspectRatio; float fHalfInterocular = 0.5f * fInterocularDistance * stereoParameters.fStereoExaggerationFactor; float fHalfPixelWidth = fDisplayWidth / stereoParameters.fPixelResolutionWidth * 0.5f; float fHalfMaximumAcuityAngle = fMaxVisualAcuityAngle * 0.5f * stereoParameters.fStereoExaggerationFactor; // float fHalfWidth = fDisplayWidth * 0.5f; float fMaxSeparationAcuityAngle = atanf(fHalfInterocular / fMaxStereoDistance); float fMaxSeparationDistance = fHalfPixelWidth / tanf(fMaxSeparationAcuityAngle); float fRefinedMaxStereoDistance = fMaxStereoDistance - fMaxSeparationDistance; float fFovHalfAngle = FovAngleY / 2.0f; bool ComfortableResult = true; if (fRefinedMaxStereoDistance < 0.0f || fMaxSeparationDistance > 0.1f * fMaxStereoDistance) { // Pixel resolution is too low to offer a comfortable stereo experience ComfortableResult = false; } float fRefinedMaxSeparationAcuityAngle = atanf(fHalfInterocular / (fRefinedMaxStereoDistance)); float fPhysicalZNearDistance = fHalfInterocular / tanf(fHalfMaximumAcuityAngle); // float fScalingFactor = fHalfMaximumAcuityAngle / atanf(fHalfInterocular / stereoParameters.fViewerDistanceInches); float fNearZSeparation = tanf(fRefinedMaxSeparationAcuityAngle) * (fRefinedMaxStereoDistance - fPhysicalZNearDistance); // float fNearZSeparation2 = fHalfInterocular * (fRefinedMaxStereoDistance - fPhysicalZNearDistance) / fRefinedMaxStereoDistance; (*zNearHeight) = cosf(fFovHalfAngle) / sinf(fFovHalfAngle); (*zNearWidth) = (*zNearHeight) / AspectRatio; (*fVirtualProjection) = (fNearZSeparation * NearZ * (*zNearWidth * 4.0f)) / (2.0f * NearZ); return ComfortableResult; } } //------------------------------------------------------------------------------ void DirectX::StereoCreateDefaultParameters ( STEREO_PARAMETERS& stereoParameters ) { // Default assumption is 1920x1200 resolution, a 22" LCD monitor, and a 2' viewing distance stereoParameters.fViewerDistanceInches = 24.0f; stereoParameters.fPixelResolutionWidth = 1920.0f; stereoParameters.fPixelResolutionHeight = 1200.0f; stereoParameters.fDisplaySizeInches = 22.0f; stereoParameters.fStereoSeparationFactor = 1.0f; stereoParameters.fStereoExaggerationFactor = 1.0f; } //------------------------------------------------------------------------------ XMMATRIX DirectX::StereoProjectionFovLH ( _In_opt_ const STEREO_PARAMETERS* pStereoParameters, STEREO_CHANNEL Channel, float FovAngleY, float AspectRatio, float NearZ, float FarZ, STEREO_MODE StereoMode ) { 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(AspectRatio, 0.0f, 0.00001f)); assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f)); STEREO_PARAMETERS DefaultParameters = {}; 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); float fVirtualProjection = 0.0f; float zNearWidth = 0.0f; float zNearHeight = 0.0f; StereoProjectionHelper(*pStereoParameters, &fVirtualProjection, &zNearWidth, &zNearHeight, FovAngleY, AspectRatio, NearZ); fVirtualProjection *= pStereoParameters->fStereoSeparationFactor; // incorporate developer defined bias // // By applying a translation, we are forcing our cameras to be parallel // float fInvertedAngle = atanf(fVirtualProjection / (2.0f * NearZ)); XMMATRIX proj = XMMatrixPerspectiveFovLH(FovAngleY, AspectRatio, NearZ, FarZ); XMMATRIX patchedProjection; if (Channel == STEREO_CHANNEL_LEFT) { if (StereoMode > STEREO_MODE_NORMAL) { XMMATRIX rots = XMMatrixRotationY(fInvertedAngle); XMMATRIX trans = XMMatrixTranslation(-fVirtualProjection, 0, 0); patchedProjection = XMMatrixMultiply(XMMatrixMultiply(rots, trans), proj); } else { XMMATRIX trans = XMMatrixTranslation(-fVirtualProjection, 0, 0); patchedProjection = XMMatrixMultiply(trans, proj); } } else { if (StereoMode > STEREO_MODE_NORMAL) { XMMATRIX rots = XMMatrixRotationY(-fInvertedAngle); XMMATRIX trans = XMMatrixTranslation(fVirtualProjection, 0, 0); patchedProjection = XMMatrixMultiply(XMMatrixMultiply(rots, trans), proj); } else { XMMATRIX trans = XMMatrixTranslation(fVirtualProjection, 0, 0); patchedProjection = XMMatrixMultiply(trans, proj); } } return patchedProjection; } //------------------------------------------------------------------------------ XMMATRIX DirectX::StereoProjectionFovRH ( _In_opt_ const STEREO_PARAMETERS* pStereoParameters, STEREO_CHANNEL Channel, float FovAngleY, float AspectRatio, float NearZ, float FarZ, STEREO_MODE StereoMode ) { 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(AspectRatio, 0.0f, 0.00001f)); assert(!XMScalarNearEqual(FarZ, NearZ, 0.00001f)); STEREO_PARAMETERS DefaultParameters = {}; 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); float fVirtualProjection = 0.0f; float zNearWidth = 0.0f; float zNearHeight = 0.0f; StereoProjectionHelper(*pStereoParameters, &fVirtualProjection, &zNearWidth, &zNearHeight, FovAngleY, AspectRatio, NearZ); fVirtualProjection *= pStereoParameters->fStereoSeparationFactor; // incorporate developer defined bias // // By applying a translation, we are forcing our cameras to be parallel // float fInvertedAngle = atanf(fVirtualProjection / (2.0f * NearZ)); XMMATRIX proj = XMMatrixPerspectiveFovRH(FovAngleY, AspectRatio, NearZ, FarZ); // // By applying a translation, we are forcing our cameras to be parallel // XMMATRIX patchedProjection; if (Channel == STEREO_CHANNEL_LEFT) { if (StereoMode > STEREO_MODE_NORMAL) { XMMATRIX rots = XMMatrixRotationY(fInvertedAngle); XMMATRIX trans = XMMatrixTranslation(-fVirtualProjection, 0, 0); patchedProjection = XMMatrixMultiply(XMMatrixMultiply(rots, trans), proj); } else { XMMATRIX trans = XMMatrixTranslation(-fVirtualProjection, 0, 0); patchedProjection = XMMatrixMultiply(trans, proj); } } else { if (StereoMode > STEREO_MODE_NORMAL) { XMMATRIX rots = XMMatrixRotationY(-fInvertedAngle); XMMATRIX trans = XMMatrixTranslation(fVirtualProjection, 0, 0); patchedProjection = XMMatrixMultiply(XMMatrixMultiply(rots, trans), proj); } else { XMMATRIX trans = XMMatrixTranslation(fVirtualProjection, 0, 0); patchedProjection = XMMatrixMultiply(trans, proj); } } return patchedProjection; }