diff --git a/Engine/source/T3D/gameBase/gameConnection.cpp b/Engine/source/T3D/gameBase/gameConnection.cpp index e3f91e160..ede671d0e 100644 --- a/Engine/source/T3D/gameBase/gameConnection.cpp +++ b/Engine/source/T3D/gameBase/gameConnection.cpp @@ -219,6 +219,7 @@ GameConnection::GameConnection() // first person mFirstPerson = true; mUpdateFirstPerson = false; + clearDisplayDevice(); } GameConnection::~GameConnection() @@ -1729,6 +1730,13 @@ DefineEngineMethod( GameConnection, setControlObject, bool, (GameBase* ctrlObj), return true; } +DefineEngineMethod( GameConnection, clearDisplayDevice, void, (),, + "@brief Clear any display device.\n\n" + "A display device may define a number of properties that are used during rendering.\n\n") +{ + object->clearDisplayDevice(); +} + DefineEngineMethod( GameConnection, getControlObject, GameBase*, (),, "@brief On the server, returns the object that the client is controlling." "By default the control object is an instance of the Player class, but can also be an instance " diff --git a/Engine/source/T3D/gameBase/gameConnection.h b/Engine/source/T3D/gameBase/gameConnection.h index fc25ba25a..324a9c989 100644 --- a/Engine/source/T3D/gameBase/gameConnection.h +++ b/Engine/source/T3D/gameBase/gameConnection.h @@ -45,6 +45,7 @@ enum GameConnectionConstants DataBlockQueueCount = 16 }; +class IDisplayDevice; class SFXProfile; class MatrixF; class MatrixF; @@ -86,6 +87,8 @@ private: F32 mCameraFov; ///< Current camera fov (in degrees). F32 mCameraPos; ///< Current camera pos (0-1). F32 mCameraSpeed; ///< Camera in/out speed. + + IDisplayDevice* mDisplayDevice; ///< Optional client display device that imposes rendering properties. /// @} public: @@ -263,6 +266,10 @@ public: void setFirstPerson(bool firstPerson); + bool hasDisplayDevice() const { return mDisplayDevice != NULL; } + const IDisplayDevice* getDisplayDevice() const { return mDisplayDevice; } + void setDisplayDevice(IDisplayDevice* display) { mDisplayDevice = display; } + void clearDisplayDevice() { mDisplayDevice = NULL; } /// @} void detectLag(); diff --git a/Engine/source/T3D/gameFunctions.cpp b/Engine/source/T3D/gameFunctions.cpp index c3b3967a3..557607a44 100644 --- a/Engine/source/T3D/gameFunctions.cpp +++ b/Engine/source/T3D/gameFunctions.cpp @@ -32,7 +32,7 @@ #include "math/mEase.h" #include "core/module.h" #include "console/engineAPI.h" - +#include "platform/output/IDisplayDevice.h" static void RegisterGameFunctions(); static void Process3D(); @@ -82,6 +82,8 @@ static S32 gEaseBack = Ease::Back; static S32 gEaseBounce = Ease::Bounce; +extern bool gEditingMission; + extern void ShowInit(); //------------------------------------------------------------------------------ @@ -354,9 +356,44 @@ bool GameProcessCameraQuery(CameraQuery *query) sVisDistanceScale = mClampF( sVisDistanceScale, 0.01f, 1.0f ); query->farPlane = gClientSceneGraph->getVisibleDistance() * sVisDistanceScale; - F32 cameraFov; - if(!connection->getControlCameraFov(&cameraFov)) + // Provide some default values + query->projectionOffset = Point2F::Zero; + query->eyeOffset = Point3F::Zero; + + F32 cameraFov = 0.0f; + bool fovSet = false; + + // Try to use the connection's display deivce, if any, but only if the editor + // is not open + if(!gEditingMission && connection->hasDisplayDevice()) + { + const IDisplayDevice* display = connection->getDisplayDevice(); + + // The connection's display device may want to set the FOV + if(display->providesYFOV()) + { + cameraFov = mRadToDeg(display->getYFOV()); + fovSet = true; + } + + // The connection's display device may want to set the projection offset + if(display->providesProjectionOffset()) + { + query->projectionOffset = display->getProjectionOffset(); + } + + // The connection's display device may want to set the eye offset + if(display->providesEyeOffset()) + { + query->eyeOffset = display->getEyeOffset(); + } + } + + // Use the connection's FOV settings if requried + if(!fovSet && !connection->getControlCameraFov(&cameraFov)) + { return false; + } query->fov = mDegToRad(cameraFov); return true; diff --git a/Engine/source/T3D/lightFlareData.cpp b/Engine/source/T3D/lightFlareData.cpp index 8dfd9badc..5a3e0e322 100644 --- a/Engine/source/T3D/lightFlareData.cpp +++ b/Engine/source/T3D/lightFlareData.cpp @@ -278,7 +278,11 @@ bool LightFlareData::_testVisibility(const SceneRenderState *state, LightFlareSt // the last result. const Point3F &lightPos = flareState->lightMat.getPosition(); const RectI &viewport = GFX->getViewport(); - bool onScreen = MathUtils::mProjectWorldToScreen( lightPos, outLightPosSS, viewport, GFX->getWorldMatrix(), state->getSceneManager()->getNonClipProjection() ); + MatrixF projMatrix; + state->getFrustum().getProjectionMatrix(&projMatrix); + if( state->isReflectPass() ) + projMatrix = state->getSceneManager()->getNonClipProjection(); + bool onScreen = MathUtils::mProjectWorldToScreen( lightPos, outLightPosSS, viewport, GFX->getWorldMatrix(), projMatrix ); // It is onscreen, so raycast as a simple occlusion test. const LightInfo *lightInfo = flareState->lightInfo; @@ -452,13 +456,17 @@ void LightFlareData::prepRender( SceneRenderState *state, LightFlareState *flare Point3F oneOverViewportExtent( 1.0f / (F32)viewport.extent.x, 1.0f / (F32)viewport.extent.y, 0.0f ); // Really convert it to screen space. + lightPosSS.x -= viewport.point.x; lightPosSS.y -= viewport.point.y; lightPosSS *= oneOverViewportExtent; lightPosSS = ( lightPosSS * 2.0f ) - Point3F::One; lightPosSS.y = -lightPosSS.y; lightPosSS.z = 0.0f; - Point3F flareVec( -lightPosSS ); + // Take any projection offset into account so that the point where the flare's + // elements converge is at the 'eye' point rather than the center of the viewport. + const Point2F& projOffset = state->getFrustum().getProjectionOffset(); + Point3F flareVec( -lightPosSS + Point3F(projOffset.x, projOffset.y, 0.0f) ); const F32 flareLength = flareVec.len(); if ( flareLength > 0.0f ) flareVec *= 1.0f / flareLength; diff --git a/Engine/source/gfx/gfxDevice.cpp b/Engine/source/gfx/gfxDevice.cpp index 4f5cae5d7..9e57bcc81 100644 --- a/Engine/source/gfx/gfxDevice.cpp +++ b/Engine/source/gfx/gfxDevice.cpp @@ -159,6 +159,9 @@ GFXDevice::GFXDevice() // misc mAllowRender = true; + mCurrentRenderStyle = RS_Standard; + mCurrentProjectionOffset = Point2F::Zero; + mStereoEyeOffset = Point3F::Zero; mCanCurrentlyRender = false; mInitialized = false; diff --git a/Engine/source/gfx/gfxDevice.h b/Engine/source/gfx/gfxDevice.h index ec9fd6797..e153d02c5 100644 --- a/Engine/source/gfx/gfxDevice.h +++ b/Engine/source/gfx/gfxDevice.h @@ -231,6 +231,13 @@ private: //-------------------------------------------------------------------------- // Core GFX interface //-------------------------------------------------------------------------- +public: + enum GFXDeviceRenderStyles + { + RS_Standard = 0, + RS_StereoSideBySide = (1<<0), + }; + private: /// Adapter for this device. @@ -254,6 +261,15 @@ protected: /// Set if we're in a mode where we want rendering to occur. bool mAllowRender; + /// The style of rendering that is to be performed, based on GFXDeviceRenderStyles + U32 mCurrentRenderStyle; + + /// The current projection offset. May be used during side-by-side rendering, for example. + Point2F mCurrentProjectionOffset; + + /// Eye offset used when using a stereo rendering style + Point3F mStereoEyeOffset; + /// This will allow querying to see if a device is initialized and ready to /// have operations performed on it. bool mInitialized; @@ -285,6 +301,24 @@ public: inline bool allowRender() const { return mAllowRender; } + /// Retrieve the current rendering style based on GFXDeviceRenderStyles + U32 getCurrentRenderStyle() const { return mCurrentRenderStyle; } + + /// Set the current rendering style, based on GFXDeviceRenderStyles + void setCurrentRenderStyle(U32 style) { mCurrentRenderStyle = style; } + + /// Set the current projection offset used during stereo rendering + const Point2F& getCurrentProjectionOffset() { return mCurrentProjectionOffset; } + + /// Get the current projection offset used during stereo rendering + void setCurrentProjectionOffset(const Point2F& offset) { mCurrentProjectionOffset = offset; } + + /// Get the current eye offset used during stereo rendering + const Point3F& getStereoEyeOffset() { return mStereoEyeOffset; } + + /// Set the current eye offset used during stereo rendering + void setStereoEyeOffset(const Point3F& offset) { mStereoEyeOffset = offset; } + GFXCardProfiler* getCardProfiler() const { return mCardProfiler; } /// Returns active graphics adapter type. diff --git a/Engine/source/gui/3d/guiTSControl.cpp b/Engine/source/gui/3d/guiTSControl.cpp index e1d2ecc0b..95dc960c3 100644 --- a/Engine/source/gui/3d/guiTSControl.cpp +++ b/Engine/source/gui/3d/guiTSControl.cpp @@ -53,6 +53,13 @@ ConsoleDocClass( GuiTSCtrl, U32 GuiTSCtrl::smFrameCount = 0; Vector GuiTSCtrl::smAwakeTSCtrls; +ImplementEnumType( GuiTSRenderStyles, + "Style of rendering for a GuiTSCtrl.\n\n" + "@ingroup Gui3D" ) + { GuiTSCtrl::RenderStyleStandard, "standard" }, + { GuiTSCtrl::RenderStyleStereoSideBySide, "stereo side by side" }, +EndImplementEnumType; + //----------------------------------------------------------------------------- @@ -131,6 +138,8 @@ GuiTSCtrl::GuiTSCtrl() mForceFOV = 0; mReflectPriority = 1.0f; + mRenderStyle = RenderStyleStandard; + mSaveModelview.identity(); mSaveProjection.identity(); mSaveViewport.set( 0, 0, 10, 10 ); @@ -142,6 +151,9 @@ GuiTSCtrl::GuiTSCtrl() mLastCameraQuery.farPlane = 10.0f; mLastCameraQuery.nearPlane = 0.01f; + mLastCameraQuery.projectionOffset = Point2F::Zero; + mLastCameraQuery.eyeOffset = Point3F::Zero; + mLastCameraQuery.ortho = false; } @@ -165,6 +177,9 @@ void GuiTSCtrl::initPersistFields() "The reflect update priorities of all visible GuiTSCtrls are added together and each control is assigned " "a share of the per-frame reflection update time according to its percentage of the total priority value." ); + addField("renderStyle", TYPEID< RenderStyles >(), Offset(mRenderStyle, GuiTSCtrl), + "Indicates how this control should render its contents." ); + endGroup( "Rendering" ); Parent::initPersistFields(); @@ -256,7 +271,9 @@ F32 GuiTSCtrl::calculateViewDistance(F32 radius) F32 fov = mLastCameraQuery.fov; F32 wwidth; F32 wheight; - F32 aspectRatio = F32(getWidth()) / F32(getHeight()); + F32 renderWidth = (mRenderStyle == RenderStyleStereoSideBySide) ? F32(getWidth())*0.5f : F32(getWidth()); + F32 renderHeight = F32(getHeight()); + F32 aspectRatio = renderWidth / renderHeight; // Use the FOV to calculate the viewport height scale // then generate the width scale from the aspect ratio. @@ -321,10 +338,27 @@ void GuiTSCtrl::onRender(Point2I offset, const RectI &updateRect) mLastCameraQuery.cameraMatrix.mul(rotMat); } + // Set up the appropriate render style + U32 prevRenderStyle = GFX->getCurrentRenderStyle(); + Point2F prevProjectionOffset = GFX->getCurrentProjectionOffset(); + Point3F prevEyeOffset = GFX->getStereoEyeOffset(); + if(mRenderStyle == RenderStyleStereoSideBySide) + { + GFX->setCurrentRenderStyle(GFXDevice::RS_StereoSideBySide); + GFX->setCurrentProjectionOffset(mLastCameraQuery.projectionOffset); + GFX->setStereoEyeOffset(mLastCameraQuery.eyeOffset); + } + else + { + GFX->setCurrentRenderStyle(GFXDevice::RS_Standard); + } + // set up the camera and viewport stuff: F32 wwidth; F32 wheight; - F32 aspectRatio = F32(getWidth()) / F32(getHeight()); + F32 renderWidth = (mRenderStyle == RenderStyleStereoSideBySide) ? F32(getWidth())*0.5f : F32(getWidth()); + F32 renderHeight = F32(getHeight()); + F32 aspectRatio = renderWidth / renderHeight; // Use the FOV to calculate the viewport height scale // then generate the width scale from the aspect ratio. @@ -339,16 +373,28 @@ void GuiTSCtrl::onRender(Point2I offset, const RectI &updateRect) wwidth = aspectRatio * wheight; } - F32 hscale = wwidth * 2.0f / F32(getWidth()); - F32 vscale = wheight * 2.0f / F32(getHeight()); - - F32 left = (updateRect.point.x - offset.x) * hscale - wwidth; - F32 right = (updateRect.point.x + updateRect.extent.x - offset.x) * hscale - wwidth; - F32 top = wheight - vscale * (updateRect.point.y - offset.y); - F32 bottom = wheight - vscale * (updateRect.point.y + updateRect.extent.y - offset.y); + F32 hscale = wwidth * 2.0f / renderWidth; + F32 vscale = wheight * 2.0f / renderHeight; Frustum frustum; - frustum.set( mLastCameraQuery.ortho, left, right, top, bottom, mLastCameraQuery.nearPlane, mLastCameraQuery.farPlane ); + if(mRenderStyle == RenderStyleStereoSideBySide) + { + F32 left = 0.0f * hscale - wwidth; + F32 right = renderWidth * hscale - wwidth; + F32 top = wheight - vscale * 0.0f; + F32 bottom = wheight - vscale * renderHeight; + + frustum.set( mLastCameraQuery.ortho, left, right, top, bottom, mLastCameraQuery.nearPlane, mLastCameraQuery.farPlane ); + } + else + { + F32 left = (updateRect.point.x - offset.x) * hscale - wwidth; + F32 right = (updateRect.point.x + updateRect.extent.x - offset.x) * hscale - wwidth; + F32 top = wheight - vscale * (updateRect.point.y - offset.y); + F32 bottom = wheight - vscale * (updateRect.point.y + updateRect.extent.y - offset.y); + + frustum.set( mLastCameraQuery.ortho, left, right, top, bottom, mLastCameraQuery.nearPlane, mLastCameraQuery.farPlane ); + } // Manipulate the frustum for tiled screenshots const bool screenShotMode = gScreenShot && gScreenShot->isPending(); @@ -412,6 +458,11 @@ void GuiTSCtrl::onRender(Point2I offset, const RectI &updateRect) // we begin rendering the child controls. saver.restore(); + // Restore the render style and any stereo parameters + GFX->setCurrentRenderStyle(prevRenderStyle); + GFX->setCurrentProjectionOffset(prevProjectionOffset); + GFX->setStereoEyeOffset(prevEyeOffset); + // Allow subclasses to render 2D elements. GFX->setClipRect(updateRect); renderGui( offset, updateRect ); diff --git a/Engine/source/gui/3d/guiTSControl.h b/Engine/source/gui/3d/guiTSControl.h index 809e49c78..cd438c526 100644 --- a/Engine/source/gui/3d/guiTSControl.h +++ b/Engine/source/gui/3d/guiTSControl.h @@ -36,6 +36,8 @@ struct CameraQuery F32 nearPlane; F32 farPlane; F32 fov; + Point2F projectionOffset; + Point3F eyeOffset; bool ortho; MatrixF cameraMatrix; }; @@ -45,12 +47,17 @@ class GuiTSCtrl : public GuiContainer { typedef GuiContainer Parent; +public: + enum RenderStyles { + RenderStyleStandard = 0, + RenderStyleStereoSideBySide = (1<<0), + }; + +protected: static U32 smFrameCount; F32 mCameraZRot; F32 mForceFOV; -protected: - /// A list of GuiTSCtrl which are awake and /// most likely rendering. static Vector smAwakeTSCtrls; @@ -59,8 +66,11 @@ protected: /// update timeslice for this viewport to get. F32 mReflectPriority; - F32 mOrthoWidth; - F32 mOrthoHeight; + /// The current render type + U32 mRenderStyle; + + F32 mOrthoWidth; + F32 mOrthoHeight; MatrixF mSaveModelview; MatrixF mSaveProjection; @@ -150,4 +160,8 @@ public: DECLARE_DESCRIPTION( "Abstract base class for controls that render a 3D viewport." ); }; +typedef GuiTSCtrl::RenderStyles GuiTSRenderStyles; + +DefineEnumType( GuiTSRenderStyles ); + #endif // _GUITSCONTROL_H_ diff --git a/Engine/source/lighting/advanced/advancedLightBinManager.cpp b/Engine/source/lighting/advanced/advancedLightBinManager.cpp index 1aeef56ff..069f0e762 100644 --- a/Engine/source/lighting/advanced/advancedLightBinManager.cpp +++ b/Engine/source/lighting/advanced/advancedLightBinManager.cpp @@ -41,7 +41,6 @@ #include "math/util/matrixSet.h" #include "console/consoleTypes.h" - const RenderInstType AdvancedLightBinManager::RIT_LightInfo( "LightInfo" ); const String AdvancedLightBinManager::smBufferName( "lightinfo" ); @@ -49,7 +48,6 @@ ShadowFilterMode AdvancedLightBinManager::smShadowFilterMode = ShadowFilterMode_ bool AdvancedLightBinManager::smPSSMDebugRender = false; bool AdvancedLightBinManager::smUseSSAOMask = false; - ImplementEnumType( ShadowFilterMode, "The shadow filtering modes for Advanced Lighting shadows.\n" "@ingroup AdvancedLighting" ) @@ -221,7 +219,7 @@ void AdvancedLightBinManager::addLight( LightInfo *light ) curBin.push_back( lEntry ); } -void AdvancedLightBinManager::clear() +void AdvancedLightBinManager::clearAllLights() { Con::setIntVariable("lightMetrics::activeLights", mLightBin.size()); Con::setIntVariable("lightMetrics::culledLights", mNumLightsCulled); @@ -454,6 +452,33 @@ void AdvancedLightBinManager::_setupPerFrameParameters( const SceneRenderState * const Point3F *wsFrustumPoints = frustum.getPoints(); const Point3F& cameraPos = frustum.getPosition(); + // Perform a camera offset. We need to manually perform this offset on the sun (or vector) light's + // polygon, which is at the far plane. + const Point2F& projOffset = frustum.getProjectionOffset(); + Point3F cameraOffsetPos = cameraPos; + if(!projOffset.isZero()) + { + // First we need to calculate the offset at the near plane. The projOffset + // given above can be thought of a percent as it ranges from 0..1 (or 0..-1). + F32 nearOffset = frustum.getNearRight() * projOffset.x; + + // Now given the near plane distance from the camera we can solve the right + // triangle and calcuate the SIN theta for the offset at the near plane. + // SIN theta = x/y + F32 sinTheta = nearOffset / frustum.getNearDist(); + + // Finally, we can calcuate the offset at the far plane, which is where our sun (or vector) + // light's polygon is drawn. + F32 farOffset = frustum.getFarDist() * sinTheta; + + // We can now apply this far plane offset to the far plane itself, which then compensates + // for the project offset. + MatrixF camTrans = frustum.getTransform(); + VectorF offset = camTrans.getRightVector(); + offset *= farOffset; + cameraOffsetPos += offset; + } + // Now build the quad for drawing full-screen vector light // passes.... this is a volatile VB and updates every frame. FarFrustumQuadVert verts[4]; @@ -461,18 +486,22 @@ void AdvancedLightBinManager::_setupPerFrameParameters( const SceneRenderState * verts[0].point.set( wsFrustumPoints[Frustum::FarBottomLeft] - cameraPos ); invCam.mulP( wsFrustumPoints[Frustum::FarBottomLeft], &verts[0].normal ); verts[0].texCoord.set( -1.0, -1.0 ); + verts[0].tangent.set(wsFrustumPoints[Frustum::FarBottomLeft] - cameraOffsetPos); verts[1].point.set( wsFrustumPoints[Frustum::FarTopLeft] - cameraPos ); invCam.mulP( wsFrustumPoints[Frustum::FarTopLeft], &verts[1].normal ); verts[1].texCoord.set( -1.0, 1.0 ); + verts[1].tangent.set(wsFrustumPoints[Frustum::FarTopLeft] - cameraOffsetPos); verts[2].point.set( wsFrustumPoints[Frustum::FarTopRight] - cameraPos ); invCam.mulP( wsFrustumPoints[Frustum::FarTopRight], &verts[2].normal ); verts[2].texCoord.set( 1.0, 1.0 ); + verts[2].tangent.set(wsFrustumPoints[Frustum::FarTopRight] - cameraOffsetPos); verts[3].point.set( wsFrustumPoints[Frustum::FarBottomRight] - cameraPos ); invCam.mulP( wsFrustumPoints[Frustum::FarBottomRight], &verts[3].normal ); verts[3].texCoord.set( 1.0, -1.0 ); + verts[3].tangent.set(wsFrustumPoints[Frustum::FarBottomRight] - cameraOffsetPos); } mFarFrustumQuadVerts.set( GFX, 4 ); dMemcpy( mFarFrustumQuadVerts.lock(), verts, sizeof( verts ) ); diff --git a/Engine/source/lighting/advanced/advancedLightBinManager.h b/Engine/source/lighting/advanced/advancedLightBinManager.h index a8aa20659..ee2064885 100644 --- a/Engine/source/lighting/advanced/advancedLightBinManager.h +++ b/Engine/source/lighting/advanced/advancedLightBinManager.h @@ -107,12 +107,15 @@ public: // RenderBinManager virtual void render(SceneRenderState *); - virtual void clear(); + virtual void clear() {} virtual void sort() {} // Add a light to the bins void addLight( LightInfo *light ); + // Clear all lights from the bins + void clearAllLights(); + virtual bool setTargetSize(const Point2I &newTargetSize); // ConsoleObject interface @@ -220,7 +223,7 @@ protected: AdvancedLightBufferConditioner *mConditioner; - typedef GFXVertexPNT FarFrustumQuadVert; + typedef GFXVertexPNTT FarFrustumQuadVert; GFXVertexBufferHandle mFarFrustumQuadVerts; diff --git a/Engine/source/lighting/advanced/advancedLightManager.cpp b/Engine/source/lighting/advanced/advancedLightManager.cpp index 36be8db78..9a911acfb 100644 --- a/Engine/source/lighting/advanced/advancedLightManager.cpp +++ b/Engine/source/lighting/advanced/advancedLightManager.cpp @@ -438,7 +438,7 @@ void AdvancedLightManager::unregisterAllLights() Parent::unregisterAllLights(); if ( mLightBinManager ) - mLightBinManager->clear(); + mLightBinManager->clearAllLights(); } bool AdvancedLightManager::setTextureStage( const SceneData &sgData, diff --git a/Engine/source/math/util/frustum.cpp b/Engine/source/math/util/frustum.cpp index e5c6fd43b..7cff16e4c 100644 --- a/Engine/source/math/util/frustum.cpp +++ b/Engine/source/math/util/frustum.cpp @@ -76,6 +76,9 @@ Frustum::Frustum( bool isOrtho, mNumTiles = 1; mCurrTile.set(0,0); mTileOverlap.set(0.0f, 0.0f); + + mProjectionOffset.zero(); + mProjectionOffsetMatrix.identity(); } //----------------------------------------------------------------------------- @@ -433,12 +436,27 @@ void Frustum::mulL( const MatrixF& mat ) //----------------------------------------------------------------------------- +void Frustum::setProjectionOffset(const Point2F& offsetMat) +{ + mProjectionOffset = offsetMat; + mProjectionOffsetMatrix.identity(); + mProjectionOffsetMatrix.setPosition(Point3F(mProjectionOffset.x, mProjectionOffset.y, 0.0f)); +} + +//----------------------------------------------------------------------------- + void Frustum::getProjectionMatrix( MatrixF *proj, bool gfxRotate ) const { if (mIsOrtho) + { MathUtils::makeOrthoProjection(proj, mNearLeft, mNearRight, mNearTop, mNearBottom, mNearDist, mFarDist, gfxRotate); + proj->mulL(mProjectionOffsetMatrix); + } else + { MathUtils::makeProjection(proj, mNearLeft, mNearRight, mNearTop, mNearBottom, mNearDist, mFarDist, gfxRotate); + proj->mulL(mProjectionOffsetMatrix); + } } //----------------------------------------------------------------------------- diff --git a/Engine/source/math/util/frustum.h b/Engine/source/math/util/frustum.h index 4a756225c..869abcf47 100644 --- a/Engine/source/math/util/frustum.h +++ b/Engine/source/math/util/frustum.h @@ -246,6 +246,12 @@ class Frustum : public PolyhedronImpl< FrustumData > /// @} + /// Offset used for projection matrix calculations + Point2F mProjectionOffset; + + /// The calculated projection offset matrix + MatrixF mProjectionOffsetMatrix; + public: /// @name Constructors @@ -403,9 +409,23 @@ class Frustum : public PolyhedronImpl< FrustumData > /// points typically used for early rejection. const Box3F& getBounds() const { _update(); return mBounds; } + /// Get the offset used when calculating the projection matrix + const Point2F& getProjectionOffset() const { return mProjectionOffset; } + + /// Get the offset matrix used when calculating the projection matrix + const MatrixF& getProjectionOffsetMatrix() const { return mProjectionOffsetMatrix; } + + /// Set the offset used when calculating the projection matrix + void setProjectionOffset(const Point2F& offsetMat); + + /// Clear any offset used when calculating the projection matrix + void clearProjectionOffset() { mProjectionOffset.zero(); mProjectionOffsetMatrix.identity(); } + /// Generates a projection matrix from the frustum. void getProjectionMatrix( MatrixF *proj, bool gfxRotate=true ) const; + /// Will update the frustum if it is dirty + void update() { _update(); } /// @} /// @name Culling diff --git a/Engine/source/platform/output/IDisplayDevice.h b/Engine/source/platform/output/IDisplayDevice.h new file mode 100644 index 000000000..80e036a01 --- /dev/null +++ b/Engine/source/platform/output/IDisplayDevice.h @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _IDISPLAYDEVICE_H_ +#define _IDISPLAYDEVICE_H_ + +#include "console/consoleTypes.h" + +// Defines a custom display device that requires particular rendering settings +// in order for a scene to display correctly. + +class IDisplayDevice +{ +public: + virtual bool providesYFOV() const = 0; + virtual F32 getYFOV() const = 0; + + virtual bool providesEyeOffset() const = 0; + virtual const Point3F& getEyeOffset() const = 0; + + virtual bool providesProjectionOffset() const = 0; + virtual const Point2F& getProjectionOffset() const = 0; +}; + +#endif // _IDISPLAYDEVICE_H_ diff --git a/Engine/source/postFx/postEffect.cpp b/Engine/source/postFx/postEffect.cpp index dce0c9447..1dcfae1ed 100644 --- a/Engine/source/postFx/postEffect.cpp +++ b/Engine/source/postFx/postEffect.cpp @@ -435,26 +435,53 @@ void PostEffect::_updateScreenGeometry( const Frustum &frustum, const Point3F *frustumPoints = frustum.getPoints(); const Point3F& cameraPos = frustum.getPosition(); + // Perform a camera offset. We need to manually perform this offset on the postFx's + // polygon, which is at the far plane. + const Point2F& projOffset = frustum.getProjectionOffset(); + Point3F cameraOffsetPos = cameraPos; + if(!projOffset.isZero()) + { + // First we need to calculate the offset at the near plane. The projOffset + // given above can be thought of a percent as it ranges from 0..1 (or 0..-1). + F32 nearOffset = frustum.getNearRight() * projOffset.x; + + // Now given the near plane distance from the camera we can solve the right + // triangle and calcuate the SIN theta for the offset at the near plane. + // SIN theta = x/y + F32 sinTheta = nearOffset / frustum.getNearDist(); + + // Finally, we can calcuate the offset at the far plane, which is where our sun (or vector) + // light's polygon is drawn. + F32 farOffset = frustum.getFarDist() * sinTheta; + + // We can now apply this far plane offset to the far plane itself, which then compensates + // for the project offset. + MatrixF camTrans = frustum.getTransform(); + VectorF offset = camTrans.getRightVector(); + offset *= farOffset; + cameraOffsetPos += offset; + } + PFXVertex *vert = outVB->lock(); vert->point.set( -1.0, -1.0, 0.0 ); vert->texCoord.set( 0.0f, 1.0f ); - vert->wsEyeRay = frustumPoints[Frustum::FarBottomLeft] - cameraPos; + vert->wsEyeRay = frustumPoints[Frustum::FarBottomLeft] - cameraOffsetPos; vert++; vert->point.set( -1.0, 1.0, 0.0 ); vert->texCoord.set( 0.0f, 0.0f ); - vert->wsEyeRay = frustumPoints[Frustum::FarTopLeft] - cameraPos; + vert->wsEyeRay = frustumPoints[Frustum::FarTopLeft] - cameraOffsetPos; vert++; vert->point.set( 1.0, 1.0, 0.0 ); vert->texCoord.set( 1.0f, 0.0f ); - vert->wsEyeRay = frustumPoints[Frustum::FarTopRight] - cameraPos; + vert->wsEyeRay = frustumPoints[Frustum::FarTopRight] - cameraOffsetPos; vert++; vert->point.set( 1.0, -1.0, 0.0 ); vert->texCoord.set( 1.0f, 1.0f ); - vert->wsEyeRay = frustumPoints[Frustum::FarBottomRight] - cameraPos; + vert->wsEyeRay = frustumPoints[Frustum::FarBottomRight] - cameraOffsetPos; vert++; outVB->unlock(); @@ -710,6 +737,8 @@ void PostEffect::_setupConstants( const SceneRenderState *state ) MathUtils::mProjectWorldToScreen( lightPos, &sunPos, GFX->getViewport(), tmp, proj ); // And normalize it to the 0 to 1 range. + sunPos.x -= (F32)GFX->getViewport().point.x; + sunPos.y -= (F32)GFX->getViewport().point.y; sunPos.x /= (F32)GFX->getViewport().extent.x; sunPos.y /= (F32)GFX->getViewport().extent.y; diff --git a/Engine/source/scene/sceneManager.cpp b/Engine/source/scene/sceneManager.cpp index 3e323efdb..5210d5345 100644 --- a/Engine/source/scene/sceneManager.cpp +++ b/Engine/source/scene/sceneManager.cpp @@ -233,7 +233,70 @@ void SceneManager::renderScene( SceneRenderState* renderState, U32 objectMask, S // Render the scene. - renderSceneNoLights( renderState, objectMask, baseObject, baseZone ); + if(GFX->getCurrentRenderStyle() == GFXDevice::RS_StereoSideBySide) + { + // Store previous values + RectI originalVP = GFX->getViewport(); + MatrixF originalWorld = GFX->getWorldMatrix(); + + Point2F projOffset = GFX->getCurrentProjectionOffset(); + Point3F eyeOffset = GFX->getStereoEyeOffset(); + + // Render left half of display + RectI leftVP = originalVP; + leftVP.extent.x *= 0.5; + GFX->setViewport(leftVP); + + MatrixF leftWorldTrans(true); + leftWorldTrans.setPosition(Point3F(eyeOffset.x, eyeOffset.y, eyeOffset.z)); + MatrixF leftWorld(originalWorld); + leftWorld.mulL(leftWorldTrans); + GFX->setWorldMatrix(leftWorld); + + Frustum gfxFrustum = GFX->getFrustum(); + gfxFrustum.setProjectionOffset(Point2F(projOffset.x, projOffset.y)); + GFX->setFrustum(gfxFrustum); + + SceneCameraState cameraStateLeft = SceneCameraState::fromGFX(); + SceneRenderState renderStateLeft( this, renderState->getScenePassType(), cameraStateLeft ); + renderStateLeft.setSceneRenderStyle(SRS_SideBySide); + renderStateLeft.setSceneRenderField(0); + + renderSceneNoLights( &renderStateLeft, objectMask, baseObject, baseZone ); + + // Render right half of display + RectI rightVP = originalVP; + rightVP.extent.x *= 0.5; + rightVP.point.x += rightVP.extent.x; + GFX->setViewport(rightVP); + + MatrixF rightWorldTrans(true); + rightWorldTrans.setPosition(Point3F(-eyeOffset.x, eyeOffset.y, eyeOffset.z)); + MatrixF rightWorld(originalWorld); + rightWorld.mulL(rightWorldTrans); + GFX->setWorldMatrix(rightWorld); + + gfxFrustum = GFX->getFrustum(); + gfxFrustum.setProjectionOffset(Point2F(-projOffset.x, projOffset.y)); + GFX->setFrustum(gfxFrustum); + + SceneCameraState cameraStateRight = SceneCameraState::fromGFX(); + SceneRenderState renderStateRight( this, renderState->getScenePassType(), cameraStateRight ); + renderStateRight.setSceneRenderStyle(SRS_SideBySide); + renderStateRight.setSceneRenderField(1); + + renderSceneNoLights( &renderStateRight, objectMask, baseObject, baseZone ); + + // Restore previous values + GFX->setWorldMatrix(originalWorld); + gfxFrustum.clearProjectionOffset(); + GFX->setFrustum(gfxFrustum); + GFX->setViewport(originalVP); + } + else + { + renderSceneNoLights( renderState, objectMask, baseObject, baseZone ); + } // Trigger the post-render signal. diff --git a/Engine/source/scene/sceneManager.h b/Engine/source/scene/sceneManager.h index deb43fe25..00eb3bd10 100644 --- a/Engine/source/scene/sceneManager.h +++ b/Engine/source/scene/sceneManager.h @@ -89,6 +89,18 @@ enum ScenePassType }; +/// The type of scene render style +/// @see SceneRenderState +enum SceneRenderStyle +{ + /// The regular style of rendering + SRS_Standard, + + /// Side-by-side style rendering + SRS_SideBySide, +}; + + /// An object that manages the SceneObjects belonging to a scene. class SceneManager { diff --git a/Engine/source/scene/sceneRenderState.cpp b/Engine/source/scene/sceneRenderState.cpp index 8da65407d..0aeb1d273 100644 --- a/Engine/source/scene/sceneRenderState.cpp +++ b/Engine/source/scene/sceneRenderState.cpp @@ -44,7 +44,9 @@ SceneRenderState::SceneRenderState( SceneManager* sceneManager, mUsePostEffects( usePostEffects ), mDisableAdvancedLightingBins( false ), mRenderArea( view.getFrustum().getBounds() ), - mAmbientLightColor( sceneManager->getAmbientLightColor() ) + mAmbientLightColor( sceneManager->getAmbientLightColor() ), + mSceneRenderStyle( SRS_Standard ), + mRenderField( 0 ) { // Setup the default parameters for the screen metrics methods. mDiffuseCameraTransform = view.getViewWorldMatrix(); diff --git a/Engine/source/scene/sceneRenderState.h b/Engine/source/scene/sceneRenderState.h index e8503548c..7a66e40aa 100644 --- a/Engine/source/scene/sceneRenderState.h +++ b/Engine/source/scene/sceneRenderState.h @@ -69,6 +69,12 @@ class SceneRenderState /// The type of scene render pass we're doing. ScenePassType mScenePassType; + /// The render style being performed + SceneRenderStyle mSceneRenderStyle; + + /// When doing stereo rendering, the current field that is being rendered + S32 mRenderField; + /// The render pass which we are setting up with this scene state. RenderPassManager* mRenderPass; @@ -219,6 +225,23 @@ class SceneRenderState /// @} + /// @name Render Style + /// @{ + + /// Get the rendering style used for the scene + SceneRenderStyle getSceneRenderStyle() const { return mSceneRenderStyle; } + + /// Set the rendering style used for the scene + void setSceneRenderStyle(SceneRenderStyle style) { mSceneRenderStyle = style; } + + /// Get the stereo field being rendered + S32 getSceneRenderField() const { return mRenderField; } + + /// Set the stereo field being rendered + void setSceneRenderField(S32 field) { mRenderField = field; } + + /// @} + /// @name Transforms, projections, and viewports. /// @{ diff --git a/Templates/Empty/game/core/scripts/client/postFx/lightRay.cs b/Templates/Empty/game/core/scripts/client/postFx/lightRay.cs index b8b15adb7..5a6d7aa28 100644 --- a/Templates/Empty/game/core/scripts/client/postFx/lightRay.cs +++ b/Templates/Empty/game/core/scripts/client/postFx/lightRay.cs @@ -58,8 +58,9 @@ singleton PostEffect( LightRayPostFX ) isEnabled = false; allowReflectPass = false; - renderTime = "PFXAfterDiffuse"; - renderPriority = 0.1; + renderTime = "PFXBeforeBin"; + renderBin = "EditorBin"; + renderPriority = 10; shader = LightRayOccludeShader; stateBlock = LightRayStateBlock; diff --git a/Templates/Empty/game/shaders/common/hlslStructs.h b/Templates/Empty/game/shaders/common/hlslStructs.h index 7d66a0481..6a57e4db7 100644 --- a/Templates/Empty/game/shaders/common/hlslStructs.h +++ b/Templates/Empty/game/shaders/common/hlslStructs.h @@ -89,6 +89,14 @@ struct VertexIn_PNT float2 uv0 : TEXCOORD0; }; +struct VertexIn_PNTT +{ + float4 pos : POSITION; + float3 normal : NORMAL; + float3 tangent : TANGENT; + float2 uv0 : TEXCOORD0; +}; + struct VertexIn_PNCT { float4 pos : POSITION; diff --git a/Templates/Empty/game/shaders/common/lighting/advanced/farFrustumQuadV.hlsl b/Templates/Empty/game/shaders/common/lighting/advanced/farFrustumQuadV.hlsl index bf6fa9619..08cf61285 100644 --- a/Templates/Empty/game/shaders/common/lighting/advanced/farFrustumQuadV.hlsl +++ b/Templates/Empty/game/shaders/common/lighting/advanced/farFrustumQuadV.hlsl @@ -24,7 +24,7 @@ #include "farFrustumQuad.hlsl" -FarFrustumQuadConnectV main( VertexIn_PNT IN, +FarFrustumQuadConnectV main( VertexIn_PNTT IN, uniform float4 rtParams0 ) { FarFrustumQuadConnectV OUT; @@ -36,7 +36,7 @@ FarFrustumQuadConnectV main( VertexIn_PNT IN, // Interpolators will generate eye rays the // from far-frustum corners. - OUT.wsEyeRay = IN.pos.xyz; + OUT.wsEyeRay = IN.tangent.xyz; OUT.vsEyeRay = IN.normal.xyz; return OUT; diff --git a/Templates/Empty/game/shaders/common/lighting/advanced/vectorLightP.hlsl b/Templates/Empty/game/shaders/common/lighting/advanced/vectorLightP.hlsl index 326802966..4be1bc9bd 100644 --- a/Templates/Empty/game/shaders/common/lighting/advanced/vectorLightP.hlsl +++ b/Templates/Empty/game/shaders/common/lighting/advanced/vectorLightP.hlsl @@ -73,7 +73,7 @@ float4 main( FarFrustumQuadConnectP IN, // Use eye ray to get ws pos float4 worldPos = float4(eyePosWorld + IN.wsEyeRay * depth, 1.0f); - + // Get the light attenuation. float dotNL = dot(-lightDirection, normal); diff --git a/Templates/Full/game/core/scripts/client/postFx/lightRay.cs b/Templates/Full/game/core/scripts/client/postFx/lightRay.cs index b8b15adb7..5a6d7aa28 100644 --- a/Templates/Full/game/core/scripts/client/postFx/lightRay.cs +++ b/Templates/Full/game/core/scripts/client/postFx/lightRay.cs @@ -58,8 +58,9 @@ singleton PostEffect( LightRayPostFX ) isEnabled = false; allowReflectPass = false; - renderTime = "PFXAfterDiffuse"; - renderPriority = 0.1; + renderTime = "PFXBeforeBin"; + renderBin = "EditorBin"; + renderPriority = 10; shader = LightRayOccludeShader; stateBlock = LightRayStateBlock; diff --git a/Templates/Full/game/shaders/common/hlslStructs.h b/Templates/Full/game/shaders/common/hlslStructs.h index 7d66a0481..6a57e4db7 100644 --- a/Templates/Full/game/shaders/common/hlslStructs.h +++ b/Templates/Full/game/shaders/common/hlslStructs.h @@ -89,6 +89,14 @@ struct VertexIn_PNT float2 uv0 : TEXCOORD0; }; +struct VertexIn_PNTT +{ + float4 pos : POSITION; + float3 normal : NORMAL; + float3 tangent : TANGENT; + float2 uv0 : TEXCOORD0; +}; + struct VertexIn_PNCT { float4 pos : POSITION; diff --git a/Templates/Full/game/shaders/common/lighting/advanced/farFrustumQuadV.hlsl b/Templates/Full/game/shaders/common/lighting/advanced/farFrustumQuadV.hlsl index bf6fa9619..08cf61285 100644 --- a/Templates/Full/game/shaders/common/lighting/advanced/farFrustumQuadV.hlsl +++ b/Templates/Full/game/shaders/common/lighting/advanced/farFrustumQuadV.hlsl @@ -24,7 +24,7 @@ #include "farFrustumQuad.hlsl" -FarFrustumQuadConnectV main( VertexIn_PNT IN, +FarFrustumQuadConnectV main( VertexIn_PNTT IN, uniform float4 rtParams0 ) { FarFrustumQuadConnectV OUT; @@ -36,7 +36,7 @@ FarFrustumQuadConnectV main( VertexIn_PNT IN, // Interpolators will generate eye rays the // from far-frustum corners. - OUT.wsEyeRay = IN.pos.xyz; + OUT.wsEyeRay = IN.tangent.xyz; OUT.vsEyeRay = IN.normal.xyz; return OUT; diff --git a/Templates/Full/game/shaders/common/lighting/advanced/vectorLightP.hlsl b/Templates/Full/game/shaders/common/lighting/advanced/vectorLightP.hlsl index 326802966..4be1bc9bd 100644 --- a/Templates/Full/game/shaders/common/lighting/advanced/vectorLightP.hlsl +++ b/Templates/Full/game/shaders/common/lighting/advanced/vectorLightP.hlsl @@ -73,7 +73,7 @@ float4 main( FarFrustumQuadConnectP IN, // Use eye ray to get ws pos float4 worldPos = float4(eyePosWorld + IN.wsEyeRay * depth, 1.0f); - + // Get the light attenuation. float dotNL = dot(-lightDirection, normal); diff --git a/Tools/projectGenerator/modules/core.inc b/Tools/projectGenerator/modules/core.inc index 84cb1c016..a6f3cae5a 100644 --- a/Tools/projectGenerator/modules/core.inc +++ b/Tools/projectGenerator/modules/core.inc @@ -73,6 +73,7 @@ switch( Generator::$platform ) addEngineSrcDir('platform/threads'); addEngineSrcDir('platform/async'); addEngineSrcDir('platform/input'); +addEngineSrcDir('platform/output'); addEngineSrcDir('app'); addEngineSrcDir('app/net');