//********************************** Banshee Engine (www.banshee3d.com) **************************************************// //**************** Copyright (c) 2016 Marko Pintera (marko.pintera@gmail.com). All rights reserved. **********************// #include "Renderer/BsCamera.h" #include "RTTI/BsCameraRTTI.h" #include "Math/BsMath.h" #include "Math/BsMatrix3.h" #include "Math/BsVector2.h" #include "Math/BsAABox.h" #include "Math/BsSphere.h" #include "Error/BsException.h" #include "RenderAPI/BsRenderAPI.h" #include "Scene/BsSceneObject.h" #include "Renderer/BsRendererManager.h" #include "Renderer/BsRenderer.h" #include "Allocators/BsFrameAlloc.h" #include "Scene/BsSceneManager.h" namespace bs { const float CameraBase::INFINITE_FAR_PLANE_ADJUST = 0.00001f; CameraBase::CameraBase() : mLayers(0xFFFFFFFFFFFFFFFF), mProjType(PT_PERSPECTIVE), mHorzFOV(Degree(90.0f)), mFarDist(1000.0f) , mNearDist(0.05f), mAspect(1.33333333333333f), mOrthoHeight(5), mPriority(0), mCustomViewMatrix(false) , mCustomProjMatrix(false), mMSAA(1), mFrustumExtentsManuallySet(false), mProjMatrixRS(BsZero), mProjMatrix(BsZero) , mViewMatrix(BsZero), mProjMatrixRSInv(BsZero), mProjMatrixInv(BsZero), mViewMatrixInv(BsZero) , mRecalcFrustum(true), mRecalcFrustumPlanes(true), mRecalcView(true) { mRenderSettings = bs_shared_ptr_new(); invalidateFrustum(); } void CameraBase::setHorzFOV(const Radian& fov) { mHorzFOV = fov; invalidateFrustum(); _markCoreDirty(); } const Radian& CameraBase::getHorzFOV() const { return mHorzFOV; } void CameraBase::setFarClipDistance(float farPlane) { mFarDist = farPlane; invalidateFrustum(); _markCoreDirty(); } float CameraBase::getFarClipDistance() const { return mFarDist; } void CameraBase::setNearClipDistance(float nearPlane) { if (nearPlane <= 0) { LOGERR("Near clip distance must be greater than zero."); return; } mNearDist = nearPlane; invalidateFrustum(); _markCoreDirty(); } float CameraBase::getNearClipDistance() const { return mNearDist; } const Matrix4& CameraBase::getProjectionMatrix() const { updateFrustum(); return mProjMatrix; } const Matrix4& CameraBase::getProjectionMatrixInv() const { updateFrustum(); return mProjMatrixInv; } const Matrix4& CameraBase::getProjectionMatrixRS() const { updateFrustum(); return mProjMatrixRS; } const Matrix4& CameraBase::getProjectionMatrixRSInv() const { updateFrustum(); return mProjMatrixRSInv; } const Matrix4& CameraBase::getViewMatrix() const { updateView(); return mViewMatrix; } const Matrix4& CameraBase::getViewMatrixInv() const { updateView(); return mViewMatrixInv; } const ConvexVolume& CameraBase::getFrustum() const { // Make any pending updates to the calculated frustum planes updateFrustumPlanes(); return mFrustum; } ConvexVolume CameraBase::getWorldFrustum() const { const Vector& frustumPlanes = getFrustum().getPlanes(); const Transform& tfrm = getTransform(); Matrix4 worldMatrix; worldMatrix.setTRS(tfrm.getPosition(), tfrm.getRotation(), Vector3::ONE); Vector worldPlanes(frustumPlanes.size()); UINT32 i = 0; for (auto& plane : frustumPlanes) { worldPlanes[i] = worldMatrix.multiplyAffine(plane); i++; } return ConvexVolume(worldPlanes); } void CameraBase::calcProjectionParameters(float& left, float& right, float& bottom, float& top) const { if (mCustomProjMatrix) { // Convert clipspace corners to camera space Matrix4 invProj = mProjMatrix.inverse(); Vector3 topLeft(-0.5f, 0.5f, 0.0f); Vector3 bottomRight(0.5f, -0.5f, 0.0f); topLeft = invProj.multiply(topLeft); bottomRight = invProj.multiply(bottomRight); left = topLeft.x; top = topLeft.y; right = bottomRight.x; bottom = bottomRight.y; } else { if (mFrustumExtentsManuallySet) { left = mLeft; right = mRight; top = mTop; bottom = mBottom; } else if (mProjType == PT_PERSPECTIVE) { Radian thetaX(mHorzFOV * 0.5f); float tanThetaX = Math::tan(thetaX); float tanThetaY = tanThetaX / mAspect; float half_w = tanThetaX * mNearDist; float half_h = tanThetaY * mNearDist; left = -half_w; right = half_w; bottom = -half_h; top = half_h; mLeft = left; mRight = right; mTop = top; mBottom = bottom; } else { float half_w = getOrthoWindowWidth() * 0.5f; float half_h = getOrthoWindowHeight() * 0.5f; left = -half_w; right = half_w; bottom = -half_h; top = half_h; mLeft = left; mRight = right; mTop = top; mBottom = bottom; } } } void CameraBase::updateFrustum() const { if (isFrustumOutOfDate()) { float left, right, bottom, top; calcProjectionParameters(left, right, bottom, top); if (!mCustomProjMatrix) { float inv_w = 1 / (right - left); float inv_h = 1 / (top - bottom); float inv_d = 1 / (mFarDist - mNearDist); if (mProjType == PT_PERSPECTIVE) { float A = 2 * mNearDist * inv_w; float B = 2 * mNearDist * inv_h; float C = (right + left) * inv_w; float D = (top + bottom) * inv_h; float q, qn; if (mFarDist == 0) { // Infinite far plane q = CameraBase::INFINITE_FAR_PLANE_ADJUST - 1; qn = mNearDist * (CameraBase::INFINITE_FAR_PLANE_ADJUST - 2); } else { q = -(mFarDist + mNearDist) * inv_d; qn = -2 * (mFarDist * mNearDist) * inv_d; } mProjMatrix = Matrix4::ZERO; mProjMatrix[0][0] = A; mProjMatrix[0][2] = C; mProjMatrix[1][1] = B; mProjMatrix[1][2] = D; mProjMatrix[2][2] = q; mProjMatrix[2][3] = qn; mProjMatrix[3][2] = -1; } else if (mProjType == PT_ORTHOGRAPHIC) { float A = 2 * inv_w; float B = 2 * inv_h; float C = -(right + left) * inv_w; float D = -(top + bottom) * inv_h; float q, qn; if (mFarDist == 0) { // Can not do infinite far plane here, avoid divided zero only q = -CameraBase::INFINITE_FAR_PLANE_ADJUST / mNearDist; qn = -CameraBase::INFINITE_FAR_PLANE_ADJUST - 1; } else { q = -2 * inv_d; qn = -(mFarDist + mNearDist) * inv_d; } mProjMatrix = Matrix4::ZERO; mProjMatrix[0][0] = A; mProjMatrix[0][3] = C; mProjMatrix[1][1] = B; mProjMatrix[1][3] = D; mProjMatrix[2][2] = q; mProjMatrix[2][3] = qn; mProjMatrix[3][3] = 1; } } ct::RenderAPI* renderAPI = ct::RenderAPI::instancePtr(); renderAPI->convertProjectionMatrix(mProjMatrix, mProjMatrixRS); mProjMatrixInv = mProjMatrix.inverse(); mProjMatrixRSInv = mProjMatrixRS.inverse(); // Calculate bounding box (local) // Box is from 0, down -Z, max dimensions as determined from far plane // If infinite view frustum just pick a far value float farDist = (mFarDist == 0) ? 100000 : mFarDist; // Near plane bounds Vector3 min(left, bottom, -farDist); Vector3 max(right, top, 0); if (mCustomProjMatrix) { // Some custom projection matrices can have unusual inverted settings // So make sure the AABB is the right way around to start with Vector3 tmp = min; min.floor(max); max.ceil(tmp); } if (mProjType == PT_PERSPECTIVE) { // Merge with far plane bounds float radio = farDist / mNearDist; min.floor(Vector3(left * radio, bottom * radio, -farDist)); max.ceil(Vector3(right * radio, top * radio, 0)); } mBoundingBox.setExtents(min, max); mRecalcFrustum = false; mRecalcFrustumPlanes = true; } } bool CameraBase::isFrustumOutOfDate() const { return mRecalcFrustum; } void CameraBase::updateView() const { if (!mCustomViewMatrix && mRecalcView) { mViewMatrix.makeView(mTransform.getPosition(), mTransform.getRotation()); mViewMatrixInv = mViewMatrix.inverseAffine(); mRecalcView = false; } } void CameraBase::updateFrustumPlanes() const { updateFrustum(); if (mRecalcFrustumPlanes) { mFrustum = ConvexVolume(mProjMatrix); mRecalcFrustumPlanes = false; } } float CameraBase::getAspectRatio() const { return mAspect; } void CameraBase::setAspectRatio(float r) { mAspect = r; invalidateFrustum(); _markCoreDirty(); } const AABox& CameraBase::getBoundingBox() const { updateFrustum(); return mBoundingBox; } void CameraBase::setProjectionType(ProjectionType pt) { mProjType = pt; invalidateFrustum(); _markCoreDirty(); } ProjectionType CameraBase::getProjectionType() const { return mProjType; } void CameraBase::setCustomViewMatrix(bool enable, const Matrix4& viewMatrix) { mCustomViewMatrix = enable; if (enable) { BS_ASSERT(viewMatrix.isAffine()); mViewMatrix = viewMatrix; mViewMatrixInv = mViewMatrix.inverseAffine(); } _markCoreDirty(); } void CameraBase::setCustomProjectionMatrix(bool enable, const Matrix4& projMatrix) { mCustomProjMatrix = enable; if (enable) mProjMatrix = projMatrix; invalidateFrustum(); _markCoreDirty(); } void CameraBase::setOrthoWindow(float w, float h) { mOrthoHeight = h; mAspect = w / h; invalidateFrustum(); _markCoreDirty(); } void CameraBase::setOrthoWindowHeight(float h) { mOrthoHeight = h; invalidateFrustum(); _markCoreDirty(); } void CameraBase::setOrthoWindowWidth(float w) { mOrthoHeight = w / mAspect; invalidateFrustum(); _markCoreDirty(); } float CameraBase::getOrthoWindowHeight() const { return mOrthoHeight; } float CameraBase::getOrthoWindowWidth() const { return mOrthoHeight * mAspect; } void CameraBase::setFrustumExtents(float left, float right, float top, float bottom) { mFrustumExtentsManuallySet = true; mLeft = left; mRight = right; mTop = top; mBottom = bottom; invalidateFrustum(); _markCoreDirty(); } void CameraBase::resetFrustumExtents() { mFrustumExtentsManuallySet = false; invalidateFrustum(); _markCoreDirty(); } void CameraBase::getFrustumExtents(float& outleft, float& outright, float& outtop, float& outbottom) const { updateFrustum(); outleft = mLeft; outright = mRight; outtop = mTop; outbottom = mBottom; } void CameraBase::setTransform(const Transform& transform) { SceneActor::setTransform(transform); mRecalcView = true; } void CameraBase::invalidateFrustum() const { mRecalcFrustum = true; mRecalcFrustumPlanes = true; } Vector2I CameraBase::worldToScreenPoint(const Vector3& worldPoint) const { Vector2 ndcPoint = worldToNdcPoint(worldPoint); return ndcToScreenPoint(ndcPoint); } Vector2 CameraBase::worldToNdcPoint(const Vector3& worldPoint) const { Vector3 viewPoint = worldToViewPoint(worldPoint); return viewToNdcPoint(viewPoint); } Vector3 CameraBase::worldToViewPoint(const Vector3& worldPoint) const { return getViewMatrix().multiplyAffine(worldPoint); } Vector3 CameraBase::screenToWorldPoint(const Vector2I& screenPoint, float depth) const { Vector2 ndcPoint = screenToNdcPoint(screenPoint); return ndcToWorldPoint(ndcPoint, depth); } Vector3 CameraBase::screenToWorldPointDeviceDepth(const Vector2I& screenPoint, float deviceDepth) const { Vector2 ndcPoint = screenToNdcPoint(screenPoint); Vector4 worldPoint(ndcPoint.x, ndcPoint.y, deviceDepth, 1.0f); worldPoint = getProjectionMatrixRS().inverse().multiply(worldPoint); Vector3 worldPoint3D; if (Math::abs(worldPoint.w) > 1e-7f) { float invW = 1.0f / worldPoint.w; worldPoint3D.x = worldPoint.x * invW; worldPoint3D.y = worldPoint.y * invW; worldPoint3D.z = worldPoint.z * invW; } return viewToWorldPoint(worldPoint3D); } Vector3 CameraBase::screenToViewPoint(const Vector2I& screenPoint, float depth) const { Vector2 ndcPoint = screenToNdcPoint(screenPoint); return ndcToViewPoint(ndcPoint, depth); } Vector2 CameraBase::screenToNdcPoint(const Vector2I& screenPoint) const { Rect2I viewport = getViewportRect(); Vector2 ndcPoint; ndcPoint.x = (float)(((screenPoint.x - viewport.x) / (float)viewport.width) * 2.0f - 1.0f); const RenderAPIInfo& info = RenderAPI::getAPIInfo(); if(info.isFlagSet(RenderAPIFeatureFlag::NDCYAxisDown)) ndcPoint.y = (float)(((screenPoint.y - viewport.y) / (float)viewport.height) * 2.0f - 1.0f); else ndcPoint.y = (float)((1.0f - ((screenPoint.y - viewport.y) / (float)viewport.height)) * 2.0f - 1.0f); return ndcPoint; } Vector3 CameraBase::viewToWorldPoint(const Vector3& viewPoint) const { return getViewMatrix().inverseAffine().multiplyAffine(viewPoint); } Vector2I CameraBase::viewToScreenPoint(const Vector3& viewPoint) const { Vector2 ndcPoint = viewToNdcPoint(viewPoint); return ndcToScreenPoint(ndcPoint); } Vector2 CameraBase::viewToNdcPoint(const Vector3& viewPoint) const { Vector3 projPoint = projectPoint(viewPoint); return Vector2(projPoint.x, projPoint.y); } Vector3 CameraBase::ndcToWorldPoint(const Vector2& ndcPoint, float depth) const { Vector3 viewPoint = ndcToViewPoint(ndcPoint, depth); return viewToWorldPoint(viewPoint); } Vector3 CameraBase::ndcToViewPoint(const Vector2& ndcPoint, float depth) const { return unprojectPoint(Vector3(ndcPoint.x, ndcPoint.y, depth)); } Vector2I CameraBase::ndcToScreenPoint(const Vector2& ndcPoint) const { Rect2I viewport = getViewportRect(); Vector2I screenPoint; screenPoint.x = Math::roundToInt(viewport.x + ((ndcPoint.x + 1.0f) * 0.5f) * viewport.width); screenPoint.y = Math::roundToInt(viewport.y + (1.0f - (ndcPoint.y + 1.0f) * 0.5f) * viewport.height); return screenPoint; } Ray CameraBase::screenPointToRay(const Vector2I& screenPoint) const { Vector2 ndcPoint = screenToNdcPoint(screenPoint); Vector3 near = unprojectPoint(Vector3(ndcPoint.x, ndcPoint.y, mNearDist)); Vector3 far = unprojectPoint(Vector3(ndcPoint.x, ndcPoint.y, mNearDist + 1.0f)); Ray ray(near, Vector3::normalize(far - near)); ray.transformAffine(getViewMatrix().inverseAffine()); return ray; } Vector3 CameraBase::projectPoint(const Vector3& point) const { Vector4 projPoint4(point.x, point.y, point.z, 1.0f); projPoint4 = getProjectionMatrixRS().multiply(projPoint4); if (Math::abs(projPoint4.w) > 1e-7f) { float invW = 1.0f / projPoint4.w; projPoint4.x *= invW; projPoint4.y *= invW; projPoint4.z *= invW; } else { projPoint4.x = 0.0f; projPoint4.y = 0.0f; projPoint4.z = 0.0f; } return Vector3(projPoint4.x, projPoint4.y, projPoint4.z); } Vector3 CameraBase::unprojectPoint(const Vector3& point) const { // Point.z is expected to be in view space, so we need to do some extra work to get the proper coordinates // (as opposed to if point.z was in device coordinates, in which case we could just inverse project) // Get world position for a point near the far plane (0.95f) Vector4 farAwayPoint(point.x, point.y, 0.95f, 1.0f); farAwayPoint = getProjectionMatrixRS().inverse().multiply(farAwayPoint); // Can't proceed if w is too small if (Math::abs(farAwayPoint.w) > 1e-7f) { // Perspective divide, to get the values that make sense in 3D space float invW = 1.0f / farAwayPoint.w; Vector3 farAwayPoint3D; farAwayPoint3D.x = farAwayPoint.x * invW; farAwayPoint3D.y = farAwayPoint.y * invW; farAwayPoint3D.z = farAwayPoint.z * invW; // Find the distance to the far point along the camera's viewing axis float distAlongZ = farAwayPoint3D.dot(-Vector3::UNIT_Z); // Do nothing if point is behind the camera if (distAlongZ >= 0.0f) { if (mProjType == PT_PERSPECTIVE) { // Direction from origin to our point Vector3 dir = farAwayPoint3D; // Camera is at (0, 0, 0) so it's the same vector // Our view space depth (point.z) is distance along the camera's viewing axis. Since our direction // vector is not parallel to the viewing axis, instead of normalizing it with its own length, we // "normalize" with the length projected along the camera's viewing axis. dir /= distAlongZ; // And now we just find the final position along the direction return dir * point.z; } else // Ortographic { // Depth difference between our arbitrary point and actual depth float depthDiff = distAlongZ - point.z; // Depth difference along viewing direction Vector3 depthDiffVec = depthDiff * -Vector3::UNIT_Z; // Return point that is depthDiff closer than our arbitrary point return farAwayPoint3D - depthDiffVec; } } } return Vector3(0.0f, 0.0f, 0.0f); } Camera::Camera(SPtr target, float left, float top, float width, float height) :mMain(false) { if (target != nullptr) target->blockUntilCoreInitialized(); mViewport = Viewport::create(target, left, top, width, height); } SPtr Camera::getCore() const { return std::static_pointer_cast(mCoreSpecific); } SPtr Camera::create(SPtr target, float left, float top, float width, float height) { Camera* handler = new (bs_alloc()) Camera(target, left, top, width, height); SPtr handlerPtr = bs_core_ptr(handler); handlerPtr->_setThisPtr(handlerPtr); handlerPtr->initialize(); return handlerPtr; } SPtr Camera::createEmpty() { Camera* handler = new (bs_alloc()) Camera(); SPtr handlerPtr = bs_core_ptr(handler); handlerPtr->_setThisPtr(handlerPtr); return handlerPtr; } SPtr Camera::createCore() const { ct::Camera* handler = new (bs_alloc()) ct::Camera(mViewport->getCore()); SPtr handlerPtr = bs_shared_ptr(handler); handlerPtr->_setThisPtr(handlerPtr); return handlerPtr; } void Camera::initialize() { CoreObject::initialize(); gSceneManager()._registerCamera(std::static_pointer_cast(getThisPtr())); } void Camera::destroy() { gSceneManager()._unregisterCamera(std::static_pointer_cast(getThisPtr())); CoreObject::destroy(); } Rect2I Camera::getViewportRect() const { return mViewport->getPixelArea(); } CoreSyncData Camera::syncToCore(FrameAlloc* allocator) { UINT32 dirtyFlag = getCoreDirtyFlags(); UINT32 size = getActorSyncDataSize(); size += rttiGetElemSize(dirtyFlag); UINT32 ppSize = 0; if (dirtyFlag != (UINT32)ActorDirtyFlag::Transform) { size += rttiGetElemSize(mLayers); size += rttiGetElemSize(mProjType); size += rttiGetElemSize(mHorzFOV); size += rttiGetElemSize(mFarDist); size += rttiGetElemSize(mNearDist); size += rttiGetElemSize(mAspect); size += rttiGetElemSize(mOrthoHeight); size += rttiGetElemSize(mPriority); size += rttiGetElemSize(mCustomViewMatrix); size += rttiGetElemSize(mCustomProjMatrix); size += rttiGetElemSize(mFrustumExtentsManuallySet); size += rttiGetElemSize(mMSAA); size += sizeof(UINT32); if(mRenderSettings != nullptr) { mRenderSettings->_getSyncData(nullptr, ppSize); size += ppSize; } } UINT8* buffer = allocator->alloc(size); char* dataPtr = (char*)buffer; dataPtr = syncActorTo(dataPtr); dataPtr = rttiWriteElem(dirtyFlag, dataPtr); if (dirtyFlag != (UINT32)ActorDirtyFlag::Transform) { dataPtr = rttiWriteElem(mLayers, dataPtr); dataPtr = rttiWriteElem(mProjType, dataPtr); dataPtr = rttiWriteElem(mHorzFOV, dataPtr); dataPtr = rttiWriteElem(mFarDist, dataPtr); dataPtr = rttiWriteElem(mNearDist, dataPtr); dataPtr = rttiWriteElem(mAspect, dataPtr); dataPtr = rttiWriteElem(mOrthoHeight, dataPtr); dataPtr = rttiWriteElem(mPriority, dataPtr); dataPtr = rttiWriteElem(mCustomViewMatrix, dataPtr); dataPtr = rttiWriteElem(mCustomProjMatrix, dataPtr); dataPtr = rttiWriteElem(mFrustumExtentsManuallySet, dataPtr); dataPtr = rttiWriteElem(mMSAA, dataPtr); dataPtr = rttiWriteElem(ppSize, dataPtr); if(mRenderSettings != nullptr) mRenderSettings->_getSyncData((UINT8*)dataPtr, ppSize); dataPtr += ppSize; } return CoreSyncData(buffer, size); } void Camera::getCoreDependencies(Vector& dependencies) { dependencies.push_back(mViewport.get()); } void Camera::_markCoreDirty(ActorDirtyFlag flag) { markCoreDirty((UINT32)flag); } RTTITypeBase* Camera::getRTTIStatic() { return CameraRTTI::instance(); } RTTITypeBase* Camera::getRTTI() const { return Camera::getRTTIStatic(); } namespace ct { Camera::~Camera() { RendererManager::instance().getActive()->notifyCameraRemoved(this); } Camera::Camera(SPtr target, float left, float top, float width, float height) : mRendererId(0) { mViewport = Viewport::create(target, left, top, width, height); } Camera::Camera(const SPtr& viewport) : mRendererId(0) { mViewport = viewport; } void Camera::initialize() { RendererManager::instance().getActive()->notifyCameraAdded(this); CoreObject::initialize(); } Rect2I Camera::getViewportRect() const { return mViewport->getPixelArea(); } void Camera::syncToCore(const CoreSyncData& data) { char* dataPtr = (char*)data.getBuffer(); UINT32 dirtyFlag; dataPtr = syncActorFrom(dataPtr); dataPtr = rttiReadElem(dirtyFlag, dataPtr); mRecalcFrustum = true; mRecalcFrustumPlanes = true; mRecalcView = true; if (dirtyFlag != (UINT32)ActorDirtyFlag::Transform) { dataPtr = rttiReadElem(mLayers, dataPtr); dataPtr = rttiReadElem(mProjType, dataPtr); dataPtr = rttiReadElem(mHorzFOV, dataPtr); dataPtr = rttiReadElem(mFarDist, dataPtr); dataPtr = rttiReadElem(mNearDist, dataPtr); dataPtr = rttiReadElem(mAspect, dataPtr); dataPtr = rttiReadElem(mOrthoHeight, dataPtr); dataPtr = rttiReadElem(mPriority, dataPtr); dataPtr = rttiReadElem(mCustomViewMatrix, dataPtr); dataPtr = rttiReadElem(mCustomProjMatrix, dataPtr); dataPtr = rttiReadElem(mFrustumExtentsManuallySet, dataPtr); dataPtr = rttiReadElem(mMSAA, dataPtr); UINT32 ppSize = 0; dataPtr = rttiReadElem(ppSize, dataPtr); if(ppSize > 0) { if (mRenderSettings == nullptr) mRenderSettings = bs_shared_ptr_new(); mRenderSettings->_setSyncData((UINT8*)dataPtr, ppSize); dataPtr += ppSize; } } RendererManager::instance().getActive()->notifyCameraUpdated(this, (UINT32)dirtyFlag); } } }