mirror of
https://github.com/ncblakely/GiantsTools
synced 2024-11-23 22:55:37 +01:00
258 lines
10 KiB
C++
258 lines
10 KiB
C++
//-------------------------------------------------------------------------------------
|
|
// 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;
|
|
}
|