Optimise silhouette rendering.

Do some intersection tests on the CPU so that the silhouette render
passes only have to draw models/patches that might actually contribute
to silhouettes, saving the CPU and GPU cost of rendering more objects
than necessary.

This was SVN commit r15483.
This commit is contained in:
Ykkrosh 2014-07-03 01:00:25 +00:00
parent ffd6e10edf
commit eb7955599a
11 changed files with 779 additions and 47 deletions

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2011 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -22,8 +22,11 @@
#include "precompiled.h"
#include "HFTracer.h"
#include "Terrain.h"
#include "graphics/Patch.h"
#include "graphics/Terrain.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/MathUtil.h"
#include "maths/Vector3D.h"
// To cope well with points that are slightly off the edge of the map,
@ -50,8 +53,8 @@ CHFTracer::CHFTracer(CTerrain *pTerrain):
// RayTriIntersect: intersect a ray with triangle defined by vertices
// v0,v1,v2; return true if ray hits triangle at distance less than dist,
// or false otherwise
bool CHFTracer::RayTriIntersect(const CVector3D& v0, const CVector3D& v1, const CVector3D& v2,
const CVector3D& origin, const CVector3D& dir, float& dist) const
static bool RayTriIntersect(const CVector3D& v0, const CVector3D& v1, const CVector3D& v2,
const CVector3D& origin, const CVector3D& dir, float& dist)
{
const float EPSILON=0.00001f;
@ -227,3 +230,129 @@ bool CHFTracer::RayIntersect(const CVector3D& origin, const CVector3D& dir, int&
// fell off end of heightmap with no intersection; return a miss
return false;
}
static bool TestTile(u16* heightmap, int stride, int i, int j, const CVector3D& pos, const CVector3D& dir, CVector3D& isct)
{
u16 y00 = heightmap[i + j*stride];
u16 y10 = heightmap[i+1 + j*stride];
u16 y01 = heightmap[i + (j+1)*stride];
u16 y11 = heightmap[i+1 + (j+1)*stride];
CVector3D p00( i * TERRAIN_TILE_SIZE, y00 * HEIGHT_SCALE, j * TERRAIN_TILE_SIZE);
CVector3D p10((i+1) * TERRAIN_TILE_SIZE, y10 * HEIGHT_SCALE, j * TERRAIN_TILE_SIZE);
CVector3D p01( i * TERRAIN_TILE_SIZE, y01 * HEIGHT_SCALE, (j+1) * TERRAIN_TILE_SIZE);
CVector3D p11((i+1) * TERRAIN_TILE_SIZE, y11 * HEIGHT_SCALE, (j+1) * TERRAIN_TILE_SIZE);
int mid1 = y00+y11;
int mid2 = y01+y10;
int triDir = (mid1 < mid2);
float dist = FLT_MAX;
if (triDir)
{
if (RayTriIntersect(p00, p10, p01, pos, dir, dist) || // lower-left triangle
RayTriIntersect(p11, p01, p10, pos, dir, dist)) // upper-right triangle
{
isct = pos + dir * dist;
return true;
}
}
else
{
if (RayTriIntersect(p00, p11, p01, pos, dir, dist) || // upper-left triangle
RayTriIntersect(p00, p10, p11, pos, dir, dist)) // lower-right triangle
{
isct = pos + dir * dist;
return true;
}
}
return false;
}
bool CHFTracer::PatchRayIntersect(CPatch* patch, const CVector3D& origin, const CVector3D& dir, CVector3D* out)
{
// (TODO: This largely duplicates RayIntersect - some refactoring might be
// nice in the future.)
// General approach:
// Given the ray defined by origin + dir * t, we increase t until it
// enters the patch's bounding box. The x,z coordinates identify which
// tile it is currently above/below. Do an intersection test vs the tile's
// two triangles. If it doesn't hit, do a 2D line rasterisation to find
// the next tiles the ray will pass through, and test each of them.
// Start by jumping to the point where the ray enters the bounding box
CBoundingBoxAligned bound = patch->GetWorldBounds();
float tmin, tmax;
if (!bound.RayIntersect(origin, dir, tmin, tmax))
{
// Ray missed patch; no intersection
return false;
}
int heightmapStride = patch->m_Parent->GetVerticesPerSide();
// Get heightmap, offset to start at this patch
u16* heightmap = patch->m_Parent->GetHeightMap() +
patch->m_X * PATCH_SIZE +
patch->m_Z * PATCH_SIZE * heightmapStride;
// Get patch-space position of ray origin and bbox entry point
CVector3D patchPos(
patch->m_X * PATCH_SIZE * TERRAIN_TILE_SIZE,
0.0f,
patch->m_Z * PATCH_SIZE * TERRAIN_TILE_SIZE);
CVector3D originPatch = origin - patchPos;
CVector3D entryPatch = originPatch + dir * tmin;
// We want to do a simple 2D line rasterisation (with the 3D ray projected
// down onto the Y plane). That will tell us which cells are intersected
// in 2D dimensions, then we can do a more precise 3D intersection test.
//
// WLOG, assume the ray has direction dir.x > 0, dir.z > 0, and starts in
// cell (i,j). The next cell intersecting the line must be either (i+1,j)
// or (i,j+1). To tell which, just check whether the point (i+1,j+1) is
// above or below the ray. Advance into that cell and repeat.
//
// (If the ray passes precisely through (i+1,j+1), we can pick either.
// If the ray is parallel to Y, only the first cell matters, then we can
// carry on rasterising in any direction (a bit of a waste of time but
// should be extremely rare, and it's safe and simple).)
// Work out which tile we're starting in
int i = clamp((int)(entryPatch.X / TERRAIN_TILE_SIZE), 0, (int)PATCH_SIZE-1);
int j = clamp((int)(entryPatch.Z / TERRAIN_TILE_SIZE), 0, (int)PATCH_SIZE-1);
// Work out which direction the ray is going in
int di = (dir.X >= 0 ? 1 : 0);
int dj = (dir.Z >= 0 ? 1 : 0);
do
{
CVector3D isct;
if (TestTile(heightmap, heightmapStride, i, j, originPatch, dir, isct))
{
if (out)
*out = isct + patchPos;
return true;
}
// Get the vertex between the two possible next cells
float nx = (i + di) * (int)TERRAIN_TILE_SIZE;
float nz = (j + dj) * (int)TERRAIN_TILE_SIZE;
// Test which side of the ray the vertex is on, and advance into the
// appropriate cell, using a test that works for all 4 combinations
// of di,dj
float dot = dir.Z * (nx - originPatch.X) - dir.X * (nz - originPatch.Z);
if ((di == dj) == (dot > 0.0f))
j += dj*2-1;
else
i += di*2-1;
}
while (i >= 0 && j >= 0 && i < PATCH_SIZE && j < PATCH_SIZE);
// Ran off the edge of the patch, so no intersection
return false;
}

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2009 Wildfire Games.
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -22,6 +22,7 @@
#ifndef INCLUDED_HFTRACER
#define INCLUDED_HFTRACER
class CPatch;
class CVector3D;
class CTerrain;
@ -37,13 +38,24 @@ public:
// occurs (and fill in grid coordinates and point of intersection), or false otherwise
bool RayIntersect(const CVector3D& origin, const CVector3D& dir, int& x, int& z, CVector3D& ipt) const;
private:
// intersect a ray with triangle defined by vertices
// v0,v1,v2; return true if ray hits triangle at distance less than dist,
// or false otherwise
bool RayTriIntersect(const CVector3D& v0, const CVector3D& v1, const CVector3D& v2,
const CVector3D& origin, const CVector3D& dir, float& dist) const;
/**
* Intersects ray with a single patch.
* The ray is a half-infinite line starting at @p origin with direction @p dir
* (not required to be a unit vector).. The patch is treated as a collection
* of two-sided triangles, corresponding to the terrain tiles.
*
* If there is an intersection, returns true; and if @p out is not NULL, it
* is set to the intersection point. This is guaranteed to be the earliest
* tile intersected (starting at @p origin), but not necessarily the earlier
* triangle inside that tile.
*
* This partly duplicates RayIntersect, but it only operates on a single
* patch, and it's more precise (it uses the same tile triangulation as the
* renderer), and tries to be more numerically robust.
*/
static bool PatchRayIntersect(CPatch* patch, const CVector3D& origin, const CVector3D& dir, CVector3D* out);
private:
// test if ray intersects either of the triangles in the given
bool CellIntersect(int cx, int cz, const CVector3D& origin, const CVector3D& dir, float& dist) const;

View File

@ -18,7 +18,6 @@
#ifndef INCLUDED_GRAPHICS_OVERLAY
#define INCLUDED_GRAPHICS_OVERLAY
#include "graphics/RenderableObject.h"
#include "graphics/Texture.h"
#include "maths/Vector2D.h"
#include "maths/Vector3D.h"

View File

@ -18,6 +18,7 @@
#ifndef INCLUDED_SHADERPROGRAM
#define INCLUDED_SHADERPROGRAM
#include "graphics/ShaderProgramPtr.h"
#include "graphics/Texture.h"
#include "lib/ogl.h"
#include "lib/file/vfs/vfs_path.h"
@ -204,6 +205,4 @@ protected:
int m_ValidStreams; // which streams have been specified via VertexPointer etc since the last Bind
};
typedef shared_ptr<CShaderProgram> CShaderProgramPtr;
#endif // INCLUDED_SHADERPROGRAM

View File

@ -24,7 +24,7 @@
// necessary includes
#include "Vector3D.h"
#include "graphics/ShaderProgram.h"
#include "graphics/ShaderProgramPtr.h"
class CFrustum;
class CMatrix3D;

View File

@ -45,25 +45,6 @@ int CVector3D::operator ! () const
return 1;
}
float CVector3D::Dot (const CVector3D &vector) const
{
return ( X * vector.X +
Y * vector.Y +
Z * vector.Z );
}
CVector3D CVector3D::Cross (const CVector3D &vector) const
{
CVector3D Temp;
Temp.X = (Y * vector.Z) - (Z * vector.Y);
Temp.Y = (Z * vector.X) - (X * vector.Z);
Temp.Z = (X * vector.Y) - (Y * vector.X);
return Temp;
}
float CVector3D::LengthSquared () const
{
return ( SQR(X) + SQR(Y) + SQR(Z) );

View File

@ -96,8 +96,21 @@ class CVector3D
}
public:
float Dot (const CVector3D &vector) const;
CVector3D Cross (const CVector3D &vector) const;
float Dot (const CVector3D &vector) const
{
return ( X * vector.X +
Y * vector.Y +
Z * vector.Z );
}
CVector3D Cross (const CVector3D &vector) const
{
CVector3D Temp;
Temp.X = (Y * vector.Z) - (Z * vector.Y);
Temp.Y = (Z * vector.X) - (X * vector.Z);
Temp.Z = (X * vector.Y) - (Y * vector.X);
return Temp;
}
float Length () const;
float LengthSquared () const;

View File

@ -65,6 +65,7 @@
#include "renderer/ParticleRenderer.h"
#include "renderer/RenderModifiers.h"
#include "renderer/ShadowMap.h"
#include "renderer/SilhouetteRenderer.h"
#include "renderer/SkyManager.h"
#include "renderer/TerrainOverlay.h"
#include "renderer/TerrainRenderer.h"
@ -293,6 +294,8 @@ public:
CFontManager fontManager;
SilhouetteRenderer silhouetteRenderer;
/// Various model renderers
struct Models
{
@ -1300,7 +1303,6 @@ SScreenRect CRenderer::RenderRefractions(const CShaderDefines& context, const CB
return screenScissor;
}
void CRenderer::RenderSilhouettes(const CShaderDefines& context)
{
PROFILE3_GPU("silhouettes");
@ -1338,18 +1340,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(CULL_DEFAULT);
m->terrainRenderer.RenderPatches(CULL_SILHOUETTE_OCCLUDER);
glCullFace(GL_BACK);
}
{
PROFILE("render model occluders");
m->CallModelRenderers(contextOccluder, CULL_DEFAULT, MODELFLAG_SILHOUETTE_OCCLUDER);
m->CallModelRenderers(contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0);
}
{
PROFILE("render transparent occluders");
m->CallTranspModelRenderers(contextOccluder, CULL_DEFAULT, MODELFLAG_SILHOUETTE_OCCLUDER);
m->CallTranspModelRenderers(contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0);
}
glDepthFunc(GL_GEQUAL);
@ -1377,14 +1379,10 @@ void CRenderer::RenderSilhouettes(const CShaderDefines& context)
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
}
// TODO: For performance, we probably ought to do a quick raycasting check
// to see which units are likely blocked by occluders and not bother
// rendering any of the others
{
PROFILE("render models");
m->CallModelRenderers(contextDisplay, CULL_DEFAULT, MODELFLAG_SILHOUETTE_DISPLAY);
// (This won't render transparent objects with SILHOUETTE_DISPLAY - will
PROFILE("render casters");
m->CallModelRenderers(contextDisplay, CULL_SILHOUETTE_CASTER, 0);
// (This won't render transparent objects with SILHOUETTE_CASTER - will
// we have any units that need that?)
}
@ -1606,6 +1604,8 @@ void CRenderer::RenderSubmissions(const CBoundingBoxAligned& waterScissor)
ogl_WarnIfError();
}
m->silhouetteRenderer.RenderDebugOverlays(m_ViewCamera);
// render overlays that should appear on top of all other objects
m->overlayRenderer.RenderForegroundOverlays(m_ViewCamera);
ogl_WarnIfError();
@ -1622,6 +1622,7 @@ void CRenderer::EndFrame()
m->terrainRenderer.EndFrame();
m->overlayRenderer.EndFrame();
m->particleRenderer.EndFrame();
m->silhouetteRenderer.EndFrame();
// Finish model renderers
m->Model.NormalSkinned->EndFrame();
@ -1712,10 +1713,15 @@ SViewPort CRenderer::GetViewport()
void CRenderer::Submit(CPatch* patch)
{
if (m_CurrentCullGroup == CULL_DEFAULT)
{
m->shadow.AddShadowReceiverBound(patch->GetWorldBounds());
m->silhouetteRenderer.AddOccluder(patch);
}
if (m_CurrentCullGroup == CULL_SHADOWS)
{
m->shadow.AddShadowCasterBound(patch->GetWorldBounds());
}
m->terrainRenderer.Submit(m_CurrentCullGroup, patch);
}
@ -1772,6 +1778,11 @@ void CRenderer::SubmitNonRecursive(CModel* model)
if (m_CurrentCullGroup == CULL_DEFAULT)
{
m->shadow.AddShadowReceiverBound(model->GetWorldBounds());
if (model->GetFlags() & MODELFLAG_SILHOUETTE_OCCLUDER)
m->silhouetteRenderer.AddOccluder(model);
if (model->GetFlags() & MODELFLAG_SILHOUETTE_DISPLAY)
m->silhouetteRenderer.AddCaster(model);
}
if (m_CurrentCullGroup == CULL_SHADOWS)
@ -1815,6 +1826,20 @@ void CRenderer::RenderScene(Scene& scene)
m->particleManager.RenderSubmit(*this, frustum);
if (m_Options.m_Silhouettes)
{
m->silhouetteRenderer.ComputeSubmissions(m_ViewCamera);
m_CurrentCullGroup = CULL_DEFAULT;
m->silhouetteRenderer.RenderSubmitOverlays(*this);
m_CurrentCullGroup = CULL_SILHOUETTE_OCCLUDER;
m->silhouetteRenderer.RenderSubmitOccluders(*this);
m_CurrentCullGroup = CULL_SILHOUETTE_CASTER;
m->silhouetteRenderer.RenderSubmitCasters(*this);
}
if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER)
{
m_CurrentCullGroup = CULL_SHADOWS;

View File

@ -102,6 +102,8 @@ public:
CULL_SHADOWS,
CULL_REFLECTIONS,
CULL_REFRACTIONS,
CULL_SILHOUETTE_OCCLUDER,
CULL_SILHOUETTE_CASTER,
CULL_MAX
};

View File

@ -0,0 +1,495 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#include "precompiled.h"
#include "SilhouetteRenderer.h"
#include "graphics/Camera.h"
#include "graphics/HFTracer.h"
#include "graphics/Model.h"
#include "graphics/Patch.h"
#include "graphics/ShaderManager.h"
#include "maths/MathUtil.h"
#include "ps/Profile.h"
#include "renderer/Renderer.h"
#include "renderer/Scene.h"
// For debugging
static const bool g_DisablePreciseIntersections = false;
SilhouetteRenderer::SilhouetteRenderer()
{
m_DebugEnabled = false;
}
void SilhouetteRenderer::AddOccluder(CPatch* patch)
{
m_SubmittedPatchOccluders.push_back(patch);
}
void SilhouetteRenderer::AddOccluder(CModel* model)
{
m_SubmittedModelOccluders.push_back(model);
}
void SilhouetteRenderer::AddCaster(CModel* model)
{
m_SubmittedModelCasters.push_back(model);
}
/*
* Silhouettes are the solid-coloured versions of units that are rendered when
* standing behind a building or terrain, so the player won't lose them.
*
* The rendering is done in CRenderer::RenderSilhouettes, by rendering the
* units (silhouette casters) and buildings/terrain (silhouette occluders)
* in an extra pass using depth and stencil buffers. It's very inefficient to
* render those objects when they're not actually going to contribute to a
* silhouette.
*
* This class is responsible for finding the subset of casters/occluders
* that might contribute to a silhouette and will need to be rendered.
*
* The algorithm is largely based on sweep-and-prune for detecting intersection
* along a single axis:
*
* First we compute the 2D screen-space bounding box of every occluder, and
* their minimum distance from the camera. We also compute the screen-space
* position of each caster (approximating them as points, which is not perfect
* but almost always good enough).
*
* We split each occluder's screen-space bounds into a left ('in') edge and
* right ('out') edge. We put those edges plus the caster points into a list,
* and sort by x coordinate.
*
* Then we walk through the list, maintaining an active set of occluders.
* An 'in' edge will add an occluder to the set, an 'out' edge will remove it.
* When we reach a caster point, the active set contains all the occluders that
* intersect it in x. We do a quick test of y and depth coordinates against
* each occluder in the set. If they pass that test, we do a more precise ray
* vs bounding box test (for model occluders) or ray vs patch (for terrain
* occluders) to see if we really need to render that caster and occluder.
*
* Performance relies on the active set being quite small. Given the game's
* typical occluder sizes and camera angles, this works out okay.
*
* We have to do precise ray/patch intersection tests for terrain, because
* if we just used the patch's bounding box, pretty much every unit would
* be seen as intersecting the patch it's standing on.
*
* We store screen-space coordinates as 14-bit integers (0..16383) because
* that lets us pack and sort the edge/point list efficiently.
*/
static const int MAX_COORD = 16384;
struct Occluder
{
CRenderableObject* renderable;
bool isPatch;
u16 x0, y0, x1, y1;
float z;
bool rendered;
};
struct Caster
{
CModel* model;
u16 x, y;
float z;
bool rendered;
};
enum { EDGE_IN, EDGE_OUT, POINT };
// Entry is essentially:
// struct Entry {
// u16 id; // index into occluders array
// u16 type : 2;
// u16 x : 14;
// };
// where x is in the most significant bits, so that sorting as a uint32_t
// is the same as sorting by x. To avoid worrying about endianness and the
// compiler's ability to handle bitfields efficiently, we use uint32_t instead
// of the actual struct.
typedef uint32_t Entry;
static Entry EntryCreate(int type, u16 id, u16 x) { return (x << 18) | (type << 16) | id; }
static int EntryGetId(Entry e) { return e & 0xffff; }
static int EntryGetType(Entry e) { return (e >> 16) & 3; }
struct ActiveList
{
std::vector<u16> m_Ids;
void Add(u16 id)
{
m_Ids.push_back(id);
}
void Remove(u16 id)
{
ssize_t sz = m_Ids.size();
for (ssize_t i = sz-1; i >= 0; --i)
{
if (m_Ids[i] == id)
{
m_Ids[i] = m_Ids[sz-1];
m_Ids.pop_back();
return;
}
}
debug_warn(L"Failed to find id");
}
};
static void ComputeScreenBounds(Occluder& occluder, const CBoundingBoxAligned& bounds, CMatrix3D& proj)
{
int x0 = INT_MAX, y0 = INT_MAX, x1 = INT_MIN, y1 = INT_MIN;
float z0 = FLT_MAX;
for (size_t ix = 0; ix <= 1; ix++)
{
for (size_t iy = 0; iy <= 1; iy++)
{
for (size_t iz = 0; iz <= 1; iz++)
{
CVector4D vec(bounds[ix].X, bounds[iy].Y, bounds[iz].Z, 1.0f);
CVector4D svec = proj.Transform(vec);
x0 = std::min(x0, MAX_COORD/2 + (int)(MAX_COORD/2 * svec.X / svec.W));
y0 = std::min(y0, MAX_COORD/2 + (int)(MAX_COORD/2 * svec.Y / svec.W));
x1 = std::max(x1, MAX_COORD/2 + (int)(MAX_COORD/2 * svec.X / svec.W));
y1 = std::max(y1, MAX_COORD/2 + (int)(MAX_COORD/2 * svec.Y / svec.W));
z0 = std::min(z0, svec.Z / svec.W);
}
}
}
// TODO: there must be a quicker way to do this than to test every vertex,
// given the symmetry of the bounding box
occluder.x0 = clamp(x0, 0, MAX_COORD-1);
occluder.y0 = clamp(y0, 0, MAX_COORD-1);
occluder.x1 = clamp(x1, 0, MAX_COORD-1);
occluder.y1 = clamp(y1, 0, MAX_COORD-1);
occluder.z = z0;
}
static void ComputeScreenPos(Caster& caster, const CVector3D& pos, CMatrix3D& proj)
{
CVector4D vec(pos.X, pos.Y, pos.Z, 1.0f);
CVector4D svec = proj.Transform(vec);
int x = MAX_COORD/2 + (int)(MAX_COORD/2 * svec.X / svec.W);
int y = MAX_COORD/2 + (int)(MAX_COORD/2 * svec.Y / svec.W);
float z = svec.Z / svec.W;
caster.x = clamp(x, 0, MAX_COORD-1);
caster.y = clamp(y, 0, MAX_COORD-1);
caster.z = z;
}
void SilhouetteRenderer::ComputeSubmissions(const CCamera& camera)
{
PROFILE3("compute silhouettes");
m_DebugBounds.clear();
m_DebugRects.clear();
m_DebugSpheres.clear();
m_VisiblePatchOccluders.clear();
m_VisibleModelOccluders.clear();
m_VisibleModelCasters.clear();
std::vector<Occluder> occluders;
std::vector<Caster> casters;
std::vector<Entry> entries;
occluders.reserve(m_SubmittedModelOccluders.size() + m_SubmittedPatchOccluders.size());
casters.reserve(m_SubmittedModelCasters.size());
entries.reserve((m_SubmittedModelOccluders.size() + m_SubmittedPatchOccluders.size()) * 2 + m_SubmittedModelCasters.size());
CMatrix3D proj = camera.GetViewProjection();
// Bump the positions of unit casters upwards a bit, so they're not always
// detected as intersecting the terrain they're standing on
CVector3D posOffset(0.0f, 0.1f, 0.0f);
#if 0
// For debugging ray-patch intersections - casts a ton of rays and draws
// a sphere where they intersect
extern int g_xres, g_yres;
for (int y = 0; y < g_yres; y += 8)
{
for (int x = 0; x < g_xres; x += 8)
{
SOverlaySphere sphere;
sphere.m_Color = CColor(1, 0, 0, 1);
sphere.m_Radius = 0.25f;
sphere.m_Center = camera.GetWorldCoordinates(x, y, false);
CVector3D origin, dir;
camera.BuildCameraRay(x, y, origin, dir);
for (size_t i = 0; i < m_SubmittedPatchOccluders.size(); ++i)
{
CPatch* occluder = m_SubmittedPatchOccluders[i];
if (CHFTracer::PatchRayIntersect(occluder, origin, dir, &sphere.m_Center))
sphere.m_Color = CColor(0, 0, 1, 1);
}
m_DebugSpheres.push_back(sphere);
}
}
#endif
{
PROFILE("compute bounds");
for (size_t i = 0; i < m_SubmittedModelOccluders.size(); ++i)
{
CModel* occluder = m_SubmittedModelOccluders[i];
Occluder d;
d.renderable = occluder;
d.isPatch = false;
d.rendered = false;
ComputeScreenBounds(d, occluder->GetWorldBounds(), proj);
// Skip zero-sized occluders, so we don't need to worry about EDGE_OUT
// getting sorted before EDGE_IN
if (d.x0 == d.x1 || d.y0 == d.y1)
continue;
size_t id = occluders.size();
occluders.push_back(d);
entries.push_back(EntryCreate(EDGE_IN, id, d.x0));
entries.push_back(EntryCreate(EDGE_OUT, id, d.x1));
}
for (size_t i = 0; i < m_SubmittedPatchOccluders.size(); ++i)
{
CPatch* occluder = m_SubmittedPatchOccluders[i];
Occluder d;
d.renderable = occluder;
d.isPatch = true;
d.rendered = false;
ComputeScreenBounds(d, occluder->GetWorldBounds(), proj);
// Skip zero-sized occluders
if (d.x0 == d.x1 || d.y0 == d.y1)
continue;
size_t id = occluders.size();
occluders.push_back(d);
entries.push_back(EntryCreate(EDGE_IN, id, d.x0));
entries.push_back(EntryCreate(EDGE_OUT, id, d.x1));
}
for (size_t i = 0; i < m_SubmittedModelCasters.size(); ++i)
{
CModel* model = m_SubmittedModelCasters[i];
CVector3D pos = model->GetTransform().GetTranslation() + posOffset;
Caster d;
d.model = model;
d.rendered = false;
ComputeScreenPos(d, pos, proj);
size_t id = casters.size();
casters.push_back(d);
entries.push_back(EntryCreate(POINT, id, d.x));
}
}
// Make sure the u16 id didn't overflow
ENSURE(occluders.size() < 65536 && casters.size() < 65536);
{
PROFILE("sorting");
std::sort(entries.begin(), entries.end());
}
{
PROFILE("sweeping");
ActiveList active;
CVector3D cameraPos = camera.GetOrientation().GetTranslation();
for (size_t i = 0; i < entries.size(); ++i)
{
Entry e = entries[i];
int type = EntryGetType(e);
u16 id = EntryGetId(e);
if (type == EDGE_IN)
active.Add(id);
else if (type == EDGE_OUT)
active.Remove(id);
else
{
Caster& caster = casters[id];
for (size_t j = 0; j < active.m_Ids.size(); ++j)
{
Occluder& occluder = occluders[active.m_Ids[j]];
if (caster.y < occluder.y0 || caster.y > occluder.y1)
continue;
if (caster.z < occluder.z)
continue;
// No point checking further if both are already being rendered
if (caster.rendered && occluder.rendered)
continue;
if (!g_DisablePreciseIntersections)
{
CVector3D pos = caster.model->GetTransform().GetTranslation() + posOffset;
if (occluder.isPatch)
{
CPatch* patch = static_cast<CPatch*>(occluder.renderable);
if (!CHFTracer::PatchRayIntersect(patch, pos, cameraPos - pos, NULL))
continue;
}
else
{
float tmin, tmax;
if (!occluder.renderable->GetWorldBounds().RayIntersect(pos, cameraPos - pos, tmin, tmax))
continue;
}
}
caster.rendered = true;
occluder.rendered = true;
}
}
}
}
if (m_DebugEnabled)
{
for (size_t i = 0; i < occluders.size(); ++i)
{
DebugRect r;
r.color = occluders[i].rendered ? CColor(1.0f, 1.0f, 0.0f, 1.0f) : CColor(0.2f, 0.2f, 0.0f, 1.0f);
r.x0 = occluders[i].x0;
r.y0 = occluders[i].y0;
r.x1 = occluders[i].x1;
r.y1 = occluders[i].y1;
m_DebugRects.push_back(r);
DebugBounds b;
b.color = r.color;
b.bounds = occluders[i].renderable->GetWorldBounds();
m_DebugBounds.push_back(b);
}
}
for (size_t i = 0; i < occluders.size(); ++i)
{
if (occluders[i].rendered)
{
if (occluders[i].isPatch)
m_VisiblePatchOccluders.push_back(static_cast<CPatch*>(occluders[i].renderable));
else
m_VisibleModelOccluders.push_back(static_cast<CModel*>(occluders[i].renderable));
}
}
for (size_t i = 0; i < casters.size(); ++i)
if (casters[i].rendered)
m_VisibleModelCasters.push_back(casters[i].model);
}
void SilhouetteRenderer::RenderSubmitOverlays(SceneCollector& collector)
{
for (size_t i = 0; i < m_DebugSpheres.size(); i++)
collector.Submit(&m_DebugSpheres[i]);
}
void SilhouetteRenderer::RenderSubmitOccluders(SceneCollector& collector)
{
for (size_t i = 0; i < m_VisiblePatchOccluders.size(); ++i)
collector.Submit(m_VisiblePatchOccluders[i]);
for (size_t i = 0; i < m_VisibleModelOccluders.size(); ++i)
collector.SubmitNonRecursive(m_VisibleModelOccluders[i]);
}
void SilhouetteRenderer::RenderSubmitCasters(SceneCollector& collector)
{
for (size_t i = 0; i < m_VisibleModelCasters.size(); ++i)
collector.SubmitNonRecursive(m_VisibleModelCasters[i]);
}
void SilhouetteRenderer::RenderDebugOverlays(const CCamera& camera)
{
CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
shaderTech->BeginPass();
CShaderProgramPtr shader = shaderTech->GetShader();
glDepthMask(0);
glDisable(GL_CULL_FACE);
shader->Uniform(str_transform, camera.GetViewProjection());
for (size_t i = 0; i < m_DebugBounds.size(); ++i)
{
shader->Uniform(str_color, m_DebugBounds[i].color);
m_DebugBounds[i].bounds.RenderOutline(shader);
}
CMatrix3D m;
m.SetIdentity();
m.Scale(1.0f, -1.f, 1.0f);
m.Translate(0.0f, (float)g_yres, -1000.0f);
CMatrix3D proj;
proj.SetOrtho(0.f, MAX_COORD, 0.f, MAX_COORD, -1.f, 1000.f);
m = proj * m;
shader->Uniform(str_transform, proj);
for (size_t i = 0; i < m_DebugRects.size(); ++i)
{
const DebugRect& r = m_DebugRects[i];
shader->Uniform(str_color, r.color);
u16 verts[] = {
r.x0, r.y0,
r.x1, r.y0,
r.x1, r.y1,
r.x0, r.y1,
r.x0, r.y0,
};
shader->VertexPointer(2, GL_SHORT, 0, verts);
glDrawArrays(GL_LINE_STRIP, 0, 5);
}
shaderTech->EndPass();
glEnable(GL_CULL_FACE);
glDepthMask(1);
}
void SilhouetteRenderer::EndFrame()
{
m_SubmittedPatchOccluders.clear();
m_SubmittedModelOccluders.clear();
m_SubmittedModelCasters.clear();
}

View File

@ -0,0 +1,77 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INCLUDED_SILHOUETTERENDERER
#define INCLUDED_SILHOUETTERENDERER
#include "ps/Overlay.h"
#include "graphics/Overlay.h"
#include "maths/BoundingBoxAligned.h"
class CCamera;
class CModel;
class CPatch;
class SceneCollector;
class SilhouetteRenderer
{
public:
SilhouetteRenderer();
void AddOccluder(CPatch* patch);
void AddOccluder(CModel* model);
void AddCaster(CModel* model);
void ComputeSubmissions(const CCamera& camera);
void RenderSubmitOverlays(SceneCollector& collector);
void RenderSubmitOccluders(SceneCollector& collector);
void RenderSubmitCasters(SceneCollector& collector);
void RenderDebugOverlays(const CCamera& camera);
void EndFrame();
private:
bool m_DebugEnabled;
std::vector<CPatch*> m_SubmittedPatchOccluders;
std::vector<CModel*> m_SubmittedModelOccluders;
std::vector<CModel*> m_SubmittedModelCasters;
std::vector<CPatch*> m_VisiblePatchOccluders;
std::vector<CModel*> m_VisibleModelOccluders;
std::vector<CModel*> m_VisibleModelCasters;
struct DebugBounds
{
CColor color;
CBoundingBoxAligned bounds;
};
struct DebugRect
{
CColor color;
u16 x0, y0, x1, y1;
};
std::vector<DebugBounds> m_DebugBounds;
std::vector<DebugRect> m_DebugRects;
std::vector<SOverlaySphere> m_DebugSpheres;
};
#endif // INCLUDED_SILHOUETTERENDERER