1
0
forked from 0ad/0ad

Fix culling for shadows and reflections.

Previously we had a single culling frustum based on the main camera, and
any object outside the frustum would never get rendered, even if it
should actually contribute to shadows or reflections/refractions. This
caused ugly pop-in effects in the shadows and reflections while
scrolling.

Extend the renderer to support multiple cull groups, each with a
separate frustum and with separate lists of submitted objects, so that
shadows and reflections will render the correctly culled sets of
objects.

Update the shadow map generation to compute the (hopefully) correct
bounds and matrices for this new scheme.

Include terrain patches in the shadow bounds, so hills can cast shadows
correctly.

Remove the code that tried to render objects slightly outside the camera
frustum in order to reduce the pop-in effect, since that was a
workaround for the lack of a proper fix.

Remove the model/patch filtering code, which was used to cull objects
that were in the normal camera frustum but should be excluded from
reflections/refractions, since that's redundant now too.

Inline DistanceToPlane to save a few hundred usecs per frame inside
CCmpUnitRenderer::RenderSubmit.

Fixes #504, #579.

This was SVN commit r15445.
This commit is contained in:
Ykkrosh 2014-06-25 01:11:10 +00:00
parent bb4b911e99
commit b1b96a89d6
31 changed files with 720 additions and 509 deletions

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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; j<patchesPerSide; j++)
{
for (ssize_t i=0; i<patchesPerSide; i++)
{
CPatch* patch=pTerrain->GetPatch(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);

View File

@ -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(); \

View File

@ -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

View File

@ -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();

View File

@ -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;
};
/**

View File

@ -28,7 +28,7 @@
///////////////////////////////////////////////////////////////////////////////
// CPatch constructor
CPatch::CPatch()
: m_Parent(0), m_bWillBeDrawn(false)
: m_Parent(0)
{
}

View File

@ -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();
};

View File

@ -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)

View File

@ -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;

View File

@ -414,3 +414,77 @@ void CBrush::GetFaces(std::vector<std::vector<size_t> >& out) const
faceStartIdx = j + 1;
}
}
void CBrush::Render(CShaderProgramPtr& shader) const
{
std::vector<float> data;
std::vector<std::vector<size_t> > 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<float> data;
std::vector<std::vector<size_t> > 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);
}

View File

@ -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:
/**

View File

@ -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)

View File

@ -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

View File

@ -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
{

View File

@ -215,7 +215,7 @@ struct ShaderModelRendererInternals
ModelVertexRendererPtr vertexRenderer;
/// List of submitted models for rendering in this frame
std::vector<CModel*> submissions;
std::vector<CModel*> 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,16 +246,20 @@ 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];
model->ValidatePosition();
CModelRData* rdata = static_cast<CModelRData*>(model->GetRenderData());
ENSURE(rdata->GetKey() == m->vertexRenderer.get());
@ -263,13 +267,15 @@ void ShaderModelRenderer::PrepareModels()
m->vertexRenderer->UpdateModelData(model, rdata, rdata->m_UpdateFlags);
rdata->m_UpdateFlags = 0;
}
}
}
// 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);
}
}

View File

@ -46,15 +46,6 @@ typedef shared_ptr<ModelRenderer> 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;

View File

@ -27,14 +27,16 @@
struct ParticleRendererInternals
{
int frameNumber;
CShaderTechniquePtr shader;
CShaderTechniquePtr shaderSolid;
std::vector<CParticleEmitter*> emitters;
std::vector<CParticleEmitter*> 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<CParticleEmitter*>& 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<CParticleEmitter*>& 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);

View File

@ -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;

View File

@ -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

View File

@ -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);
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);
}
else if (m_Options.m_WaterRefraction)
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)
{
// 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)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
m->overlayRenderer.Submit(overlay);
}
void CRenderer::Submit(SOverlaySprite* overlay)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
m->overlayRenderer.Submit(overlay);
}
void CRenderer::Submit(SOverlayQuad* overlay)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
m->overlayRenderer.Submit(overlay);
}
void CRenderer::Submit(SOverlaySphere* 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;
}

View File

@ -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];

View File

@ -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());
50.0, 0.0, 50.0,
0.0, 50.0, 50.0,
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);
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();

View File

@ -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.

View File

@ -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);
}

View File

@ -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;

View File

@ -78,12 +78,10 @@ struct TerrainRendererInternals
Phase phase;
/// Patches that were submitted for this frame
std::vector<CPatchRData*> visiblePatches;
std::vector<CPatchRData*> filteredPatches;
std::vector<CPatchRData*> visiblePatches[CRenderer::CULL_MAX];
/// Decals that were submitted for this frame
std::vector<CDecalRData*> visibleDecals;
std::vector<CDecalRData*> filteredDecals;
std::vector<CDecalRData*> 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<CPatchRData*>::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<CDecalRData*>::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<CPatchRData*>& visiblePatches = filtered ? m->filteredPatches : m->visiblePatches;
std::vector<CDecalRData*>& visibleDecals = filtered ? m->filteredDecals : m->visibleDecals;
std::vector<CPatchRData*>& visiblePatches = m->visiblePatches[cullGroup];
std::vector<CDecalRData*>& 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<CPatchRData*>& visiblePatches = m->visiblePatches;
std::vector<CPatchRData*>& 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<CPatchRData*>& visiblePatches = filtered ? m->filteredPatches : m->visiblePatches;
std::vector<CDecalRData*>& visibleDecals = filtered ? m->filteredDecals : m->visibleDecals;
std::vector<CPatchRData*>& visiblePatches = m->visiblePatches[cullGroup];
std::vector<CDecalRData*>& 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<CPatchRData*>& visiblePatches = filtered ? m->filteredPatches : m->visiblePatches;
std::vector<CPatchRData*>& 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<CPatchRData*>& visiblePatches = filtered ? m->filteredPatches : m->visiblePatches;
std::vector<CPatchRData*>& 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<CPatchRData*>& 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<CPatchRData*>& 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<CPatchRData*>& 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<CPatchRData*>& visiblePatches = m->visiblePatches[cullGroup];
for (size_t i = 0; i < visiblePatches.size(); ++i)
visiblePatches[i]->RenderPriorities(textRenderer);
textRenderer.Render();
tech->EndPass();

View File

@ -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);
};

View File

@ -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<SUnit> m_Units;
std::vector<tag_t> 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,15 +357,21 @@ void CCmpUnitRenderer::RenderSubmit(SceneCollector& collector, const CFrustum& f
unit.culled = false;
CModelAbstract& unitModel = unit.actor->GetModel();
if (unit.lastTransformFrame != m_FrameNumber)
{
CmpPtr<ICmpPosition> cmpPosition(unit.entity);
if (!cmpPosition)
continue;
CMatrix3D transform(cmpPosition->GetInterpolatedTransform(m_FrameOffset));
CModelAbstract& unitModel = unit.actor->GetModel();
unitModel.SetTransform(transform);
unit.lastTransformFrame = m_FrameNumber;
}
if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), unitModel.GetWorldBoundsRec()))
continue;