Browse Source

Merge branch 'projection-matrix'

Lasse Öörni 9 years ago
parent
commit
48e9f8e112

+ 3 - 1
Source/Urho3D/AngelScript/GraphicsAPI.cpp

@@ -120,7 +120,9 @@ static void RegisterCamera(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Camera", "void set_fillMode(FillMode)", asMETHOD(Camera, SetFillMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "FillMode get_fillMode() const", asMETHOD(Camera, GetFillMode), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "const Frustum& get_frustum() const", asMETHOD(Camera, GetFrustum), asCALL_THISCALL);
-    engine->RegisterObjectMethod("Camera", "const Matrix4& get_projection() const", asMETHODPR(Camera, GetProjection, () const, const Matrix4&), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Camera", "void set_projection(const Matrix4&in)", asMETHOD(Camera, SetProjection), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Camera", "Matrix4 get_projection() const", asMETHOD(Camera, GetProjection), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Camera", "Matrix4 get_gpuProjection() const", asMETHOD(Camera, GetGPUProjection), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "const Matrix3x4& get_view() const", asMETHOD(Camera, GetView), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "Frustum get_viewSpaceFrustum() const", asMETHOD(Camera, GetViewSpaceFrustum), asCALL_THISCALL);
     engine->RegisterObjectMethod("Camera", "float get_halfViewSize() const", asMETHOD(Camera, GetHalfViewSize), asCALL_THISCALL);

+ 2 - 0
Source/Urho3D/AngelScript/MathAPI.cpp

@@ -1087,7 +1087,9 @@ static void RegisterVolumes(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Frustum", "void Define(float, float, float, float, float, const Matrix3x4&in)", asMETHODPR(Frustum, Define, (float, float, float, float, float, const Matrix3x4&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Frustum", "void Define(const Vector3&in, const Vector3&in, const Matrix3x4&in)", asMETHODPR(Frustum, Define, (const Vector3&, const Vector3&, const Matrix3x4&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Frustum", "void Define(const BoundingBox&in, const Matrix3x4&in)", asMETHODPR(Frustum, Define, (const BoundingBox&, const Matrix3x4&), void), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Frustum", "void Define(const Matrix4&in)", asMETHODPR(Frustum, Define, (const Matrix4&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Frustum", "void DefineOrtho(float, float, float, float, float, const Matrix3x4&in)", asMETHOD(Frustum, DefineOrtho), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Frustum", "void DefineSplit(const Matrix4&in, float, float)", asMETHOD(Frustum, DefineSplit), asCALL_THISCALL);
     engine->RegisterObjectMethod("Frustum", "void Transform(const Matrix3&in)", asMETHODPR(Frustum, Transform, (const Matrix3&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Frustum", "void Transform(const Matrix3x4&in)", asMETHODPR(Frustum, Transform, (const Matrix3x4&), void), asCALL_THISCALL);
     engine->RegisterObjectMethod("Frustum", "Intersection IsInside(const Vector3&in)", asMETHODPR(Frustum, IsInside, (const Vector3&) const, Intersection), asCALL_THISCALL);

+ 1 - 1
Source/Urho3D/Graphics/Batch.cpp

@@ -86,7 +86,7 @@ void CalculateShadowMatrix(Matrix4& dest, LightBatchQueue* queue, unsigned split
     const IntRect& viewport = queue->shadowSplits_[split].shadowViewport_;
 
     Matrix3x4 shadowView(shadowCamera->GetView());
-    Matrix4 shadowProj(shadowCamera->GetProjection());
+    Matrix4 shadowProj(shadowCamera->GetGPUProjection());
     Matrix4 texAdjust(Matrix4::IDENTITY);
 
     Texture2D* shadowMap = queue->shadowMap_;

+ 1 - 1
Source/Urho3D/Graphics/BillboardSet.cpp

@@ -736,7 +736,7 @@ void BillboardSet::CalculateFixedScreenSize(const FrameInfo& frame)
 
     if (!frame.camera_->IsOrthographic())
     {
-        Matrix4 viewProj(frame.camera_->GetProjection(false) * frame.camera_->GetView());
+        Matrix4 viewProj(frame.camera_->GetProjection() * frame.camera_->GetView());
         const Matrix3x4& worldTransform = node_->GetWorldTransform();
         Matrix3x4 billboardTransform = relative_ ? worldTransform : Matrix3x4::IDENTITY;
 

+ 131 - 138
Source/Urho3D/Graphics/Camera.cpp

@@ -241,72 +241,102 @@ void Camera::SetUseClipping(bool enable)
 void Camera::SetClipPlane(const Plane& plane)
 {
     clipPlane_ = plane;
-    projectionDirty_ = true;
     MarkNetworkUpdate();
 }
 
-
 void Camera::SetFlipVertical(bool enable)
 {
     flipVertical_ = enable;
-    projectionDirty_ = true;
+    MarkNetworkUpdate();
+}
+
+void Camera::SetProjection(const Matrix4& projection)
+{
+    projection_ = projection;
+    Matrix4 projInverse = projection_.Inverse();
+
+    // Calculate the actual near & far clip from the custom matrix
+    projNearClip_ = (projInverse * Vector3(0.0f, 0.0f, 0.0f)).z_;
+    projFarClip_ = (projInverse * Vector3(0.0f, 0.0f, 1.0f)).z_;
+    projectionDirty_ = false;
+    autoAspectRatio_ = false;
+    frustumDirty_ = true;
+    // Called due to autoAspectRatio changing state, the projection itself is not serialized
     MarkNetworkUpdate();
 }
 
 float Camera::GetNearClip() const
 {
-    // Orthographic camera has always near clip at 0 to avoid trouble with shader depth parameters,
-    // and unlike in perspective mode there should be no depth buffer precision issue
-    if (!orthographic_)
-        return nearClip_;
-    else
-        return 0.0f;
+    if (projectionDirty_)
+        UpdateProjection();
+
+    return projNearClip_;
+}
+
+float Camera::GetFarClip() const
+{
+    if (projectionDirty_)
+        UpdateProjection();
+
+    return projFarClip_;
+}
+
+const Frustum& Camera::GetFrustum() const
+{
+    if (frustumDirty_)
+    {
+        // Use projection_ instead of GetProjection() so that Y-flip has no effect. Update first if necessary
+        if (projectionDirty_)
+            UpdateProjection();
+
+        frustum_.Define(projection_ * GetView());
+        frustumDirty_ = false;
+    }
+
+    return frustum_;
 }
 
 Frustum Camera::GetSplitFrustum(float nearClip, float farClip) const
 {
-    Frustum ret;
+    if (projectionDirty_)
+        UpdateProjection();
 
-    Matrix3x4 worldTransform = GetEffectiveWorldTransform();
-    nearClip = Max(nearClip, GetNearClip());
-    farClip = Min(farClip, farClip_);
+    nearClip = Max(nearClip, projNearClip_);
+    farClip = Min(farClip, projFarClip_);
     if (farClip < nearClip)
         farClip = nearClip;
 
-    if (!orthographic_)
-        ret.Define(fov_, aspectRatio_, zoom_, nearClip, farClip, worldTransform);
-    else
-        ret.DefineOrtho(orthoSize_, aspectRatio_, zoom_, nearClip, farClip, worldTransform);
+    Frustum ret;
+    // DefineSplit() needs to project the near & far distances, so can not use a combined view-projection matrix.
+    // Transform to world space afterward instead
+    ret.DefineSplit(projection_, nearClip, farClip);
+    ret.Transform(GetEffectiveWorldTransform());
 
     return ret;
 }
 
 Frustum Camera::GetViewSpaceFrustum() const
 {
-    Frustum ret;
-
-    if (!orthographic_)
-        ret.Define(fov_, aspectRatio_, zoom_, GetNearClip(), farClip_);
-    else
-        ret.DefineOrtho(orthoSize_, aspectRatio_, zoom_, GetNearClip(), farClip_);
+    if (projectionDirty_)
+        UpdateProjection();
 
+    Frustum ret;
+    ret.Define(projection_);
     return ret;
 }
 
 Frustum Camera::GetViewSpaceSplitFrustum(float nearClip, float farClip) const
 {
-    Frustum ret;
+    if (projectionDirty_)
+        UpdateProjection();
 
-    nearClip = Max(nearClip, GetNearClip());
-    farClip = Min(farClip, farClip_);
+    nearClip = Max(nearClip, projNearClip_);
+    farClip = Min(farClip, projFarClip_);
     if (farClip < nearClip)
         farClip = nearClip;
-
-    if (!orthographic_)
-        ret.Define(fov_, aspectRatio_, zoom_, nearClip, farClip);
-    else
-        ret.DefineOrtho(orthoSize_, aspectRatio_, zoom_, nearClip, farClip);
-
+    
+    Frustum ret;
+    ret.DefineSplit(projection_, nearClip, farClip);
     return ret;
 }
 
@@ -322,7 +352,7 @@ Ray Camera::GetScreenRay(float x, float y) const
         return ret;
     }
 
-    Matrix4 viewProjInverse = (GetProjection(false) * GetView()).Inverse();
+    Matrix4 viewProjInverse = (GetProjection() * GetView()).Inverse();
 
     // The parameters range from 0.0 to 1.0. Expand to normalized device coordinates (-1.0 to 1.0) & flip Y axis
     x = 2.0f * x - 1.0f;
@@ -342,7 +372,7 @@ Vector2 Camera::WorldToScreenPoint(const Vector3& worldPos) const
 
     if (eyeSpacePos.z_ > 0.0f)
     {
-        Vector3 screenSpacePos = GetProjection(false) * eyeSpacePos;
+        Vector3 screenSpacePos = GetProjection() * eyeSpacePos;
         ret.x_ = screenSpacePos.x_;
         ret.y_ = screenSpacePos.y_;
     }
@@ -365,124 +395,42 @@ Vector3 Camera::ScreenToWorldPoint(const Vector3& screenPos) const
     return ray.origin_ + ray.direction_ * rayDistance;
 }
 
-const Frustum& Camera::GetFrustum() const
-{
-    if (frustumDirty_)
-    {
-        Matrix3x4 worldTransform = GetEffectiveWorldTransform();
-
-        if (!orthographic_)
-            frustum_.Define(fov_, aspectRatio_, zoom_, GetNearClip(), farClip_, worldTransform);
-        else
-            frustum_.DefineOrtho(orthoSize_, aspectRatio_, zoom_, GetNearClip(), farClip_, worldTransform);
-
-        frustumDirty_ = false;
-    }
-
-    return frustum_;
-}
-
-const Matrix4& Camera::GetProjection() const
+Matrix4 Camera::GetProjection() const
 {
     if (projectionDirty_)
-    {
-        projection_ = GetProjection(true);
-        projectionDirty_ = false;
-    }
+        UpdateProjection();
 
-    return projection_;
+    return flipVertical_ ? flipMatrix * projection_ : projection_;
 }
 
-Matrix4 Camera::GetProjection(bool apiSpecific) const
+Matrix4 Camera::GetGPUProjection() const
 {
-    Matrix4 ret(Matrix4::ZERO);
-
-    // Whether to construct matrix using OpenGL or Direct3D clip space convention
-#ifdef URHO3D_OPENGL
-    bool openGLFormat = apiSpecific;
+#ifndef URHO3D_OPENGL
+    return GetProjection(); // Already matches API-specific format
 #else
-    bool openGLFormat = false;
-#endif
-
-    if (!orthographic_)
-    {
-        float nearClip = GetNearClip();
-        float h = (1.0f / tanf(fov_ * M_DEGTORAD * 0.5f)) * zoom_;
-        float w = h / aspectRatio_;
-        float q, r;
-
-        if (openGLFormat)
-        {
-            q = (farClip_ + nearClip) / (farClip_ - nearClip);
-            r = -2.0f * farClip_ * nearClip / (farClip_ - nearClip);
-        }
-        else
-        {
-            q = farClip_ / (farClip_ - nearClip);
-            r = -q * nearClip;
-        }
-
-        ret.m00_ = w;
-        ret.m02_ = projectionOffset_.x_ * 2.0f;
-        ret.m11_ = h;
-        ret.m12_ = projectionOffset_.y_ * 2.0f;
-        ret.m22_ = q;
-        ret.m23_ = r;
-        ret.m32_ = 1.0f;
-    }
-    else
-    {
-        // Disregard near clip, because it does not affect depth precision as with perspective projection
-        float h = (1.0f / (orthoSize_ * 0.5f)) * zoom_;
-        float w = h / aspectRatio_;
-        float q, r;
-
-        if (openGLFormat)
-        {
-            q = 2.0f / farClip_;
-            r = -1.0f;
-        }
-        else
-        {
-            q = 1.0f / farClip_;
-            r = 0.0f;
-        }
-
-        ret.m00_ = w;
-        ret.m03_ = projectionOffset_.x_ * 2.0f;
-        ret.m11_ = h;
-        ret.m13_ = projectionOffset_.y_ * 2.0f;
-        ret.m22_ = q;
-        ret.m23_ = r;
-        ret.m33_ = 1.0f;
-    }
-
-    if (flipVertical_)
-        ret = flipMatrix * ret;
+    // See formulation for depth range conversion at http://www.ogre3d.org/forums/viewtopic.php?f=4&t=13357
+    Matrix4 ret = GetProjection();
+    
+    ret.m20_ = 2.0f * ret.m20_ - ret.m30_;
+    ret.m21_ = 2.0f * ret.m21_ - ret.m31_;
+    ret.m22_ = 2.0f * ret.m22_ - ret.m32_;
+    ret.m23_ = 2.0f * ret.m23_ - ret.m33_;
 
     return ret;
+#endif
 }
 
 void Camera::GetFrustumSize(Vector3& near, Vector3& far) const
 {
-    near.z_ = GetNearClip();
-    far.z_ = farClip_;
+    // Use projection_ instead of GetProjection() so that Y-flip has no effect. Update first if necessary
+    if (projectionDirty_)
+        UpdateProjection();
+    Matrix4 projInverse = projection_.Inverse();
 
-    if (!orthographic_)
-    {
-        float halfViewSize = tanf(fov_ * M_DEGTORAD * 0.5f) / zoom_;
-        near.y_ = near.z_ * halfViewSize;
-        near.x_ = near.y_ * aspectRatio_;
-        far.y_ = far.z_ * halfViewSize;
-        far.x_ = far.y_ * aspectRatio_;
-    }
-    else
-    {
-        float halfViewSize = orthoSize_ * 0.5f / zoom_;
-        near.y_ = far.y_ = halfViewSize;
-        near.x_ = far.x_ = near.y_ * aspectRatio_;
-    }
+    near = projInverse * Vector3(1.0f, 1.0f, 0.0f);
+    far = projInverse * Vector3(1.0f, 1.0f, 1.0f);
 
+    /// \todo Necessary? Explain this
     if (flipVertical_)
     {
         near.y_ = -near.y_;
@@ -584,7 +532,7 @@ Matrix3x4 Camera::GetEffectiveWorldTransform() const
 
 bool Camera::IsProjectionValid() const
 {
-    return farClip_ > GetNearClip();
+    return GetFarClip() > GetNearClip();
 }
 
 const Matrix3x4& Camera::GetView() const
@@ -650,4 +598,49 @@ void Camera::OnMarkedDirty(Node* node)
     viewDirty_ = true;
 }
 
+void Camera::UpdateProjection() const
+{
+    // Start from a zero matrix in case it was custom previously
+    projection_ = Matrix4::ZERO;
+
+    if (!orthographic_)
+    {
+        float h = (1.0f / tanf(fov_ * M_DEGTORAD * 0.5f)) * zoom_;
+        float w = h / aspectRatio_;
+        float q = farClip_ / (farClip_ - nearClip_);
+        float r = -q * nearClip_;
+
+        projection_.m00_ = w;
+        projection_.m02_ = projectionOffset_.x_ * 2.0f;
+        projection_.m11_ = h;
+        projection_.m12_ = projectionOffset_.y_ * 2.0f;
+        projection_.m22_ = q;
+        projection_.m23_ = r;
+        projection_.m32_ = 1.0f;
+        projNearClip_ = nearClip_;
+        projFarClip_ = farClip_;
+    }
+    else
+    {
+        // Disregard near clip, because it does not affect depth precision as with perspective projection
+        float h = (1.0f / (orthoSize_ * 0.5f)) * zoom_;
+        float w = h / aspectRatio_;
+        float q = 1.0f / farClip_;
+        float r = 0.0f;
+
+        projection_.m00_ = w;
+        projection_.m03_ = projectionOffset_.x_ * 2.0f;
+        projection_.m11_ = h;
+        projection_.m13_ = projectionOffset_.y_ * 2.0f;
+        projection_.m22_ = q;
+        projection_.m23_ = r;
+        projection_.m33_ = 1.0f;
+        // Near clip does not affect depth accuracy in ortho projection, so let it stay 0 to avoid problems with shader depth parameters
+        projNearClip_ = 0.0f;
+        projFarClip_ = farClip_;
+    }
+
+    projectionDirty_ = false;
+}
+
 }

+ 23 - 11
Source/Urho3D/Graphics/Camera.h

@@ -94,11 +94,16 @@ public:
     void SetClipPlane(const Plane& plane);
     /// Set vertical flipping mode. Called internally by View to resolve OpenGL / Direct3D9 rendertarget sampling differences.
     void SetFlipVertical(bool enable);
+    /// Set custom projection matrix, which should be specified in D3D convention with depth range 0 - 1. Disables auto aspect ratio.
+    /** Change any of the standard view parameters (FOV, far clip, zoom etc.) to revert to the standard projection. 
+        Note that the custom projection is not serialized or replicated through the network.
+     */
+    void SetProjection(const Matrix4& projection);
 
-    /// Return far clip distance.
-    float GetFarClip() const { return farClip_; }
+    /// Return far clip distance. If a custom projection matrix is in use, is calculated from it instead of the value assigned with SetFarClip().
+    float GetFarClip() const;
 
-    /// Return near clip distance.
+    /// Return near clip distance. If a custom projection matrix is in use, is calculated from it instead of the value assigned with SetNearClip().
     float GetNearClip() const;
 
     /// Return vertical field of view in degrees.
@@ -133,10 +138,10 @@ public:
 
     /// Return frustum in world space.
     const Frustum& GetFrustum() const;
-    /// Return API-specific projection matrix.
-    const Matrix4& GetProjection() const;
-    /// Return either API-specific or API-independent (D3D convention) projection matrix.
-    Matrix4 GetProjection(bool apiSpecific) const;
+    /// Return projection matrix. It's in D3D convention with depth range 0 - 1.
+    Matrix4 GetProjection() const;
+    /// Return projection matrix converted to API-specific format for use as a shader parameter.
+    Matrix4 GetGPUProjection() const;
     /// Return view matrix.
     const Matrix3x4& GetView() const;
     /// Return frustum near and far sizes.
@@ -149,11 +154,11 @@ public:
     Frustum GetViewSpaceFrustum() const;
     /// Return split frustum in view space.
     Frustum GetViewSpaceSplitFrustum(float nearClip, float farClip) const;
-    /// Return ray corresponding to normalized screen coordinates (0.0 - 1.0), with origin on the near clip plane.
+    /// Return ray corresponding to normalized screen coordinates (0 - 1), with origin on the near clip plane.
     Ray GetScreenRay(float x, float y) const;
-    /// Convert a world space point to normalized screen coordinates (0.0 - 1.0).
+    /// Convert a world space point to normalized screen coordinates (0 - 1).
     Vector2 WorldToScreenPoint(const Vector3& worldPos) const;
-    /// Convert normalized screen coordinates (0.0 - 1.0) and distance along view Z axis (in Z coordinate) to a world space point. The distance can not be closer than the near clip plane.
+    /// Convert normalized screen coordinates (0 - 1) and distance along view Z axis (in Z coordinate) to a world space point. The distance can not be closer than the near clip plane.
     /** Note that a HitDistance() from the camera screen ray is not the same as distance along the view Z axis, as under a perspective projection the ray is likely to not be Z-aligned.
      */
     Vector3 ScreenToWorldPoint(const Vector3& screenPos) const;
@@ -212,11 +217,14 @@ protected:
     virtual void OnMarkedDirty(Node* node);
 
 private:
+    /// Recalculate projection matrix.
+    void UpdateProjection() const;
+
     /// Cached view matrix.
     mutable Matrix3x4 view_;
     /// Cached projection matrix.
     mutable Matrix4 projection_;
-    /// Cached frustum.
+    /// Cached world space frustum.
     mutable Frustum frustum_;
     /// View matrix dirty flag.
     mutable bool viewDirty_;
@@ -226,6 +234,10 @@ private:
     mutable bool frustumDirty_;
     /// Orthographic mode flag.
     bool orthographic_;
+    /// Cached actual near clip distance.
+    mutable float projNearClip_;
+    /// Cached actual far clip distance.
+    mutable float projFarClip_;
     /// Near clip distance.
     float nearClip_;
     /// Far clip distance.

+ 2 - 1
Source/Urho3D/Graphics/DebugRenderer.cpp

@@ -71,6 +71,7 @@ void DebugRenderer::SetView(Camera* camera)
 
     view_ = camera->GetView();
     projection_ = camera->GetProjection();
+    gpuProjection_ = camera->GetGPUProjection();
     frustum_ = camera->GetFrustum();
 }
 
@@ -500,7 +501,7 @@ void DebugRenderer::Render()
     graphics->SetShaderParameter(VSP_MODEL, Matrix3x4::IDENTITY);
     graphics->SetShaderParameter(VSP_VIEW, view_);
     graphics->SetShaderParameter(VSP_VIEWINV, view_.Inverse());
-    graphics->SetShaderParameter(VSP_VIEWPROJ, projection_ * view_);
+    graphics->SetShaderParameter(VSP_VIEWPROJ, gpuProjection_ * view_);
     graphics->SetShaderParameter(PSP_MATDIFFCOLOR, Color(1.0f, 1.0f, 1.0f, 1.0f));
     graphics->SetVertexBuffer(vertexBuffer_);
 

+ 2 - 0
Source/Urho3D/Graphics/DebugRenderer.h

@@ -174,6 +174,8 @@ private:
     Matrix3x4 view_;
     /// Projection transform.
     Matrix4 projection_;
+    /// Projection transform in API-specific format.
+    Matrix4 gpuProjection_;
     /// View frustum.
     Frustum frustum_;
     /// Vertex buffer.

+ 1 - 1
Source/Urho3D/Graphics/OcclusionBuffer.cpp

@@ -125,7 +125,7 @@ void OcclusionBuffer::SetView(Camera* camera)
         return;
 
     view_ = camera->GetView();
-    projection_ = camera->GetProjection(false);
+    projection_ = camera->GetProjection();
     viewProj_ = projection_ * view_;
     nearClip_ = camera->GetNearClip();
     farClip_ = camera->GetFarClip();

+ 1 - 1
Source/Urho3D/Graphics/Renderer.cpp

@@ -1400,7 +1400,7 @@ void Renderer::OptimizeLightByStencil(Light* light, Camera* camera)
 
         Geometry* geometry = GetLightGeometry(light);
         const Matrix3x4& view = camera->GetView();
-        const Matrix4& projection = camera->GetProjection();
+        const Matrix4& projection = camera->GetGPUProjection();
         Vector3 cameraPos = camera->GetNode()->GetWorldPosition();
         float lightDist;
 

+ 3 - 3
Source/Urho3D/Graphics/View.cpp

@@ -746,7 +746,7 @@ void View::SetCameraShaderParameters(Camera* camera)
     camera->GetFrustumSize(nearVector, farVector);
     graphics_->SetShaderParameter(VSP_FRUSTUMSIZE, farVector);
 
-    Matrix4 projection = camera->GetProjection();
+    Matrix4 projection = camera->GetGPUProjection();
 #ifdef URHO3D_OPENGL
     // Add constant depth bias manually to the projection matrix due to glPolygonOffset() inconsistency
     float constantBias = 2.0f * graphics_->GetDepthConstantBias();
@@ -1565,7 +1565,7 @@ void View::ExecuteRenderPathCommands()
                         SetRenderTargets(command);
                         bool allowDepthWrite = SetTextures(command);
                         graphics_->SetClipPlane(camera_->GetUseClipping(), camera_->GetClipPlane(), camera_->GetView(),
-                            camera_->GetProjection());
+                            camera_->GetGPUProjection());
                         queue.Draw(this, camera_, command.markToStencil_, false, allowDepthWrite);
                     }
                 }
@@ -1600,7 +1600,7 @@ void View::ExecuteRenderPathCommands()
 
                         bool allowDepthWrite = SetTextures(command);
                         graphics_->SetClipPlane(camera_->GetUseClipping(), camera_->GetClipPlane(), camera_->GetView(),
-                            camera_->GetProjection());
+                            camera_->GetGPUProjection());
 
                         // Draw base (replace blend) batches first
                         i->litBaseBatches_.Draw(this, camera_, false, false, allowDepthWrite);

+ 5 - 2
Source/Urho3D/LuaScript/pkgs/Graphics/Camera.pkg

@@ -27,6 +27,7 @@ class Camera : public Component
     void SetReflectionPlane(const Plane& reflectionPlane);
     void SetUseClipping(bool enable);
     void SetClipPlane(const Plane& clipPlane);
+    void SetProjection(const Matrix4& projection);
 
     float GetFarClip() const;
     float GetNearClip() const;
@@ -41,7 +42,8 @@ class Camera : public Component
     bool IsOrthographic() const;
     bool GetAutoAspectRatio() const;
     const Frustum& GetFrustum() const;
-    const Matrix4& GetProjection() const;
+    Matrix4 GetProjection() const;
+    Matrix4 GetGPUProjection() const;
     const Matrix3x4& GetView() const;
     void GetFrustumSize(Vector3& near, Vector3& far) const;
     float GetHalfViewSize() const;
@@ -76,7 +78,8 @@ class Camera : public Component
     tolua_property__is_set bool orthographic;
     tolua_property__get_set bool autoAspectRatio;
     tolua_readonly tolua_property__get_set Frustum& frustum;
-    tolua_readonly tolua_property__get_set Matrix4& projection;
+    tolua_readonly tolua_property__get_set Matrix4 projection;
+    tolua_readonly tolua_property__get_set Matrix4 GPUProjection;
     tolua_readonly tolua_property__get_set Matrix3x4& view;
     tolua_readonly tolua_property__get_set float halfViewSize;
     tolua_readonly tolua_property__get_set Frustum viewSpaceFrustum;

+ 2 - 0
Source/Urho3D/LuaScript/pkgs/Math/Frustum.pkg

@@ -22,7 +22,9 @@ class Frustum
     void Define(float fov, float aspectRatio, float zoom, float nearZ, float farZ, const Matrix3x4& transform = Matrix3x4::IDENTITY);
     void Define(const Vector3& near, const Vector3& far, const Matrix3x4& transform = Matrix3x4::IDENTITY);
     void Define(const BoundingBox& box, const Matrix3x4& transform = Matrix3x4::IDENTITY);
+    void Define(const Matrix4& projection);
     void DefineOrtho(float orthoSize, float aspectRatio, float zoom, float nearZ, float farZ, const Matrix3x4& transform = Matrix3x4::IDENTITY);
+    void DefineSplit(const Matrix4& projection, float near, float far);
     void Transform(const Matrix3& transform);
     void Transform(const Matrix3x4& transform);
     

+ 38 - 0
Source/Urho3D/Math/Frustum.cpp

@@ -121,6 +121,22 @@ void Frustum::Define(const BoundingBox& box, const Matrix3x4& transform)
     UpdatePlanes();
 }
 
+void Frustum::Define(const Matrix4& projection)
+{
+    Matrix4 projInverse = projection.Inverse();
+
+    vertices_[0] = projInverse * Vector3(1.0f, 1.0f, 0.0f);
+    vertices_[1] = projInverse * Vector3(1.0f, -1.0f, 0.0f);
+    vertices_[2] = projInverse * Vector3(-1.0f, -1.0f, 0.0f);
+    vertices_[3] = projInverse * Vector3(-1.0f, 1.0f, 0.0f);
+    vertices_[4] = projInverse * Vector3(1.0f, 1.0f, 1.0f);
+    vertices_[5] = projInverse * Vector3(1.0f, -1.0f, 1.0f);
+    vertices_[6] = projInverse * Vector3(-1.0f, -1.0f, 1.0f);
+    vertices_[7] = projInverse * Vector3(-1.0f, 1.0f, 1.0f);
+
+    UpdatePlanes();
+}
+
 void Frustum::DefineOrtho(float orthoSize, float aspectRatio, float zoom, float nearZ, float farZ, const Matrix3x4& transform)
 {
     nearZ = Max(nearZ, 0.0f);
@@ -136,6 +152,28 @@ void Frustum::DefineOrtho(float orthoSize, float aspectRatio, float zoom, float
     Define(near, far, transform);
 }
 
+void Frustum::DefineSplit(const Matrix4& projection, float near, float far)
+{
+    Matrix4 projInverse = projection.Inverse();
+
+    // Figure out depth values for near & far
+    Vector4 nearTemp = projection * Vector4(0.0f, 0.0f, near, 1.0f);
+    Vector4 farTemp = projection * Vector4(0.0f, 0.0f, far, 1.0f);
+    float nearZ = nearTemp.z_ / nearTemp.w_;
+    float farZ = farTemp.z_ / farTemp.w_;
+
+    vertices_[0] = projInverse * Vector3(1.0f, 1.0f, nearZ);
+    vertices_[1] = projInverse * Vector3(1.0f, -1.0f, nearZ);
+    vertices_[2] = projInverse * Vector3(-1.0f, -1.0f, nearZ);
+    vertices_[3] = projInverse * Vector3(-1.0f, 1.0f, nearZ);
+    vertices_[4] = projInverse * Vector3(1.0f, 1.0f, farZ);
+    vertices_[5] = projInverse * Vector3(1.0f, -1.0f, farZ);
+    vertices_[6] = projInverse * Vector3(-1.0f, -1.0f, farZ);
+    vertices_[7] = projInverse * Vector3(-1.0f, 1.0f, farZ);
+
+    UpdatePlanes();
+}
+
 void Frustum::Transform(const Matrix3& transform)
 {
     for (unsigned i = 0; i < NUM_FRUSTUM_VERTICES; ++i)

+ 4 - 0
Source/Urho3D/Math/Frustum.h

@@ -64,9 +64,13 @@ public:
     void Define(const Vector3& near, const Vector3& far, const Matrix3x4& transform = Matrix3x4::IDENTITY);
     /// Define with a bounding box and a transform matrix.
     void Define(const BoundingBox& box, const Matrix3x4& transform = Matrix3x4::IDENTITY);
+    /// Define from a projection or view-projection matrix.
+    void Define(const Matrix4& projection);
     /// Define with orthographic projection parameters and a transform matrix.
     void DefineOrtho
         (float orthoSize, float aspectRatio, float zoom, float nearZ, float farZ, const Matrix3x4& transform = Matrix3x4::IDENTITY);
+    /// Define a split (limited) frustum from a projection matrix, with near & far distances specified.
+    void DefineSplit(const Matrix4& projection, float near, float far);
     /// Transform by a 3x3 matrix.
     void Transform(const Matrix3& transform);
     /// Transform by a 3x4 matrix.

+ 1 - 1
Source/Urho3D/UI/Text3D.cpp

@@ -713,7 +713,7 @@ void Text3D::CalculateFixedScreenSize(const FrameInfo& frame)
 
         if (!frame.camera_->IsOrthographic())
         {
-            Matrix4 viewProj(frame.camera_->GetProjection(false) * frame.camera_->GetView());
+            Matrix4 viewProj(frame.camera_->GetProjection() * frame.camera_->GetView());
             Vector4 projPos(viewProj * Vector4(worldPosition, 1.0f));
             worldScale *= textScaling * halfViewWorldSize * projPos.w_;
         }