diff --git a/source/graphics/Camera.cpp b/source/graphics/Camera.cpp index 036849a49e..52bc4f1a4d 100644 --- a/source/graphics/Camera.cpp +++ b/source/graphics/Camera.cpp @@ -89,9 +89,9 @@ void CCamera::UpdateFrustum(const CBoundingBoxAligned& scissor) MatFinal = m_ProjMat * MatView; - // get the RIGHT plane m_ViewFrustum.SetNumPlanes(6); + // get the RIGHT plane m_ViewFrustum.m_aPlanes[0].m_Norm.X = scissor[1].X*MatFinal._41 - MatFinal._11; m_ViewFrustum.m_aPlanes[0].m_Norm.Y = scissor[1].X*MatFinal._42 - MatFinal._12; m_ViewFrustum.m_aPlanes[0].m_Norm.Z = scissor[1].X*MatFinal._43 - MatFinal._13; diff --git a/source/graphics/Frustum.cpp b/source/graphics/Frustum.cpp index a73d1ddbed..070f84c6d9 100644 --- a/source/graphics/Frustum.cpp +++ b/source/graphics/Frustum.cpp @@ -30,6 +30,7 @@ portal rendering, where a portal may have 3 or more edges. #include "Frustum.h" #include "maths/BoundingBoxAligned.h" #include "maths/MathUtil.h" +#include "maths/Matrix3D.h" CFrustum::CFrustum () { @@ -46,7 +47,10 @@ void CFrustum::SetNumPlanes (size_t num) //clip it if (m_NumPlanes >= MAX_NUM_FRUSTUM_PLANES) + { + debug_warn(L"CFrustum::SetNumPlanes: Too many planes"); m_NumPlanes = MAX_NUM_FRUSTUM_PLANES-1; + } } void CFrustum::AddPlane (const CPlane& plane) @@ -60,6 +64,17 @@ void CFrustum::AddPlane (const CPlane& plane) m_aPlanes[m_NumPlanes++] = plane; } +void CFrustum::Transform(CMatrix3D& m) +{ + for (size_t i = 0; i < m_NumPlanes; i++) + { + CVector3D n = m.Rotate(m_aPlanes[i].m_Norm); + CVector3D p = m.Transform(m_aPlanes[i].m_Norm * -m_aPlanes[i].m_Dist); + m_aPlanes[i].Set(n, p); + m_aPlanes[i].Normalize(); + } +} + bool CFrustum::IsPointVisible (const CVector3D &point) const { PLANESIDE Side; @@ -74,6 +89,7 @@ bool CFrustum::IsPointVisible (const CVector3D &point) const return true; } + bool CFrustum::DoesSegmentIntersect(const CVector3D& startRef, const CVector3D &endRef) { CVector3D start = startRef; @@ -179,5 +195,3 @@ bool CFrustum::IsBoxVisible (const CVector3D &position,const CBoundingBoxAligned return true; } - - diff --git a/source/graphics/Frustum.h b/source/graphics/Frustum.h index a0cf1932b8..ef8d5343a1 100644 --- a/source/graphics/Frustum.h +++ b/source/graphics/Frustum.h @@ -34,6 +34,7 @@ portal rendering, where a portal may have 3 or more edges. #define MAX_NUM_FRUSTUM_PLANES (10) class CBoundingBoxAligned; +class CMatrix3D; class CFrustum { @@ -50,6 +51,8 @@ public: void AddPlane (const CPlane& plane); + void Transform(CMatrix3D& m); + //The following methods return true if the shape is //partially or completely in front of the frustum planes bool IsPointVisible (const CVector3D &point) const; diff --git a/source/graphics/GameView.cpp b/source/graphics/GameView.cpp index 71378f338c..1cc07a4b6d 100644 --- a/source/graphics/GameView.cpp +++ b/source/graphics/GameView.cpp @@ -477,15 +477,6 @@ void CGameView::BeginFrame() { // Set up cull camera m->CullCamera = m->ViewCamera; - - // One way to fix shadows popping in at the edge of the screen is to widen the culling frustum so that - // objects aren't culled as early. The downside is that objects will get rendered even though they appear - // off screen, which is somewhat inefficient. A better solution would be to decouple shadow map rendering - // from model rendering; as it is now, a shadow map is only rendered if its associated model is to be - // rendered. - // (See http://trac.wildfiregames.com/ticket/504) - m->CullCamera.SetProjection(m->ViewNear, m->ViewFar, GetCullFOV()); - m->CullCamera.UpdateFrustum(); } g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera); @@ -523,51 +514,7 @@ void CGameView::EnumerateObjects(const CFrustum& frustum, SceneCollector* c) } if (!m->Culling || frustum.IsBoxVisible (CVector3D(0,0,0), bounds)) { - //c->Submit(patch); - - // set the renderstate for this patch - patch->setDrawState(true); - - // set the renderstate for the neighbors - CPatch *nPatch; - - nPatch = pTerrain->GetPatch(i-1,j-1); - if(nPatch) nPatch->setDrawState(true); - - nPatch = pTerrain->GetPatch(i,j-1); - if(nPatch) nPatch->setDrawState(true); - - nPatch = pTerrain->GetPatch(i+1,j-1); - if(nPatch) nPatch->setDrawState(true); - - nPatch = pTerrain->GetPatch(i-1,j); - if(nPatch) nPatch->setDrawState(true); - - nPatch = pTerrain->GetPatch(i+1,j); - if(nPatch) nPatch->setDrawState(true); - - nPatch = pTerrain->GetPatch(i-1,j+1); - if(nPatch) nPatch->setDrawState(true); - - nPatch = pTerrain->GetPatch(i,j+1); - if(nPatch) nPatch->setDrawState(true); - - nPatch = pTerrain->GetPatch(i+1,j+1); - if(nPatch) nPatch->setDrawState(true); - } - } - } - - // draw the patches - for (ssize_t j=0; jGetPatch(i,j); // can't fail - if(patch->getDrawState() == true) - { c->Submit(patch); - patch->setDrawState(false); } } } @@ -1102,11 +1049,6 @@ float CGameView::GetFOV() const return m->ViewFOV; } -float CGameView::GetCullFOV() const -{ - return m->ViewFOV + DEGTORAD(6.0f); //add 6 degrees to the default FOV for use with the culling frustum; -} - void CGameView::SetCameraProjection() { m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV); diff --git a/source/graphics/GameView.h b/source/graphics/GameView.h index a95072b595..3073b8697a 100644 --- a/source/graphics/GameView.h +++ b/source/graphics/GameView.h @@ -102,7 +102,6 @@ public: float GetNear() const; float GetFar() const; float GetFOV() const; - float GetCullFOV() const; #define DECLARE_BOOLEAN_SETTING(NAME) \ bool Get##NAME##Enabled(); \ diff --git a/source/graphics/Model.h b/source/graphics/Model.h index b0cb0a96fb..889f99e6a5 100644 --- a/source/graphics/Model.h +++ b/source/graphics/Model.h @@ -42,7 +42,6 @@ class CSimulation2; #define MODELFLAG_SILHOUETTE_DISPLAY (1<<2) #define MODELFLAG_SILHOUETTE_OCCLUDER (1<<3) #define MODELFLAG_IGNORE_LOS (1<<4) -#define MODELFLAG_FILTERED (1<<5) // used internally by renderer /////////////////////////////////////////////////////////////////////////////// // CModel: basically, a mesh object - holds the texturing and skinning diff --git a/source/graphics/ParticleEmitter.cpp b/source/graphics/ParticleEmitter.cpp index df44a2c20f..b07f1f9d4c 100644 --- a/source/graphics/ParticleEmitter.cpp +++ b/source/graphics/ParticleEmitter.cpp @@ -31,7 +31,8 @@ CParticleEmitter::CParticleEmitter(const CParticleEmitterTypePtr& type) : m_Type(type), m_Active(true), m_NextParticleIdx(0), m_EmissionRoundingError(0.f), m_LastUpdateTime(type->m_Manager.GetCurrentTime()), m_IndexArray(GL_DYNAMIC_DRAW), - m_VertexArray(GL_DYNAMIC_DRAW) + m_VertexArray(GL_DYNAMIC_DRAW), + m_LastFrameNumber(-1) { // If we should start with particles fully emitted, pretend that we // were created in the past so the first update will produce lots of @@ -80,8 +81,13 @@ CParticleEmitter::CParticleEmitter(const CParticleEmitterTypePtr& type) : m_IndexArray.FreeBackingStore(); } -void CParticleEmitter::UpdateArrayData() +void CParticleEmitter::UpdateArrayData(int frameNumber) { + if (m_LastFrameNumber == frameNumber) + return; + + m_LastFrameNumber = frameNumber; + // Update m_Particles m_Type->UpdateEmitter(*this, m_Type->m_Manager.GetCurrentTime() - m_LastUpdateTime); m_LastUpdateTime = m_Type->m_Manager.GetCurrentTime(); diff --git a/source/graphics/ParticleEmitter.h b/source/graphics/ParticleEmitter.h index 3afd70edc5..1bf9b81546 100644 --- a/source/graphics/ParticleEmitter.h +++ b/source/graphics/ParticleEmitter.h @@ -107,8 +107,11 @@ public: /** * Update particle and vertex array data. Must be called before RenderArray. + * + * If frameNumber is the same as the previous call to UpdateArrayData, + * then the function will do no work and return immediately. */ - void UpdateArrayData(); + void UpdateArrayData(int frameNumber); /** * Bind rendering state (textures and blend modes). @@ -158,6 +161,8 @@ private: VertexArray::Attribute m_AttributeAxis; VertexArray::Attribute m_AttributeUV; VertexArray::Attribute m_AttributeColor; + + int m_LastFrameNumber; }; /** diff --git a/source/graphics/Patch.cpp b/source/graphics/Patch.cpp index e8a1e9c020..af006a8c7c 100644 --- a/source/graphics/Patch.cpp +++ b/source/graphics/Patch.cpp @@ -28,7 +28,7 @@ /////////////////////////////////////////////////////////////////////////////// // CPatch constructor CPatch::CPatch() -: m_Parent(0), m_bWillBeDrawn(false) +: m_Parent(0) { } diff --git a/source/graphics/Patch.h b/source/graphics/Patch.h index 7c5cce07ac..d4cdd98ef2 100644 --- a/source/graphics/Patch.h +++ b/source/graphics/Patch.h @@ -58,9 +58,6 @@ public: // calculate and store bounds of this patch void CalcBounds(); - // is already in the DrawList - bool m_bWillBeDrawn; - public: // minipatches (tiles) making up the patch CMiniPatch m_MiniPatches[PATCH_SIZE][PATCH_SIZE]; @@ -69,10 +66,6 @@ public: // parent terrain CTerrain* m_Parent; - // draw state... - void setDrawState(bool value) { m_bWillBeDrawn = value; }; - bool getDrawState() { return m_bWillBeDrawn; }; - int GetSideFlags(); }; diff --git a/source/maths/BoundingBoxAligned.cpp b/source/maths/BoundingBoxAligned.cpp index b606829470..f718a104c1 100644 --- a/source/maths/BoundingBoxAligned.cpp +++ b/source/maths/BoundingBoxAligned.cpp @@ -211,6 +211,39 @@ void CBoundingBoxAligned::IntersectFrustumConservative(const CFrustum& frustum) buf.Bounds(*this); } +/////////////////////////////////////////////////////////////////////////////// +CFrustum CBoundingBoxAligned::ToFrustum() const +{ + CFrustum frustum; + + frustum.SetNumPlanes(6); + + // get the LEFT plane + frustum.m_aPlanes[0].m_Norm = CVector3D(1, 0, 0); + frustum.m_aPlanes[0].m_Dist = -m_Data[0].X; + + // get the RIGHT plane + frustum.m_aPlanes[1].m_Norm = CVector3D(-1, 0, 0); + frustum.m_aPlanes[1].m_Dist = m_Data[1].X; + + // get the BOTTOM plane + frustum.m_aPlanes[2].m_Norm = CVector3D(0, 1, 0); + frustum.m_aPlanes[2].m_Dist = -m_Data[0].Y; + + // get the TOP plane + frustum.m_aPlanes[3].m_Norm = CVector3D(0, -1, 0); + frustum.m_aPlanes[3].m_Dist = m_Data[1].Y; + + // get the NEAR plane + frustum.m_aPlanes[4].m_Norm = CVector3D(0, 0, 1); + frustum.m_aPlanes[4].m_Dist = -m_Data[0].Z; + + // get the FAR plane + frustum.m_aPlanes[5].m_Norm = CVector3D(0, 0, -1); + frustum.m_aPlanes[5].m_Dist = m_Data[1].Z; + + return frustum; +} /////////////////////////////////////////////////////////////////////////////// void CBoundingBoxAligned::Expand(float amount) @@ -264,11 +297,11 @@ void CBoundingBoxAligned::RenderOutline(CShaderProgramPtr& shader) const ADD_PT(0, 1, x, y, z); ADD_PT(0, 0, x, y, z); #define ADD_PT(u_, v_, x, y, z) \ STMT(int u = u_; int v = v_; \ - data.push_back(u); \ - data.push_back(v); \ - data.push_back(m_Data[x].X); \ - data.push_back(m_Data[y].Y); \ - data.push_back(m_Data[z].Z); \ + data.push_back(u); \ + data.push_back(v); \ + data.push_back(m_Data[x].X); \ + data.push_back(m_Data[y].Y); \ + data.push_back(m_Data[z].Z); \ ) ADD_FACE(u, v, 0); diff --git a/source/maths/BoundingBoxAligned.h b/source/maths/BoundingBoxAligned.h index c8d94f87b5..a3aedfea3e 100644 --- a/source/maths/BoundingBoxAligned.h +++ b/source/maths/BoundingBoxAligned.h @@ -142,12 +142,18 @@ public: void IntersectFrustumConservative(const CFrustum& frustum); /** - * Render: Render the surfaces of the bound object as triangles. + * Construct a CFrustum that describes the same volume as this bounding box. + * Only valid for non-empty bounding boxes - check IsEmpty() first. + */ + CFrustum ToFrustum() const; + + /** + * Render the surfaces of the bound object as triangles. */ void Render(CShaderProgramPtr& shader) const; /** - * Render: Render the outline of the bound object as lines. + * Render the outline of the bound object as lines. */ void RenderOutline(CShaderProgramPtr& shader) const; diff --git a/source/maths/Brush.cpp b/source/maths/Brush.cpp index 90f27f7afc..742beeb62b 100644 --- a/source/maths/Brush.cpp +++ b/source/maths/Brush.cpp @@ -414,3 +414,77 @@ void CBrush::GetFaces(std::vector >& out) const faceStartIdx = j + 1; } } + +void CBrush::Render(CShaderProgramPtr& shader) const +{ + std::vector data; + + std::vector > faces; + GetFaces(faces); + +#define ADD_VERT(a) \ + STMT( \ + data.push_back(u); \ + data.push_back(v); \ + data.push_back(m_Vertices[faces[i][a]].X); \ + data.push_back(m_Vertices[faces[i][a]].Y); \ + data.push_back(m_Vertices[faces[i][a]].Z); \ + ) + + for (size_t i = 0; i < faces.size(); ++i) + { + // Triangulate into (0,1,2), (0,2,3), ... + for (size_t j = 1; j < faces[i].size() - 2; ++j) + { + float u = 0; + float v = 0; + ADD_VERT(0); + ADD_VERT(j); + ADD_VERT(j+1); + } + } + +#undef ADD_VERT + + shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]); + shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]); + + shader->AssertPointersBound(); + glDrawArrays(GL_TRIANGLES, 0, data.size() / 5); +} + +void CBrush::RenderOutline(CShaderProgramPtr& shader) const +{ + std::vector data; + + std::vector > faces; + GetFaces(faces); + +#define ADD_VERT(a) \ + STMT( \ + data.push_back(u); \ + data.push_back(v); \ + data.push_back(m_Vertices[faces[i][a]].X); \ + data.push_back(m_Vertices[faces[i][a]].Y); \ + data.push_back(m_Vertices[faces[i][a]].Z); \ + ) + + for (size_t i = 0; i < faces.size(); ++i) + { + for (size_t j = 0; j < faces[i].size() - 1; ++j) + { + float u = 0; + float v = 0; + ADD_VERT(j); + ADD_VERT(j+1); + } + } + +#undef ADD_VERT + + shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]); + shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]); + + shader->AssertPointersBound(); + glDrawArrays(GL_LINES, 0, data.size() / 5); +} diff --git a/source/maths/Brush.h b/source/maths/Brush.h index 34e8aff6fb..2fdbbf88e9 100644 --- a/source/maths/Brush.h +++ b/source/maths/Brush.h @@ -24,6 +24,8 @@ #include "Vector3D.h" +#include "graphics/ShaderProgram.h" + class CBoundingBoxAligned; class CFrustum; class CPlane; @@ -78,6 +80,16 @@ public: */ void Intersect(const CFrustum& frustum, CBrush& result) const; + /** + * Render the surfaces of the brush as triangles. + */ + void Render(CShaderProgramPtr& shader) const; + + /** + * Render the outline of the brush as lines. + */ + void RenderOutline(CShaderProgramPtr& shader) const; + private: /** diff --git a/source/maths/Plane.cpp b/source/maths/Plane.cpp index d780fcec78..93b159d592 100644 --- a/source/maths/Plane.cpp +++ b/source/maths/Plane.cpp @@ -95,19 +95,6 @@ PLANESIDE CPlane::ClassifyPoint (const CVector3D &point) const return PS_ON; } -//solves the plane equation for a particular point -float CPlane::DistanceToPlane (const CVector3D &point) const -{ - float Dist; - - Dist = m_Norm.X * point.X + - m_Norm.Y * point.Y + - m_Norm.Z * point.Z + - m_Dist; - - return Dist; -} - //calculates the intersection point of a line with this //plane. Returns false if there is no intersection bool CPlane::FindLineSegIntersection (const CVector3D &start, const CVector3D &end, CVector3D *intsect) diff --git a/source/maths/Plane.h b/source/maths/Plane.h index c8ec217a32..fa25f7f931 100644 --- a/source/maths/Plane.h +++ b/source/maths/Plane.h @@ -56,7 +56,7 @@ class CPlane PLANESIDE ClassifyPoint (const CVector3D &point) const; //solves the plane equation for a particular point - float DistanceToPlane (const CVector3D &point) const; + inline float DistanceToPlane (const CVector3D &point) const; //calculates the intersection point of a line with this //plane. Returns false if there is no intersection @@ -68,4 +68,16 @@ class CPlane float m_Dist; //Plane distance (ie D in the plane eq.) }; +float CPlane::DistanceToPlane (const CVector3D &point) const +{ + float Dist; + + Dist = m_Norm.X * point.X + + m_Norm.Y * point.Y + + m_Norm.Z * point.Z + + m_Dist; + + return Dist; +} + #endif diff --git a/source/renderer/DecalRData.h b/source/renderer/DecalRData.h index 4a380726bb..5b1c07728f 100644 --- a/source/renderer/DecalRData.h +++ b/source/renderer/DecalRData.h @@ -21,11 +21,11 @@ #include "graphics/Camera.h" #include "graphics/RenderableObject.h" #include "graphics/ShaderProgram.h" -#include "renderer/ShadowMap.h" #include "renderer/VertexArray.h" class CModelDecal; class CSimulation2; +class ShadowMap; class CDecalRData : public CRenderData { diff --git a/source/renderer/ModelRenderer.cpp b/source/renderer/ModelRenderer.cpp index 578cab1b0b..7b758ebdfe 100644 --- a/source/renderer/ModelRenderer.cpp +++ b/source/renderer/ModelRenderer.cpp @@ -215,7 +215,7 @@ struct ShaderModelRendererInternals ModelVertexRendererPtr vertexRenderer; /// List of submitted models for rendering in this frame - std::vector submissions; + std::vector submissions[CRenderer::CULL_MAX]; }; @@ -232,7 +232,7 @@ ShaderModelRenderer::~ShaderModelRenderer() } // Submit one model. -void ShaderModelRenderer::Submit(CModel* model) +void ShaderModelRenderer::Submit(int cullGroup, CModel* model) { CModelDefPtr mdef = model->GetModelDef(); CModelRData* rdata = (CModelRData*)model->GetRenderData(); @@ -246,22 +246,27 @@ void ShaderModelRenderer::Submit(CModel* model) model->SetDirty(~0u); } - m->submissions.push_back(model); + m->submissions[cullGroup].push_back(model); } // Call update for all submitted models and enter the rendering phase void ShaderModelRenderer::PrepareModels() { - for (size_t i = 0; i < m->submissions.size(); ++i) + for (int cullGroup = 0; cullGroup < CRenderer::CULL_MAX; ++cullGroup) { - CModel* model = m->submissions[i]; + for (size_t i = 0; i < m->submissions[cullGroup].size(); ++i) + { + CModel* model = m->submissions[cullGroup][i]; - CModelRData* rdata = static_cast(model->GetRenderData()); - ENSURE(rdata->GetKey() == m->vertexRenderer.get()); + model->ValidatePosition(); - m->vertexRenderer->UpdateModelData(model, rdata, rdata->m_UpdateFlags); - rdata->m_UpdateFlags = 0; + CModelRData* rdata = static_cast(model->GetRenderData()); + ENSURE(rdata->GetKey() == m->vertexRenderer.get()); + + m->vertexRenderer->UpdateModelData(model, rdata, rdata->m_UpdateFlags); + rdata->m_UpdateFlags = 0; + } } } @@ -269,7 +274,8 @@ void ShaderModelRenderer::PrepareModels() // Clear the submissions list void ShaderModelRenderer::EndFrame() { - m->submissions.clear(); + for (int cullGroup = 0; cullGroup < CRenderer::CULL_MAX; ++cullGroup) + m->submissions[cullGroup].clear(); } @@ -358,9 +364,9 @@ struct SMRCompareTechBucket } }; -void ShaderModelRenderer::Render(const RenderModifierPtr& modifier, const CShaderDefines& context, int flags) +void ShaderModelRenderer::Render(const RenderModifierPtr& modifier, const CShaderDefines& context, int cullGroup, int flags) { - if (m->submissions.empty()) + if (m->submissions[cullGroup].empty()) return; CMatrix3D worldToCam; @@ -431,9 +437,9 @@ void ShaderModelRenderer::Render(const RenderModifierPtr& modifier, const CShade { PROFILE3("bucketing by material"); - for (size_t i = 0; i < m->submissions.size(); ++i) + for (size_t i = 0; i < m->submissions[cullGroup].size(); ++i) { - CModel* model = m->submissions[i]; + CModel* model = m->submissions[cullGroup][i]; uint32_t condFlags = 0; @@ -770,19 +776,3 @@ void ShaderModelRenderer::Render(const RenderModifierPtr& modifier, const CShade } } } - -void ShaderModelRenderer::Filter(CModelFilter& filter, int passed, int flags) -{ - for (size_t i = 0; i < m->submissions.size(); ++i) - { - CModel* model = m->submissions[i]; - - if (flags && !(model->GetFlags() & flags)) - continue; - - if (filter.Filter(model)) - model->SetFlags(model->GetFlags() | passed); - else - model->SetFlags(model->GetFlags() & ~passed); - } -} diff --git a/source/renderer/ModelRenderer.h b/source/renderer/ModelRenderer.h index 3aed661515..bd2411a817 100644 --- a/source/renderer/ModelRenderer.h +++ b/source/renderer/ModelRenderer.h @@ -46,15 +46,6 @@ typedef shared_ptr ModelRendererPtr; class CModel; class CShaderDefines; -class CModelFilter -{ - NONCOPYABLE(CModelFilter); -public: - CModelFilter() {} - virtual ~CModelFilter() {} - virtual bool Filter(CModel* model) = 0; -}; - /** * Class CModelRData: Render data that is maintained per CModel. * ModelRenderer implementations may derive from this class to store @@ -132,7 +123,7 @@ public: * @param model The model that will be added to the list of models * submitted this frame. */ - virtual void Submit(CModel* model) = 0; + virtual void Submit(int cullGroup, CModel* model) = 0; /** * PrepareModels: Calculate renderer data for all previously @@ -166,18 +157,7 @@ public: * If flags is non-zero, only models that contain flags in their * CModel::GetFlags() are rendered. */ - virtual void Render(const RenderModifierPtr& modifier, const CShaderDefines& context, int flags) = 0; - - /** - * Filter: Filter submitted models, setting the passed flags on any models - * that pass the filter, and clearing them from models that fail. - * - * @param filter Filter to select a subset of models. - * @param passed Flags to be set/cleared. - * @param flags If non-zero, only models that contain @p flags - * have the filter test applied. - */ - virtual void Filter(CModelFilter& filter, int passed, int flags = 0) = 0; + virtual void Render(const RenderModifierPtr& modifier, const CShaderDefines& context, int cullGroup, int flags) = 0; /** * CopyPositionAndNormals: Copy unanimated object-space vertices and @@ -284,11 +264,10 @@ public: virtual ~ShaderModelRenderer(); // Batching implementations - virtual void Submit(CModel* model); + virtual void Submit(int cullGroup, CModel* model); virtual void PrepareModels(); virtual void EndFrame(); - virtual void Render(const RenderModifierPtr& modifier, const CShaderDefines& context, int flags); - virtual void Filter(CModelFilter& filter, int passed, int flags); + virtual void Render(const RenderModifierPtr& modifier, const CShaderDefines& context, int cullGroup, int flags); private: ShaderModelRendererInternals* m; diff --git a/source/renderer/ParticleRenderer.cpp b/source/renderer/ParticleRenderer.cpp index b0408eb3c4..dad3345dc1 100644 --- a/source/renderer/ParticleRenderer.cpp +++ b/source/renderer/ParticleRenderer.cpp @@ -27,14 +27,16 @@ struct ParticleRendererInternals { + int frameNumber; CShaderTechniquePtr shader; CShaderTechniquePtr shaderSolid; - std::vector emitters; + std::vector emitters[CRenderer::CULL_MAX]; }; ParticleRenderer::ParticleRenderer() { m = new ParticleRendererInternals(); + m->frameNumber = 0; } ParticleRenderer::~ParticleRenderer() @@ -42,14 +44,15 @@ ParticleRenderer::~ParticleRenderer() delete m; } -void ParticleRenderer::Submit(CParticleEmitter* emitter) +void ParticleRenderer::Submit(int cullGroup, CParticleEmitter* emitter) { - m->emitters.push_back(emitter); + m->emitters[cullGroup].push_back(emitter); } void ParticleRenderer::EndFrame() { - m->emitters.clear(); + for (int cullGroup = 0; cullGroup < CRenderer::CULL_MAX; ++cullGroup) + m->emitters[cullGroup].clear(); // this should leave the capacity unchanged, which is okay since it // won't be very large or very variable } @@ -91,30 +94,36 @@ void ParticleRenderer::PrepareForRendering(const CShaderDefines& context) } } + ++m->frameNumber; + + for (int cullGroup = 0; cullGroup < CRenderer::CULL_MAX; ++cullGroup) { PROFILE("update emitters"); - for (size_t i = 0; i < m->emitters.size(); ++i) + for (size_t i = 0; i < m->emitters[cullGroup].size(); ++i) { - CParticleEmitter* emitter = m->emitters[i]; - emitter->UpdateArrayData(); + CParticleEmitter* emitter = m->emitters[cullGroup][i]; + emitter->UpdateArrayData(m->frameNumber); } } + for (int cullGroup = 0; cullGroup < CRenderer::CULL_MAX; ++cullGroup) { // Sort back-to-front by distance from camera PROFILE("sort emitters"); CMatrix3D worldToCam; g_Renderer.GetViewCamera().m_Orientation.GetInverse(worldToCam); - std::stable_sort(m->emitters.begin(), m->emitters.end(), SortEmitterDistance(worldToCam)); + std::stable_sort(m->emitters[cullGroup].begin(), m->emitters[cullGroup].end(), SortEmitterDistance(worldToCam)); } // TODO: should batch by texture here when possible, maybe } -void ParticleRenderer::RenderParticles(bool solidColor) +void ParticleRenderer::RenderParticles(int cullGroup, bool solidColor) { CShaderTechniquePtr shader = solidColor ? m->shaderSolid : m->shader; + std::vector& emitters = m->emitters[cullGroup]; + shader->BeginPass(); shader->GetShader()->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); @@ -123,9 +132,9 @@ void ParticleRenderer::RenderParticles(bool solidColor) glEnable(GL_BLEND); glDepthMask(0); - for (size_t i = 0; i < m->emitters.size(); ++i) + for (size_t i = 0; i < emitters.size(); ++i) { - CParticleEmitter* emitter = m->emitters[i]; + CParticleEmitter* emitter = emitters[i]; emitter->Bind(shader->GetShader()); emitter->RenderArray(shader->GetShader()); @@ -142,11 +151,13 @@ void ParticleRenderer::RenderParticles(bool solidColor) shader->EndPass(); } -void ParticleRenderer::RenderBounds(CShaderProgramPtr& shader) +void ParticleRenderer::RenderBounds(int cullGroup, CShaderProgramPtr& shader) { - for (size_t i = 0; i < m->emitters.size(); ++i) + std::vector& emitters = m->emitters[cullGroup]; + + for (size_t i = 0; i < emitters.size(); ++i) { - CParticleEmitter* emitter = m->emitters[i]; + CParticleEmitter* emitter = emitters[i]; CBoundingBoxAligned bounds = emitter->m_Type->CalculateBounds(emitter->GetPosition(), emitter->GetParticleBounds()); bounds.Render(shader); diff --git a/source/renderer/ParticleRenderer.h b/source/renderer/ParticleRenderer.h index 2b6fea2167..22a480264e 100644 --- a/source/renderer/ParticleRenderer.h +++ b/source/renderer/ParticleRenderer.h @@ -38,7 +38,7 @@ public: /** * Add an emitter for rendering in this frame. */ - void Submit(CParticleEmitter* emitter); + void Submit(int cullGroup, CParticleEmitter* emitter); /** * Prepare internal data structures for rendering. @@ -55,12 +55,12 @@ public: /** * Render all the submitted particles. */ - void RenderParticles(bool solidColor = false); + void RenderParticles(int cullGroup, bool solidColor = false); /** * Render bounding boxes for all the submitted emitters. */ - void RenderBounds(CShaderProgramPtr& shader); + void RenderBounds(int cullGroup, CShaderProgramPtr& shader); private: ParticleRendererInternals* m; diff --git a/source/renderer/PatchRData.h b/source/renderer/PatchRData.h index fba4110ae9..62d9c63e76 100644 --- a/source/renderer/PatchRData.h +++ b/source/renderer/PatchRData.h @@ -23,13 +23,13 @@ #include "maths/Vector3D.h" #include "graphics/RenderableObject.h" #include "graphics/ShaderProgram.h" -#include "renderer/ShadowMap.h" #include "VertexBufferManager.h" class CPatch; class CSimulation2; class CTerrainTextureEntry; class CTextRenderer; +class ShadowMap; ////////////////////////////////////////////////////////////////////////////////////////////////// // CPatchRData: class encapsulating logic for rendering terrain patches; holds per diff --git a/source/renderer/Renderer.cpp b/source/renderer/Renderer.cpp index 42d895b7ab..4c1aa4f303 100644 --- a/source/renderer/Renderer.cpp +++ b/source/renderer/Renderer.cpp @@ -44,6 +44,7 @@ #include "ps/Loader.h" #include "ps/ProfileViewer.h" #include "graphics/Camera.h" +#include "graphics/Decal.h" #include "graphics/FontManager.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" @@ -52,6 +53,7 @@ #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ParticleManager.h" +#include "graphics/Patch.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/Texture.h" @@ -356,7 +358,7 @@ public: /** * Renders all non-alpha-blended models with the given context. */ - void CallModelRenderers(const CShaderDefines& context, int flags) + void CallModelRenderers(const CShaderDefines& context, int cullGroup, int flags) { CShaderDefines contextSkinned = context; if (g_Renderer.m_Options.m_GPUSkinning) @@ -364,20 +366,20 @@ public: contextSkinned.Add(str_USE_INSTANCING, str_1); contextSkinned.Add(str_USE_GPU_SKINNING, str_1); } - Model.NormalSkinned->Render(Model.ModShader, contextSkinned, flags); + Model.NormalSkinned->Render(Model.ModShader, contextSkinned, cullGroup, flags); if (Model.NormalUnskinned != Model.NormalSkinned) { CShaderDefines contextUnskinned = context; contextUnskinned.Add(str_USE_INSTANCING, str_1); - Model.NormalUnskinned->Render(Model.ModShader, contextUnskinned, flags); + Model.NormalUnskinned->Render(Model.ModShader, contextUnskinned, cullGroup, flags); } } /** * Renders all alpha-blended models with the given context. */ - void CallTranspModelRenderers(const CShaderDefines& context, int flags) + void CallTranspModelRenderers(const CShaderDefines& context, int cullGroup, int flags) { CShaderDefines contextSkinned = context; if (g_Renderer.m_Options.m_GPUSkinning) @@ -385,35 +387,15 @@ public: contextSkinned.Add(str_USE_INSTANCING, str_1); contextSkinned.Add(str_USE_GPU_SKINNING, str_1); } - Model.TranspSkinned->Render(Model.ModShader, contextSkinned, flags); + Model.TranspSkinned->Render(Model.ModShader, contextSkinned, cullGroup, flags); if (Model.TranspUnskinned != Model.TranspSkinned) { CShaderDefines contextUnskinned = context; contextUnskinned.Add(str_USE_INSTANCING, str_1); - Model.TranspUnskinned->Render(Model.ModShader, contextUnskinned, flags); + Model.TranspUnskinned->Render(Model.ModShader, contextUnskinned, cullGroup, flags); } } - - /** - * Filters all non-alpha-blended models. - */ - void FilterModels(CModelFilter& filter, int passed, int flags = 0) - { - Model.NormalSkinned->Filter(filter, passed, flags); - if (Model.NormalUnskinned != Model.NormalSkinned) - Model.NormalUnskinned->Filter(filter, passed, flags); - } - - /** - * Filters all alpha-blended models. - */ - void FilterTranspModels(CModelFilter& filter, int passed, int flags = 0) - { - Model.TranspSkinned->Filter(filter, passed, flags); - if (Model.TranspUnskinned != Model.TranspSkinned) - Model.TranspUnskinned->Filter(filter, passed, flags); - } }; /////////////////////////////////////////////////////////////////////////////////// @@ -901,7 +883,7 @@ void CRenderer::RenderShadowMap(const CShaderDefines& context) PROFILE("render patches"); glCullFace(GL_FRONT); glEnable(GL_CULL_FACE); - m->terrainRenderer.RenderPatches(); + m->terrainRenderer.RenderPatches(CULL_SHADOWS); glCullFace(GL_BACK); } @@ -910,14 +892,14 @@ void CRenderer::RenderShadowMap(const CShaderDefines& context) { PROFILE("render models"); - m->CallModelRenderers(contextCast, MODELFLAG_CASTSHADOWS); + m->CallModelRenderers(contextCast, CULL_SHADOWS, MODELFLAG_CASTSHADOWS); } { PROFILE("render transparent models"); // disable face-culling for two-sided models glDisable(GL_CULL_FACE); - m->CallTranspModelRenderers(contextCast, MODELFLAG_CASTSHADOWS); + m->CallTranspModelRenderers(contextCast, CULL_SHADOWS, MODELFLAG_CASTSHADOWS); glEnable(GL_CULL_FACE); } @@ -926,19 +908,10 @@ void CRenderer::RenderShadowMap(const CShaderDefines& context) m->SetOpenGLCamera(m_ViewCamera); } -void CRenderer::RenderPatches(const CShaderDefines& context, const CFrustum* frustum) +void CRenderer::RenderPatches(const CShaderDefines& context, int cullGroup) { PROFILE3_GPU("patches"); - bool filtered = false; - if (frustum) - { - if (!m->terrainRenderer.CullPatches(frustum)) - return; - - filtered = true; - } - #if CONFIG2_GLES #warning TODO: implement wireface/edged rendering mode GLES #else @@ -951,9 +924,9 @@ void CRenderer::RenderPatches(const CShaderDefines& context, const CFrustum* fru // render all the patches, including blend pass if (GetRenderPath() == RP_SHADER) - m->terrainRenderer.RenderTerrainShader(context, (m_Caps.m_Shadows && m_Options.m_Shadows) ? &m->shadow : 0, filtered); + m->terrainRenderer.RenderTerrainShader(context, cullGroup, (m_Caps.m_Shadows && m_Options.m_Shadows) ? &m->shadow : 0); else - m->terrainRenderer.RenderTerrain(filtered); + m->terrainRenderer.RenderTerrain(cullGroup); #if !CONFIG2_GLES @@ -975,14 +948,14 @@ void CRenderer::RenderPatches(const CShaderDefines& context, const CFrustum* fru glLineWidth(2.0f); // render tiles edges - m->terrainRenderer.RenderPatches(filtered); + m->terrainRenderer.RenderPatches(cullGroup); // set color for outline glColor3f(0, 0, 1); glLineWidth(4.0f); // render outline of each patch - m->terrainRenderer.RenderOutlines(filtered); + m->terrainRenderer.RenderOutlines(cullGroup); // .. and restore the renderstates glLineWidth(1.0f); @@ -991,31 +964,11 @@ void CRenderer::RenderPatches(const CShaderDefines& context, const CFrustum* fru #endif } -class CModelCuller : public CModelFilter -{ -public: - CModelCuller(const CFrustum& frustum) : m_Frustum(frustum) { } - - bool Filter(CModel *model) - { - return m_Frustum.IsBoxVisible(CVector3D(0, 0, 0), model->GetWorldBoundsRec()); - } - -private: - const CFrustum& m_Frustum; -}; - -void CRenderer::RenderModels(const CShaderDefines& context, const CFrustum* frustum) +void CRenderer::RenderModels(const CShaderDefines& context, int cullGroup) { PROFILE3_GPU("models"); int flags = 0; - if (frustum) - { - flags = MODELFLAG_FILTERED; - CModelCuller culler(*frustum); - m->FilterModels(culler, flags); - } #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) @@ -1024,7 +977,7 @@ void CRenderer::RenderModels(const CShaderDefines& context, const CFrustum* frus } #endif - m->CallModelRenderers(context, flags); + m->CallModelRenderers(context, cullGroup, flags); #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) @@ -1039,24 +992,18 @@ void CRenderer::RenderModels(const CShaderDefines& context, const CFrustum* frus glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); - m->CallModelRenderers(contextWireframe, flags); + m->CallModelRenderers(contextWireframe, cullGroup, flags); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } -void CRenderer::RenderTransparentModels(const CShaderDefines& context, ETransparentMode transparentMode, const CFrustum* frustum) +void CRenderer::RenderTransparentModels(const CShaderDefines& context, int cullGroup, ETransparentMode transparentMode, bool disableFaceCulling) { PROFILE3_GPU("transparent models"); int flags = 0; - if (frustum) - { - flags = MODELFLAG_FILTERED; - CModelCuller culler(*frustum); - m->FilterTranspModels(culler, flags); - } #if !CONFIG2_GLES // switch on wireframe if we need it @@ -1067,7 +1014,7 @@ void CRenderer::RenderTransparentModels(const CShaderDefines& context, ETranspar #endif // disable face culling for two-sided models in sub-renders - if (flags) + if (disableFaceCulling) glDisable(GL_CULL_FACE); CShaderDefines contextOpaque = context; @@ -1077,12 +1024,12 @@ void CRenderer::RenderTransparentModels(const CShaderDefines& context, ETranspar contextBlend.Add(str_ALPHABLEND_PASS_BLEND, str_1); if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_OPAQUE) - m->CallTranspModelRenderers(contextOpaque, flags); + m->CallTranspModelRenderers(contextOpaque, cullGroup, flags); if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_BLEND) - m->CallTranspModelRenderers(contextBlend, flags); + m->CallTranspModelRenderers(contextBlend, cullGroup, flags); - if (flags) + if (disableFaceCulling) glEnable(GL_CULL_FACE); #if !CONFIG2_GLES @@ -1099,7 +1046,7 @@ void CRenderer::RenderTransparentModels(const CShaderDefines& context, ETranspar glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); - m->CallTranspModelRenderers(contextWireframe, flags); + m->CallTranspModelRenderers(contextWireframe, cullGroup, flags); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } @@ -1111,14 +1058,14 @@ void CRenderer::RenderTransparentModels(const CShaderDefines& context, ETranspar // SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space) // Based on code from Game Programming Gems 5, from http://www.terathon.com/code/oblique.html // - worldPlane is a clip plane in world space (worldPlane.Dot(v) >= 0 for any vector v passing the clipping test) -void CRenderer::SetObliqueFrustumClipping(const CVector4D& worldPlane) +void CRenderer::SetObliqueFrustumClipping(CCamera& camera, const CVector4D& worldPlane) const { // First, we'll convert the given clip plane to camera space, then we'll // Get the view matrix and normal matrix (top 3x3 part of view matrix) - CMatrix3D normalMatrix = m_ViewCamera.m_Orientation.GetTranspose(); + CMatrix3D normalMatrix = camera.m_Orientation.GetTranspose(); CVector4D camPlane = normalMatrix.Transform(worldPlane); - CMatrix3D matrix = m_ViewCamera.GetProjection(); + CMatrix3D matrix = camera.GetProjection(); // Calculate the clip-space corner point opposite the clipping plane // as (sgn(camPlane.x), sgn(camPlane.y), 1, 1) and @@ -1141,8 +1088,81 @@ void CRenderer::SetObliqueFrustumClipping(const CVector4D& worldPlane) matrix[14] = c.W; // Load it back into the camera - m_ViewCamera.SetProjection(matrix); - m->SetOpenGLCamera(m_ViewCamera); + camera.SetProjection(matrix); +} + +void CRenderer::ComputeReflectionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const +{ + WaterManager& wm = m->waterManager; + + float fov = m_ViewCamera.GetFOV(); + + // Expand fov slightly since ripples can reflect parts of the scene that + // are slightly outside the normal camera view, and we want to avoid any + // noticeable edge-filtering artifacts + fov *= 1.05f; + + camera = m_ViewCamera; + + // Temporarily change the camera to one that is reflected. + // Also, for texturing purposes, make it render to a view port the size of the + // water texture, stretch the image according to our aspect ratio so it covers + // the whole screen despite being rendered into a square, and cover slightly more + // of the view so we can see wavy reflections of slightly off-screen objects. + camera.m_Orientation.Scale(1, -1, 1); + camera.m_Orientation.Translate(0, 2*wm.m_WaterHeight, 0); + camera.UpdateFrustum(scissor); + camera.ClipFrustum(CVector4D(0, 1, 0, -wm.m_WaterHeight)); + + SViewPort vp; + vp.m_Height = wm.m_ReflectionTextureSize; + vp.m_Width = wm.m_ReflectionTextureSize; + vp.m_X = 0; + vp.m_Y = 0; + camera.SetViewPort(vp); + camera.SetProjection(m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane(), fov); + CMatrix3D scaleMat; + scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); + camera.m_ProjMat = scaleMat * camera.m_ProjMat; + + CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight); + SetObliqueFrustumClipping(camera, camPlane); + +} + +void CRenderer::ComputeRefractionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const +{ + WaterManager& wm = m->waterManager; + + float fov = m_ViewCamera.GetFOV(); + + // Expand fov slightly since ripples can reflect parts of the scene that + // are slightly outside the normal camera view, and we want to avoid any + // noticeable edge-filtering artifacts + fov *= 1.05f; + + camera = m_ViewCamera; + + // Temporarily change the camera to make it render to a view port the size of the + // water texture, stretch the image according to our aspect ratio so it covers + // the whole screen despite being rendered into a square, and cover slightly more + // of the view so we can see wavy refractions of slightly off-screen objects. + camera.UpdateFrustum(scissor); + camera.ClipFrustum(CVector4D(0, -1, 0, wm.m_WaterHeight)); + + SViewPort vp; + vp.m_Height = wm.m_RefractionTextureSize; + vp.m_Width = wm.m_RefractionTextureSize; + vp.m_X = 0; + vp.m_Y = 0; + camera.SetViewPort(vp); + camera.SetProjection(m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane(), fov); + CMatrix3D scaleMat; + scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); + camera.m_ProjMat = scaleMat * camera.m_ProjMat; + + CVector4D camPlane(0, -1, 0, wm.m_WaterHeight); + SetObliqueFrustumClipping(camera, camPlane); } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1156,40 +1176,21 @@ SScreenRect CRenderer::RenderReflections(const CShaderDefines& context, const CB // Remember old camera CCamera normalCamera = m_ViewCamera; - // Temporarily change the camera to one that is reflected. - // Also, for texturing purposes, make it render to a view port the size of the - // water texture, stretch the image according to our aspect ratio so it covers - // the whole screen despite being rendered into a square, and cover slightly more - // of the view so we can see wavy reflections of slightly off-screen objects. - m_ViewCamera.m_Orientation.Scale(1, -1, 1); - m_ViewCamera.m_Orientation.Translate(0, 2*wm.m_WaterHeight, 0); - m_ViewCamera.UpdateFrustum(scissor); - m_ViewCamera.ClipFrustum(CVector4D(0, 1, 0, -wm.m_WaterHeight)); - - SViewPort vp; - vp.m_Height = wm.m_ReflectionTextureSize; - vp.m_Width = wm.m_ReflectionTextureSize; - vp.m_X = 0; - vp.m_Y = 0; - m_ViewCamera.SetViewPort(vp); - m_ViewCamera.SetProjection(normalCamera.GetNearPlane(), normalCamera.GetFarPlane(), normalCamera.GetFOV()*1.05f); // Slightly higher than view FOV - CMatrix3D scaleMat; - scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); - m_ViewCamera.m_ProjMat = scaleMat * m_ViewCamera.m_ProjMat; + ComputeReflectionCamera(m_ViewCamera, scissor); m->SetOpenGLCamera(m_ViewCamera); - CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight); - SetObliqueFrustumClipping(camPlane); - // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_ReflectionMatrix = m_ViewCamera.GetViewProjection(); + float vpHeight = wm.m_ReflectionTextureSize; + float vpWidth = wm.m_ReflectionTextureSize; + SScreenRect screenScissor; - screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vp.m_Width); - screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vp.m_Height); - screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vp.m_Width); - screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vp.m_Height); + screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vpWidth); + screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vpHeight); + screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vpWidth); + screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vpHeight); if (screenScissor.x1 < screenScissor.x2 && screenScissor.y1 < screenScissor.y2) { @@ -1198,20 +1199,29 @@ SScreenRect CRenderer::RenderReflections(const CShaderDefines& context, const CB glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + // Swap front/back faces to compensate for the reflected view-projection matrix glFrontFace(GL_CW); // Render sky, terrain and models m->skyManager.RenderSky(); ogl_WarnIfError(); - RenderPatches(context, &m_ViewCamera.GetFrustum()); + RenderPatches(context, CULL_REFLECTIONS); ogl_WarnIfError(); - RenderModels(context, &m_ViewCamera.GetFrustum()); + RenderModels(context, CULL_REFLECTIONS); ogl_WarnIfError(); - RenderTransparentModels(context, TRANSPARENT_OPAQUE, &m_ViewCamera.GetFrustum()); + RenderTransparentModels(context, CULL_REFLECTIONS, TRANSPARENT_OPAQUE, true); ogl_WarnIfError(); glFrontFace(GL_CCW); + // Particles are always oriented to face the camera in the vertex shader, + // so they don't need the inverted glFrontFace + if (m_Options.m_Particles) + { + RenderParticles(CULL_REFLECTIONS); + ogl_WarnIfError(); + } + glDisable(GL_SCISSOR_TEST); // Copy the image to a texture @@ -1243,37 +1253,21 @@ SScreenRect CRenderer::RenderRefractions(const CShaderDefines& context, const CB // Remember old camera CCamera normalCamera = m_ViewCamera; - // Temporarily change the camera to make it render to a view port the size of the - // water texture, stretch the image according to our aspect ratio so it covers - // the whole screen despite being rendered into a square, and cover slightly more - // of the view so we can see wavy refractions of slightly off-screen objects. - m_ViewCamera.UpdateFrustum(scissor); - m_ViewCamera.ClipFrustum(CVector4D(0, -1, 0, wm.m_WaterHeight)); - - SViewPort vp; - vp.m_Height = wm.m_RefractionTextureSize; - vp.m_Width = wm.m_RefractionTextureSize; - vp.m_X = 0; - vp.m_Y = 0; - m_ViewCamera.SetViewPort(vp); - m_ViewCamera.SetProjection(normalCamera.GetNearPlane(), normalCamera.GetFarPlane(), normalCamera.GetFOV()*1.05f); // Slightly higher than view FOV - CMatrix3D scaleMat; - scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); - m_ViewCamera.m_ProjMat = scaleMat * m_ViewCamera.m_ProjMat; + ComputeRefractionCamera(m_ViewCamera, scissor); m->SetOpenGLCamera(m_ViewCamera); - CVector4D camPlane(0, -1, 0, wm.m_WaterHeight); - SetObliqueFrustumClipping(camPlane); - // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_RefractionMatrix = m_ViewCamera.GetViewProjection(); + float vpHeight = wm.m_RefractionTextureSize; + float vpWidth = wm.m_RefractionTextureSize; + SScreenRect screenScissor; - screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vp.m_Width); - screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vp.m_Height); - screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vp.m_Width); - screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vp.m_Height); + screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vpWidth); + screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vpHeight); + screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vpWidth); + screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vpHeight); if (screenScissor.x1 < screenScissor.x2 && screenScissor.y1 < screenScissor.y2) { glEnable(GL_SCISSOR_TEST); @@ -1283,13 +1277,15 @@ SScreenRect CRenderer::RenderRefractions(const CShaderDefines& context, const CB glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Render terrain and models - RenderPatches(context, &m_ViewCamera.GetFrustum()); + RenderPatches(context, CULL_REFRACTIONS); ogl_WarnIfError(); - RenderModels(context, &m_ViewCamera.GetFrustum()); + RenderModels(context, CULL_REFRACTIONS); ogl_WarnIfError(); - RenderTransparentModels(context, TRANSPARENT_OPAQUE, &m_ViewCamera.GetFrustum()); + RenderTransparentModels(context, CULL_REFRACTIONS, TRANSPARENT_OPAQUE, false); ogl_WarnIfError(); + // Don't bother with particles since it's unlikely they'll be visible underwater + glDisable(GL_SCISSOR_TEST); // Copy the image to a texture @@ -1347,18 +1343,18 @@ void CRenderer::RenderSilhouettes(const CShaderDefines& context) // protrude into the ground, only occlude with the back faces of the // terrain (so silhouettes will still display when behind hills) glCullFace(GL_FRONT); - m->terrainRenderer.RenderPatches(); + m->terrainRenderer.RenderPatches(CULL_DEFAULT); glCullFace(GL_BACK); } { PROFILE("render model occluders"); - m->CallModelRenderers(contextOccluder, MODELFLAG_SILHOUETTE_OCCLUDER); + m->CallModelRenderers(contextOccluder, CULL_DEFAULT, MODELFLAG_SILHOUETTE_OCCLUDER); } { PROFILE("render transparent occluders"); - m->CallTranspModelRenderers(contextOccluder, MODELFLAG_SILHOUETTE_OCCLUDER); + m->CallTranspModelRenderers(contextOccluder, CULL_DEFAULT, MODELFLAG_SILHOUETTE_OCCLUDER); } glDepthFunc(GL_GEQUAL); @@ -1392,7 +1388,7 @@ void CRenderer::RenderSilhouettes(const CShaderDefines& context) { PROFILE("render models"); - m->CallModelRenderers(contextDisplay, MODELFLAG_SILHOUETTE_DISPLAY); + m->CallModelRenderers(contextDisplay, CULL_DEFAULT, MODELFLAG_SILHOUETTE_DISPLAY); // (This won't render transparent objects with SILHOUETTE_DISPLAY - will // we have any units that need that?) } @@ -1412,7 +1408,7 @@ void CRenderer::RenderSilhouettes(const CShaderDefines& context) } } -void CRenderer::RenderParticles() +void CRenderer::RenderParticles(int cullGroup) { // Only supported in shader modes if (GetRenderPath() != RP_SHADER) @@ -1420,7 +1416,7 @@ void CRenderer::RenderParticles() PROFILE3_GPU("particles"); - m->particleRenderer.RenderParticles(); + m->particleRenderer.RenderParticles(cullGroup); #if !CONFIG2_GLES if (m_ModelRenderMode == EDGED_FACES) @@ -1438,7 +1434,7 @@ void CRenderer::RenderParticles() shader->Uniform(str_color, 0.0f, 1.0f, 0.0f, 1.0f); shader->Uniform(str_transform, m_ViewCamera.GetViewProjection()); - m->particleRenderer.RenderBounds(shader); + m->particleRenderer.RenderBounds(cullGroup, shader); shaderTech->EndPass(); @@ -1449,7 +1445,7 @@ void CRenderer::RenderParticles() /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderSubmissions: force rendering of any batched objects -void CRenderer::RenderSubmissions() +void CRenderer::RenderSubmissions(const CBoundingBoxAligned& waterScissor) { PROFILE3("render submissions"); @@ -1460,6 +1456,8 @@ void CRenderer::RenderSubmissions() CShaderDefines context = m->globalContext; + int cullGroup = CULL_DEFAULT; + ogl_WarnIfError(); // Set the camera @@ -1495,39 +1493,31 @@ void CRenderer::RenderSubmissions() ogl_WarnIfError(); - CBoundingBoxAligned waterScissor; if (m_WaterManager->m_RenderWater) { - waterScissor = m->terrainRenderer.ScissorWater(m_ViewCamera.GetViewProjection()); if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater()) { PROFILE3_GPU("water scissor"); - SScreenRect dirty = { 0, 0, 0, 0 }; - if (m_Options.m_WaterRefraction && m_Options.m_WaterReflection) + SScreenRect dirty = { INT_MAX, INT_MAX, INT_MIN, INT_MIN }; + + if (m_Options.m_WaterReflection) { SScreenRect reflectionScissor = RenderReflections(context, waterScissor); - SScreenRect refractionScissor = RenderRefractions(context, waterScissor); - dirty.x1 = std::min(reflectionScissor.x1, refractionScissor.x1); - dirty.y1 = std::min(reflectionScissor.y1, refractionScissor.y1); - dirty.x2 = std::max(reflectionScissor.x2, refractionScissor.x2); - dirty.y2 = std::max(reflectionScissor.y2, refractionScissor.y2); - } - else if (m_Options.m_WaterRefraction) + dirty.x1 = std::min(dirty.x1, reflectionScissor.x1); + dirty.y1 = std::min(dirty.y1, reflectionScissor.y1); + dirty.x2 = std::max(dirty.x2, reflectionScissor.x2); + dirty.y2 = std::max(dirty.y2, reflectionScissor.y2); + } + + if (m_Options.m_WaterRefraction) { SScreenRect refractionScissor = RenderRefractions(context, waterScissor); - dirty.x1 = refractionScissor.x1; - dirty.y1 = refractionScissor.y1; - dirty.x2 = refractionScissor.x2; - dirty.y2 = refractionScissor.y2; - } - else if (m_Options.m_WaterReflection) - { - SScreenRect reflectionScissor = RenderReflections(context, waterScissor); - dirty.x1 = reflectionScissor.x1; - dirty.y1 = reflectionScissor.y1; - dirty.x2 = reflectionScissor.x2; - dirty.y2 = reflectionScissor.y2; + dirty.x1 = std::min(dirty.x1, refractionScissor.x1); + dirty.y1 = std::min(dirty.y1, refractionScissor.y1); + dirty.x2 = std::max(dirty.x2, refractionScissor.x2); + dirty.y2 = std::max(dirty.y2, refractionScissor.y2); } + if (dirty.x1 < dirty.x2 && dirty.y1 < dirty.y2) { glEnable(GL_SCISSOR_TEST); @@ -1545,7 +1535,7 @@ void CRenderer::RenderSubmissions() } // render submitted patches and models - RenderPatches(context); + RenderPatches(context, cullGroup); ogl_WarnIfError(); // render debug-related terrain overlays @@ -1556,27 +1546,27 @@ void CRenderer::RenderSubmissions() m->overlayRenderer.RenderOverlaysBeforeWater(); ogl_WarnIfError(); - RenderModels(context); + RenderModels(context, cullGroup); ogl_WarnIfError(); // render water if (m_WaterManager->m_RenderWater && g_Game && waterScissor.GetVolume() > 0) { // render transparent stuff, but only the solid parts that can occlude block water - RenderTransparentModels(context, TRANSPARENT_OPAQUE); + RenderTransparentModels(context, cullGroup, TRANSPARENT_OPAQUE, false); ogl_WarnIfError(); - m->terrainRenderer.RenderWater(context, &m->shadow); + m->terrainRenderer.RenderWater(context, cullGroup, &m->shadow); ogl_WarnIfError(); // render transparent stuff again, but only the blended parts that overlap water - RenderTransparentModels(context, TRANSPARENT_BLEND); + RenderTransparentModels(context, cullGroup, TRANSPARENT_BLEND, false); ogl_WarnIfError(); } else { // render transparent stuff, so it can overlap models/terrain - RenderTransparentModels(context, TRANSPARENT); + RenderTransparentModels(context, cullGroup, TRANSPARENT, false); ogl_WarnIfError(); } @@ -1591,7 +1581,7 @@ void CRenderer::RenderSubmissions() // particles are transparent so render after water if (m_Options.m_Particles) { - RenderParticles(); + RenderParticles(cullGroup); ogl_WarnIfError(); } @@ -1694,7 +1684,7 @@ void CRenderer::RenderTextOverlays() PROFILE3_GPU("text overlays"); if (m_DisplayTerrainPriorities) - m->terrainRenderer.RenderPriorities(); + m->terrainRenderer.RenderPriorities(CULL_DEFAULT); ogl_WarnIfError(); } @@ -1726,69 +1716,92 @@ SViewPort CRenderer::GetViewport() void CRenderer::Submit(CPatch* patch) { - m->terrainRenderer.Submit(patch); + if (m_CurrentCullGroup == CULL_DEFAULT) + m->shadow.AddShadowReceiverBound(patch->GetWorldBounds()); + + if (m_CurrentCullGroup == CULL_SHADOWS) + m->shadow.AddShadowCasterBound(patch->GetWorldBounds()); + + m->terrainRenderer.Submit(m_CurrentCullGroup, patch); } void CRenderer::Submit(SOverlayLine* overlay) { - m->overlayRenderer.Submit(overlay); + // Overlays are only needed in the default cull group for now, + // so just ignore submissions to any other group + if (m_CurrentCullGroup == CULL_DEFAULT) + m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlayTexturedLine* overlay) { - m->overlayRenderer.Submit(overlay); + if (m_CurrentCullGroup == CULL_DEFAULT) + m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlaySprite* overlay) { - m->overlayRenderer.Submit(overlay); + if (m_CurrentCullGroup == CULL_DEFAULT) + m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlayQuad* overlay) { - m->overlayRenderer.Submit(overlay); + if (m_CurrentCullGroup == CULL_DEFAULT) + m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlaySphere* overlay) { - m->overlayRenderer.Submit(overlay); + if (m_CurrentCullGroup == CULL_DEFAULT) + m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(CModelDecal* decal) { - m->terrainRenderer.Submit(decal); + // Decals can't cast shadows since they're flat on the terrain. + // They can receive shadows, but the terrain under them will have + // already been passed to AddShadowCasterBound, so don't bother + // doing it again here. + + m->terrainRenderer.Submit(m_CurrentCullGroup, decal); } void CRenderer::Submit(CParticleEmitter* emitter) { - m->particleRenderer.Submit(emitter); + m->particleRenderer.Submit(m_CurrentCullGroup, emitter); } void CRenderer::SubmitNonRecursive(CModel* model) { - if (model->GetFlags() & MODELFLAG_CASTSHADOWS) + if (m_CurrentCullGroup == CULL_DEFAULT) { - m->shadow.AddShadowedBound(model->GetWorldBounds()); + m->shadow.AddShadowReceiverBound(model->GetWorldBounds()); } - // Tricky: The call to GetWorldBounds() above can invalidate the position - model->ValidatePosition(); + if (m_CurrentCullGroup == CULL_SHADOWS) + { + if (!(model->GetFlags() & MODELFLAG_CASTSHADOWS)) + return; + + m->shadow.AddShadowCasterBound(model->GetWorldBounds()); + } bool requiresSkinning = (model->GetModelDef()->GetNumBones() != 0); if (model->GetMaterial().UsesAlphaBlending()) { if (requiresSkinning) - m->Model.TranspSkinned->Submit(model); + m->Model.TranspSkinned->Submit(m_CurrentCullGroup, model); else - m->Model.TranspUnskinned->Submit(model); + m->Model.TranspUnskinned->Submit(m_CurrentCullGroup, model); } else { if (requiresSkinning) - m->Model.NormalSkinned->Submit(model); + m->Model.NormalSkinned->Submit(m_CurrentCullGroup, model); else - m->Model.NormalUnskinned->Submit(model); + m->Model.NormalUnskinned->Submit(m_CurrentCullGroup, model); } } @@ -1801,13 +1814,54 @@ void CRenderer::RenderScene(Scene& scene) CFrustum frustum = m_CullCamera.GetFrustum(); + m_CurrentCullGroup = CULL_DEFAULT; + scene.EnumerateObjects(frustum, this); m->particleManager.RenderSubmit(*this, frustum); + if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) + { + m_CurrentCullGroup = CULL_SHADOWS; + + CFrustum shadowFrustum = m->shadow.GetShadowCasterCullFrustum(); + scene.EnumerateObjects(shadowFrustum, this); + } + + CBoundingBoxAligned waterScissor; + if (m_WaterManager->m_RenderWater) + { + waterScissor = m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera.GetViewProjection()); + + if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater()) + { + if (m_Options.m_WaterReflection) + { + m_CurrentCullGroup = CULL_REFLECTIONS; + + CCamera reflectionCamera; + ComputeReflectionCamera(reflectionCamera, waterScissor); + + scene.EnumerateObjects(reflectionCamera.GetFrustum(), this); + } + + if (m_Options.m_WaterRefraction) + { + m_CurrentCullGroup = CULL_REFRACTIONS; + + CCamera refractionCamera; + ComputeRefractionCamera(refractionCamera, waterScissor); + + scene.EnumerateObjects(refractionCamera.GetFrustum(), this); + } + } + } + + m_CurrentCullGroup = -1; + ogl_WarnIfError(); - RenderSubmissions(); + RenderSubmissions(waterScissor); m_CurrentScene = NULL; } diff --git a/source/renderer/Renderer.h b/source/renderer/Renderer.h index 918602be2f..cd35db0bfc 100644 --- a/source/renderer/Renderer.h +++ b/source/renderer/Renderer.h @@ -98,6 +98,14 @@ public: OPT_DISPLAYFRUSTUM, }; + enum CullGroup { + CULL_DEFAULT, + CULL_SHADOWS, + CULL_REFLECTIONS, + CULL_REFRACTIONS, + CULL_MAX + }; + enum RenderPath { // If no rendering path is configured explicitly, the renderer // will choose the path when Open() is called. @@ -359,18 +367,18 @@ protected: //END: Implementation of SceneCollector // render any batched objects - void RenderSubmissions(); + void RenderSubmissions(const CBoundingBoxAligned& waterScissor); // patch rendering stuff - void RenderPatches(const CShaderDefines& context, const CFrustum* frustum = 0); + void RenderPatches(const CShaderDefines& context, int cullGroup); // model rendering stuff - void RenderModels(const CShaderDefines& context, const CFrustum* frustum = 0); - void RenderTransparentModels(const CShaderDefines& context, ETransparentMode transparentMode, const CFrustum* frustum = 0); + void RenderModels(const CShaderDefines& context, int cullGroup); + void RenderTransparentModels(const CShaderDefines& context, int cullGroup, ETransparentMode transparentMode, bool disableFaceCulling); void RenderSilhouettes(const CShaderDefines& context); - void RenderParticles(); + void RenderParticles(int cullGroup); // shadow rendering stuff void RenderShadowMap(const CShaderDefines& context); @@ -379,11 +387,14 @@ protected: SScreenRect RenderReflections(const CShaderDefines& context, const CBoundingBoxAligned& scissor); SScreenRect RenderRefractions(const CShaderDefines& context, const CBoundingBoxAligned& scissor); + void ComputeReflectionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const; + void ComputeRefractionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const; + // debugging void DisplayFrustum(); // enable oblique frustum clipping with the given clip plane - void SetObliqueFrustumClipping(const CVector4D& clipPlane); + void SetObliqueFrustumClipping(CCamera& camera, const CVector4D& clipPlane) const; void ReloadShaders(); void RecomputeSystemShaderDefines(); @@ -423,6 +434,7 @@ protected: // only valid inside a call to RenderScene Scene* m_CurrentScene; + int m_CurrentCullGroup; // color used to clear screen in BeginFrame float m_ClearColor[4]; diff --git a/source/renderer/ShadowMap.cpp b/source/renderer/ShadowMap.cpp index f34926c308..f776175714 100644 --- a/source/renderer/ShadowMap.cpp +++ b/source/renderer/ShadowMap.cpp @@ -31,6 +31,7 @@ #include "graphics/ShaderManager.h" #include "maths/BoundingBoxAligned.h" +#include "maths/Brush.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" @@ -69,7 +70,10 @@ struct ShadowMapInternals // transform light space into world space CMatrix3D InvLightTransform; // bounding box of shadowed objects in light space - CBoundingBoxAligned ShadowBound; + CBoundingBoxAligned ShadowCasterBound; + CBoundingBoxAligned ShadowReceiverBound; + + CBoundingBoxAligned ShadowRenderBound; // Camera transformed into light space CCamera LightspaceCamera; @@ -198,7 +202,8 @@ void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir) m->LightTransform._44 = 1.0; m->LightTransform.GetInverse(m->InvLightTransform); - m->ShadowBound.SetEmpty(); + m->ShadowCasterBound.SetEmpty(); + m->ShadowReceiverBound.SetEmpty(); // m->LightspaceCamera = camera; @@ -210,26 +215,79 @@ void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir) ////////////////////////////////////////////////////////////////////////////// // AddShadowedBound: add a world-space bounding box to the bounds of shadowed // objects -void ShadowMap::AddShadowedBound(const CBoundingBoxAligned& bounds) +void ShadowMap::AddShadowCasterBound(const CBoundingBoxAligned& bounds) { CBoundingBoxAligned lightspacebounds; bounds.Transform(m->LightTransform, lightspacebounds); - m->ShadowBound += lightspacebounds; + m->ShadowCasterBound += lightspacebounds; } +void ShadowMap::AddShadowReceiverBound(const CBoundingBoxAligned& bounds) +{ + CBoundingBoxAligned lightspacebounds; + + bounds.Transform(m->LightTransform, lightspacebounds); + m->ShadowReceiverBound += lightspacebounds; +} + +CFrustum ShadowMap::GetShadowCasterCullFrustum() +{ + // Get the bounds of all objects that can receive shadows + CBoundingBoxAligned bound = m->ShadowReceiverBound; + + // Intersect with the camera frustum, so the shadow map doesn't have to get + // stretched to cover the off-screen parts of large models + bound.IntersectFrustumConservative(m->LightspaceCamera.GetFrustum()); + + // ShadowBound might have been empty to begin with, producing an empty result + if (bound.IsEmpty()) + { + // CFrustum can't easily represent nothingness, so approximate it with + // a single point which won't match many objects + bound += CVector3D(0.0f, 0.0f, 0.0f); + return bound.ToFrustum(); + } + + // Extend the bounds a long way towards the light source, to encompass + // all objects that might cast visible shadows. + // (The exact constant was picked entirely arbitrarily.) + bound[0].Z -= 1000.f; + + CFrustum frustum = bound.ToFrustum(); + frustum.Transform(m->InvLightTransform); + return frustum; +} /////////////////////////////////////////////////////////////////////////////////////////////////// // CalcShadowMatrices: calculate required matrices for shadow map generation - the light's // projection and transformation matrices void ShadowMapInternals::CalcShadowMatrices() { - float minZ = ShadowBound[0].Z; + // Start building the shadow map to cover all objects that will receive shadows + CBoundingBoxAligned receiverBound = ShadowReceiverBound; - ShadowBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum()); + // Intersect with the camera frustum, so the shadow map doesn't have to get + // stretched to cover the off-screen parts of large models + receiverBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum()); + + // Intersect with the shadow caster bounds, because there's no point + // wasting space around the edges of the shadow map that we're not going + // to draw into + ShadowRenderBound[0].X = std::max(receiverBound[0].X, ShadowCasterBound[0].X); + ShadowRenderBound[0].Y = std::max(receiverBound[0].Y, ShadowCasterBound[0].Y); + ShadowRenderBound[1].X = std::min(receiverBound[1].X, ShadowCasterBound[1].X); + ShadowRenderBound[1].Y = std::min(receiverBound[1].Y, ShadowCasterBound[1].Y); + + // Set the near and far planes to include just the shadow casters, + // so we make full use of the depth texture's range. Add a bit of a + // delta so we don't accidentally clip objects that are directly on + // the planes. + ShadowRenderBound[0].Z = ShadowCasterBound[0].Z - 2.f; + ShadowRenderBound[1].Z = ShadowCasterBound[1].Z + 2.f; // ShadowBound might have been empty to begin with, producing an empty result - if (ShadowBound.IsEmpty()) + if (ShadowRenderBound.IsEmpty()) { // no-op LightProjection.SetIdentity(); @@ -239,20 +297,14 @@ void ShadowMapInternals::CalcShadowMatrices() // round off the shadow boundaries to sane increments to help reduce swim effect float boundInc = 16.0f; - ShadowBound[0].X = floor(ShadowBound[0].X / boundInc) * boundInc; - ShadowBound[0].Y = floor(ShadowBound[0].Y / boundInc) * boundInc; - ShadowBound[1].X = ceil(ShadowBound[1].X / boundInc) * boundInc; - ShadowBound[1].Y = ceil(ShadowBound[1].Y / boundInc) * boundInc; - - // minimum Z bound must not be clipped too much, because objects that lie outside - // the shadow bounds cannot cast shadows either - // the 2.0 is rather arbitrary: it should be big enough so that we won't accidentally miss - // a shadow generator, and small enough not to affect Z precision - ShadowBound[0].Z = minZ - 2.0; + ShadowRenderBound[0].X = floor(ShadowRenderBound[0].X / boundInc) * boundInc; + ShadowRenderBound[0].Y = floor(ShadowRenderBound[0].Y / boundInc) * boundInc; + ShadowRenderBound[1].X = ceil(ShadowRenderBound[1].X / boundInc) * boundInc; + ShadowRenderBound[1].Y = ceil(ShadowRenderBound[1].Y / boundInc) * boundInc; // Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering - CVector3D scale = ShadowBound[1] - ShadowBound[0]; - CVector3D shift = (ShadowBound[1] + ShadowBound[0]) * -0.5; + CVector3D scale = ShadowRenderBound[1] - ShadowRenderBound[0]; + CVector3D shift = (ShadowRenderBound[1] + ShadowRenderBound[0]) * -0.5; if (scale.X < 1.0) scale.X = 1.0; @@ -266,8 +318,8 @@ void ShadowMapInternals::CalcShadowMatrices() scale.Z = 2.0 / scale.Z; // make sure a given world position falls on a consistent shadowmap texel fractional offset - float offsetX = fmod(ShadowBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth)); - float offsetY = fmod(ShadowBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight)); + float offsetX = fmod(ShadowRenderBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth)); + float offsetY = fmod(ShadowRenderBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight)); LightProjection.SetZero(); LightProjection._11 = scale.X; @@ -288,11 +340,11 @@ void ShadowMapInternals::CalcShadowMatrices() CMatrix3D lightToTex; lightToTex.SetZero(); lightToTex._11 = texscalex; - lightToTex._14 = (offsetX - ShadowBound[0].X) * texscalex; + lightToTex._14 = (offsetX - ShadowRenderBound[0].X) * texscalex; lightToTex._22 = texscaley; - lightToTex._24 = (offsetY - ShadowBound[0].Y) * texscaley; + lightToTex._24 = (offsetY - ShadowRenderBound[0].Y) * texscaley; lightToTex._33 = texscalez; - lightToTex._34 = -ShadowBound[0].Z * texscalez; + lightToTex._34 = -ShadowRenderBound[0].Z * texscalez; lightToTex._44 = 1.0; TextureMatrix = lightToTex * LightTransform; @@ -579,35 +631,50 @@ void ShadowMap::RenderDebugBounds() glDepthMask(0); glDisable(GL_CULL_FACE); - // Render shadow bound + // Render various shadow bounds: + // Yellow = bounds of objects in view frustum that receive shadows + // Red = culling frustum used to find potential shadow casters + // Green = bounds of objects in culling frustum that cast shadows + // Blue = frustum used for rendering the shadow map + shader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection() * m->InvLightTransform); + shader->Uniform(str_color, 1.0f, 1.0f, 0.0f, 1.0f); + m->ShadowReceiverBound.RenderOutline(shader); + + shader->Uniform(str_color, 0.0f, 1.0f, 0.0f, 1.0f); + m->ShadowCasterBound.RenderOutline(shader); + glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); shader->Uniform(str_color, 0.0f, 0.0f, 1.0f, 0.25f); - m->ShadowBound.Render(shader); + m->ShadowRenderBound.Render(shader); glDisable(GL_BLEND); shader->Uniform(str_color, 0.0f, 0.0f, 1.0f, 1.0f); - m->ShadowBound.RenderOutline(shader); + m->ShadowRenderBound.RenderOutline(shader); - // Draw a funny line/triangle direction indicator thing for unknown reasons - float shadowLineVerts[] = { - 0.0, 0.0, 0.0, - 0.0, 0.0, 50.0, + // Render light frustum - 0.0, 0.0, 50.0, - 50.0, 0.0, 50.0, + shader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection()); + + CFrustum frustum = GetShadowCasterCullFrustum(); + // We don't have a function to create a brush directly from a frustum, so use + // the ugly approach of creating a large cube and then intersecting with the frustum + CBoundingBoxAligned dummy(CVector3D(-1e4, -1e4, -1e4), CVector3D(1e4, 1e4, 1e4)); + CBrush brush(dummy); + CBrush frustumBrush; + brush.Intersect(frustum, frustumBrush); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + shader->Uniform(str_color, 1.0f, 0.0f, 0.0f, 0.25f); + frustumBrush.Render(shader); + glDisable(GL_BLEND); + + shader->Uniform(str_color, 1.0f, 0.0f, 0.0f, 1.0f); + frustumBrush.RenderOutline(shader); - 50.0, 0.0, 50.0, - 0.0, 50.0, 50.0, - - 0.0, 50.0, 50.0, - 0.0, 0.0, 50.0 - }; - shader->VertexPointer(3, GL_FLOAT, 0, shadowLineVerts); - shader->AssertPointersBound(); - glDrawArrays(GL_LINES, 0, 8); shaderTech->EndPass(); diff --git a/source/renderer/ShadowMap.h b/source/renderer/ShadowMap.h index a5b2d2f7f9..77fe33dcbd 100644 --- a/source/renderer/ShadowMap.h +++ b/source/renderer/ShadowMap.h @@ -89,12 +89,30 @@ public: void SetupFrame(const CCamera& camera, const CVector3D& lightdir); /** - * AddShadowedBound: Add the bounding box of an object that has to be shadowed. + * Add the bounding box of an object that will cast a shadow. * This is used to calculate the bounds for the shadow map. * * @param bounds world space bounding box */ - void AddShadowedBound(const CBoundingBoxAligned& bounds); + void AddShadowCasterBound(const CBoundingBoxAligned& bounds); + + /** + * Add the bounding box of an object that will receive a shadow. + * This is used to calculate the bounds for the shadow map. + * + * @param bounds world space bounding box + */ + void AddShadowReceiverBound(const CBoundingBoxAligned& bounds); + + /** + * Compute the frustum originating at the light source, that encompasses + * all the objects passed into AddShadowReceiverBound so far. + * + * This frustum can be used to determine which objects might cast a visible + * shadow. Those objects should be passed to AddShadowCasterBound and + * then should be rendered into the shadow map. + */ + CFrustum GetShadowCasterCullFrustum(); /** * BeginRender: Set OpenGL state for rendering into the shadow map texture. diff --git a/source/renderer/TerrainOverlay.cpp b/source/renderer/TerrainOverlay.cpp index c744947874..8893315f32 100644 --- a/source/renderer/TerrainOverlay.cpp +++ b/source/renderer/TerrainOverlay.cpp @@ -291,7 +291,7 @@ TerrainTextureOverlay::~TerrainTextureOverlay() glDeleteTextures(1, &m_Texture); } -void TerrainTextureOverlay::RenderAfterWater() +void TerrainTextureOverlay::RenderAfterWater(int cullGroup) { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); @@ -329,5 +329,5 @@ void TerrainTextureOverlay::RenderAfterWater() matrix._23 = m_TexelsPerTile / (m_TextureH * TERRAIN_TILE_SIZE); matrix._44 = 1; - g_Renderer.GetTerrainRenderer().RenderTerrainOverlayTexture(matrix); + g_Renderer.GetTerrainRenderer().RenderTerrainOverlayTexture(cullGroup, matrix); } diff --git a/source/renderer/TerrainOverlay.h b/source/renderer/TerrainOverlay.h index 5379ba2256..2a51753cc6 100644 --- a/source/renderer/TerrainOverlay.h +++ b/source/renderer/TerrainOverlay.h @@ -187,7 +187,7 @@ protected: virtual void BuildTextureRGBA(u8* data, size_t w, size_t h) = 0; private: - void RenderAfterWater(); + void RenderAfterWater(int cullGroup); float m_TexelsPerTile; GLuint m_Texture; diff --git a/source/renderer/TerrainRenderer.cpp b/source/renderer/TerrainRenderer.cpp index 61fd2adc5d..d483e20d0b 100644 --- a/source/renderer/TerrainRenderer.cpp +++ b/source/renderer/TerrainRenderer.cpp @@ -78,12 +78,10 @@ struct TerrainRendererInternals Phase phase; /// Patches that were submitted for this frame - std::vector visiblePatches; - std::vector filteredPatches; + std::vector visiblePatches[CRenderer::CULL_MAX]; /// Decals that were submitted for this frame - std::vector visibleDecals; - std::vector filteredDecals; + std::vector visibleDecals[CRenderer::CULL_MAX]; /// Fancy water shader CShaderProgramPtr fancyWaterShader; @@ -114,7 +112,7 @@ void TerrainRenderer::SetSimulation(CSimulation2* simulation) /////////////////////////////////////////////////////////////////// // Submit a patch for rendering -void TerrainRenderer::Submit(CPatch* patch) +void TerrainRenderer::Submit(int cullGroup, CPatch* patch) { ENSURE(m->phase == Phase_Submit); @@ -127,12 +125,12 @@ void TerrainRenderer::Submit(CPatch* patch) } data->Update(m->simulation); - m->visiblePatches.push_back(data); + m->visiblePatches[cullGroup].push_back(data); } /////////////////////////////////////////////////////////////////// // Submit a decal for rendering -void TerrainRenderer::Submit(CModelDecal* decal) +void TerrainRenderer::Submit(int cullGroup, CModelDecal* decal) { ENSURE(m->phase == Phase_Submit); @@ -145,7 +143,7 @@ void TerrainRenderer::Submit(CModelDecal* decal) } data->Update(m->simulation); - m->visibleDecals.push_back(data); + m->visibleDecals[cullGroup].push_back(data); } /////////////////////////////////////////////////////////////////// @@ -163,45 +161,27 @@ void TerrainRenderer::EndFrame() { ENSURE(m->phase == Phase_Render || m->phase == Phase_Submit); - m->visiblePatches.clear(); - m->visibleDecals.clear(); + for (int i = 0; i < CRenderer::CULL_MAX; ++i) + { + m->visiblePatches[i].clear(); + m->visibleDecals[i].clear(); + } m->phase = Phase_Submit; } -/////////////////////////////////////////////////////////////////// -// Culls patches and decals against a frustum. -bool TerrainRenderer::CullPatches(const CFrustum* frustum) -{ - m->filteredPatches.clear(); - for (std::vector::iterator it = m->visiblePatches.begin(); it != m->visiblePatches.end(); ++it) - { - if (frustum->IsBoxVisible(CVector3D(0, 0, 0), (*it)->GetPatch()->GetWorldBounds())) - m->filteredPatches.push_back(*it); - } - - m->filteredDecals.clear(); - for (std::vector::iterator it = m->visibleDecals.begin(); it != m->visibleDecals.end(); ++it) - { - if (frustum->IsBoxVisible(CVector3D(0, 0, 0), (*it)->GetDecal()->GetWorldBounds())) - m->filteredDecals.push_back(*it); - } - - return !m->filteredPatches.empty() || !m->filteredDecals.empty(); -} - /////////////////////////////////////////////////////////////////// // Full-featured terrain rendering with blending and everything -void TerrainRenderer::RenderTerrain(bool filtered) +void TerrainRenderer::RenderTerrain(int cullGroup) { #if CONFIG2_GLES - UNUSED2(filtered); + UNUSED2(cullGroup); #else ENSURE(m->phase == Phase_Render); - std::vector& visiblePatches = filtered ? m->filteredPatches : m->visiblePatches; - std::vector& visibleDecals = filtered ? m->filteredDecals : m->visibleDecals; + std::vector& visiblePatches = m->visiblePatches[cullGroup]; + std::vector& visibleDecals = m->visibleDecals[cullGroup]; if (visiblePatches.empty() && visibleDecals.empty()) return; @@ -396,15 +376,16 @@ void TerrainRenderer::RenderTerrain(bool filtered) #endif } -void TerrainRenderer::RenderTerrainOverlayTexture(CMatrix3D& textureMatrix) +void TerrainRenderer::RenderTerrainOverlayTexture(int cullGroup, CMatrix3D& textureMatrix) { #if CONFIG2_GLES #warning TODO: implement TerrainRenderer::RenderTerrainOverlayTexture for GLES + UNUSED2(cullGroup); UNUSED2(textureMatrix); #else ENSURE(m->phase == Phase_Render); - std::vector& visiblePatches = m->visiblePatches; + std::vector& visiblePatches = m->visiblePatches[cullGroup]; glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); @@ -428,9 +409,9 @@ void TerrainRenderer::RenderTerrainOverlayTexture(CMatrix3D& textureMatrix) // To make the overlay visible over water, render an additional map-sized // water-height patch CBoundingBoxAligned waterBounds; - for (size_t i = 0; i < m->visiblePatches.size(); ++i) + for (size_t i = 0; i < visiblePatches.size(); ++i) { - CPatchRData* data = m->visiblePatches[i]; + CPatchRData* data = visiblePatches[i]; waterBounds += data->GetWaterBounds(); } if (!waterBounds.IsEmpty()) @@ -493,12 +474,12 @@ void TerrainRenderer::PrepareShader(const CShaderProgramPtr& shader, ShadowMap* shader->Uniform(str_fogParams, lightEnv.m_FogFactor, lightEnv.m_FogMax, 0.f, 0.f); } -void TerrainRenderer::RenderTerrainShader(const CShaderDefines& context, ShadowMap* shadow, bool filtered) +void TerrainRenderer::RenderTerrainShader(const CShaderDefines& context, int cullGroup, ShadowMap* shadow) { ENSURE(m->phase == Phase_Render); - std::vector& visiblePatches = filtered ? m->filteredPatches : m->visiblePatches; - std::vector& visibleDecals = filtered ? m->filteredDecals : m->visibleDecals; + std::vector& visiblePatches = m->visiblePatches[cullGroup]; + std::vector& visibleDecals = m->visibleDecals[cullGroup]; if (visiblePatches.empty() && visibleDecals.empty()) return; @@ -545,11 +526,11 @@ void TerrainRenderer::RenderTerrainShader(const CShaderDefines& context, ShadowM /////////////////////////////////////////////////////////////////// // Render un-textured patches as polygons -void TerrainRenderer::RenderPatches(bool filtered) +void TerrainRenderer::RenderPatches(int cullGroup) { ENSURE(m->phase == Phase_Render); - std::vector& visiblePatches = filtered ? m->filteredPatches : m->visiblePatches; + std::vector& visiblePatches = m->visiblePatches[cullGroup]; if (visiblePatches.empty()) return; @@ -570,11 +551,11 @@ void TerrainRenderer::RenderPatches(bool filtered) /////////////////////////////////////////////////////////////////// // Render outlines of submitted patches as lines -void TerrainRenderer::RenderOutlines(bool filtered) +void TerrainRenderer::RenderOutlines(int cullGroup) { ENSURE(m->phase == Phase_Render); - std::vector& visiblePatches = filtered ? m->filteredPatches : m->visiblePatches; + std::vector& visiblePatches = m->visiblePatches[cullGroup]; if (visiblePatches.empty()) return; @@ -591,12 +572,14 @@ void TerrainRenderer::RenderOutlines(bool filtered) /////////////////////////////////////////////////////////////////// // Scissor rectangle of water patches -CBoundingBoxAligned TerrainRenderer::ScissorWater(const CMatrix3D &viewproj) +CBoundingBoxAligned TerrainRenderer::ScissorWater(int cullGroup, const CMatrix3D &viewproj) { + std::vector& visiblePatches = m->visiblePatches[cullGroup]; + CBoundingBoxAligned scissor; - for (size_t i = 0; i < m->visiblePatches.size(); ++i) + for (size_t i = 0; i < visiblePatches.size(); ++i) { - CPatchRData* data = m->visiblePatches[i]; + CPatchRData* data = visiblePatches[i]; const CBoundingBoxAligned& waterBounds = data->GetWaterBounds(); if (waterBounds.IsEmpty()) continue; @@ -642,7 +625,7 @@ CBoundingBoxAligned TerrainRenderer::ScissorWater(const CMatrix3D &viewproj) } // Render fancy water -bool TerrainRenderer::RenderFancyWater(const CShaderDefines& context, ShadowMap* shadow) +bool TerrainRenderer::RenderFancyWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow) { PROFILE3_GPU("fancy water"); @@ -868,9 +851,10 @@ bool TerrainRenderer::RenderFancyWater(const CShaderDefines& context, ShadowMap* m->fancyWaterShader->Uniform(str_shadowScale, width, height, 1.0f / width, 1.0f / height); } - for (size_t i = 0; i < m->visiblePatches.size(); ++i) + std::vector& visiblePatches = m->visiblePatches[cullGroup]; + for (size_t i = 0; i < visiblePatches.size(); ++i) { - CPatchRData* data = m->visiblePatches[i]; + CPatchRData* data = visiblePatches[i]; data->RenderWater(m->fancyWaterShader); } @@ -884,9 +868,11 @@ bool TerrainRenderer::RenderFancyWater(const CShaderDefines& context, ShadowMap* return true; } -void TerrainRenderer::RenderSimpleWater() +void TerrainRenderer::RenderSimpleWater(int cullGroup) { -#if !CONFIG2_GLES +#if CONFIG2_GLES + UNUSED2(cullGroup); +#else PROFILE3_GPU("simple water"); WaterManager* WaterMgr = g_Renderer.GetWaterManager(); @@ -959,9 +945,10 @@ void TerrainRenderer::RenderSimpleWater() glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); - for (size_t i = 0; i < m->visiblePatches.size(); ++i) + std::vector& visiblePatches = m->visiblePatches[cullGroup]; + for (size_t i = 0; i < visiblePatches.size(); ++i) { - CPatchRData* data = m->visiblePatches[i]; + CPatchRData* data = visiblePatches[i]; data->RenderWater(dummyShader); } @@ -990,17 +977,17 @@ void TerrainRenderer::RenderSimpleWater() /////////////////////////////////////////////////////////////////// // Render water that is part of the terrain -void TerrainRenderer::RenderWater(const CShaderDefines& context, ShadowMap* shadow) +void TerrainRenderer::RenderWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow) { WaterManager* WaterMgr = g_Renderer.GetWaterManager(); if (!WaterMgr->WillRenderFancyWater()) - RenderSimpleWater(); + RenderSimpleWater(cullGroup); else - RenderFancyWater(context, shadow); + RenderFancyWater(context, cullGroup, shadow); } -void TerrainRenderer::RenderPriorities() +void TerrainRenderer::RenderPriorities(int cullGroup) { PROFILE("priorities"); @@ -1013,8 +1000,9 @@ void TerrainRenderer::RenderPriorities() textRenderer.Font(CStrIntern("mono-stroke-10")); textRenderer.Color(1.0f, 1.0f, 0.0f); - for (size_t i = 0; i < m->visiblePatches.size(); ++i) - m->visiblePatches[i]->RenderPriorities(textRenderer); + std::vector& visiblePatches = m->visiblePatches[cullGroup]; + for (size_t i = 0; i < visiblePatches.size(); ++i) + visiblePatches[i]->RenderPriorities(textRenderer); textRenderer.Render(); tech->EndPass(); diff --git a/source/renderer/TerrainRenderer.h b/source/renderer/TerrainRenderer.h index 0543ee060d..d70c11e5d1 100644 --- a/source/renderer/TerrainRenderer.h +++ b/source/renderer/TerrainRenderer.h @@ -59,12 +59,12 @@ public: * * @param patch the patch */ - void Submit(CPatch* patch); + void Submit(int cullGroup, CPatch* patch); /** * Submit: Add a terrain decal for rendering in this frame. */ - void Submit(CModelDecal* decal); + void Submit(int cullGroup, CModelDecal* decal); /** * PrepareForRendering: Prepare internal data structures like vertex @@ -81,32 +81,22 @@ public: */ void EndFrame(); - /** - * CullPatches: Culls patches and decals against a frustum, - * and stores the results in a special filtered list that - * is used when calling render functions with @p filtered true. - */ - bool CullPatches(const CFrustum* frustum); - /** * RenderTerrain: Render textured terrain (including blends between * different terrain types). * * preconditions : PrepareForRendering must have been called this * frame before calling RenderTerrain. - * - * @param filtered If true then only render objects that passed CullPatches. */ - void RenderTerrain(bool filtered = false); + void RenderTerrain(int cullGroup); /** * Render textured terrain, as with RenderTerrain, but using shaders * instead of multitexturing. * * @param shadow A prepared shadow map, in case rendering with shadows is enabled. - * @param filtered If true then only render objects that passed CullPatches. */ - void RenderTerrainShader(const CShaderDefines& context, ShadowMap* shadow, bool filtered = false); + void RenderTerrainShader(const CShaderDefines& context, int cullGroup, ShadowMap* shadow); /** * RenderPatches: Render all patches un-textured as polygons. @@ -116,7 +106,7 @@ public: * * @param filtered If true then only render objects that passed CullPatches. */ - void RenderPatches(bool filtered = false); + void RenderPatches(int cullGroup); /** * RenderOutlines: Render the outline of patches as lines. @@ -126,7 +116,7 @@ public: * * @param filtered If true then only render objects that passed CullPatches. */ - void RenderOutlines(bool filtered = false); + void RenderOutlines(int cullGroup); /** * RenderWater: Render water for all patches that have been submitted @@ -135,24 +125,24 @@ public: * preconditions : PrepareForRendering must have been called this * frame before calling RenderWater. */ - void RenderWater(const CShaderDefines& context, ShadowMap* shadow); + void RenderWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow); /** * Calculate a scissor rectangle for the visible water patches. */ - CBoundingBoxAligned ScissorWater(const CMatrix3D& viewproj); + CBoundingBoxAligned ScissorWater(int cullGroup, const CMatrix3D& viewproj); /** * Render priority text for all submitted patches, for debugging. */ - void RenderPriorities(); + void RenderPriorities(int cullGroup); /** * Render texture unit 0 over the terrain mesh, with UV coords calculated * by the given texture matrix. * Intended for use by TerrainTextureOverlay. */ - void RenderTerrainOverlayTexture(CMatrix3D& textureMatrix); + void RenderTerrainOverlayTexture(int cullGroup, CMatrix3D& textureMatrix); private: TerrainRendererInternals* m; @@ -161,12 +151,12 @@ private: * RenderFancyWater: internal rendering method for fancy water. * Returns false if unable to render with fancy water. */ - bool RenderFancyWater(const CShaderDefines& context, ShadowMap* shadow); + bool RenderFancyWater(const CShaderDefines& context, int cullGroup, ShadowMap* shadow); /** * RenderSimpleWater: internal rendering method for water */ - void RenderSimpleWater(); + void RenderSimpleWater(int cullGroup); static void PrepareShader(const CShaderProgramPtr& shader, ShadowMap* shadow); }; diff --git a/source/simulation2/components/CCmpUnitRenderer.cpp b/source/simulation2/components/CCmpUnitRenderer.cpp index 200acc6fe6..3ec6c232fc 100644 --- a/source/simulation2/components/CCmpUnitRenderer.cpp +++ b/source/simulation2/components/CCmpUnitRenderer.cpp @@ -68,6 +68,13 @@ public: int flags; + /** + * m_FrameNumber from when the model's transform was last updated. + * This is used to avoid recomputing it multiple times per frame + * if a model is visible in multiple cull groups. + */ + int lastTransformFrame; + /** * Worst-case bounding shape, relative to position. Needs to account * for all possible animations, orientations, etc. @@ -107,6 +114,7 @@ public: std::vector m_Units; std::vector m_UnitTagsFree; + int m_FrameNumber; float m_FrameOffset; bool m_EnableDebugOverlays; @@ -128,6 +136,7 @@ public: virtual void Init(const CParamNode& UNUSED(paramNode)) { + m_FrameNumber = 0; m_FrameOffset = 0.0f; m_EnableDebugOverlays = false; } @@ -195,6 +204,7 @@ public: SUnit* unit = LookupUnit(tag); unit->entity = entity; unit->actor = actor; + unit->lastTransformFrame = -1; unit->flags = flags; unit->boundsApprox = boundsApprox; unit->inWorld = false; @@ -277,6 +287,7 @@ void CCmpUnitRenderer::Interpolate(float frameTime, float frameOffset) { PROFILE3("UnitRenderer::Interpolate"); + ++m_FrameNumber; m_FrameOffset = frameOffset; // TODO: we shouldn't update all the animations etc for units that are off-screen @@ -346,14 +357,20 @@ void CCmpUnitRenderer::RenderSubmit(SceneCollector& collector, const CFrustum& f unit.culled = false; - CmpPtr cmpPosition(unit.entity); - if (!cmpPosition) - continue; - - CMatrix3D transform(cmpPosition->GetInterpolatedTransform(m_FrameOffset)); - CModelAbstract& unitModel = unit.actor->GetModel(); - unitModel.SetTransform(transform); + + if (unit.lastTransformFrame != m_FrameNumber) + { + CmpPtr cmpPosition(unit.entity); + if (!cmpPosition) + continue; + + CMatrix3D transform(cmpPosition->GetInterpolatedTransform(m_FrameOffset)); + + unitModel.SetTransform(transform); + + unit.lastTransformFrame = m_FrameNumber; + } if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), unitModel.GetWorldBoundsRec())) continue;