forked from 0ad/0ad
Ykkrosh
b1b96a89d6
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.
491 lines
16 KiB
C++
491 lines
16 KiB
C++
/* Copyright (C) 2012 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/>.
|
|
*/
|
|
|
|
/*
|
|
* Implementation of CBrush, a class representing a convex object
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include "lib/ogl.h"
|
|
|
|
#include <float.h>
|
|
|
|
#include "Brush.h"
|
|
#include "BoundingBoxAligned.h"
|
|
#include "graphics/Frustum.h"
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Convert the given bounds into a brush
|
|
CBrush::CBrush(const CBoundingBoxAligned& bounds)
|
|
{
|
|
m_Vertices.resize(8);
|
|
|
|
for(size_t i = 0; i < 8; ++i)
|
|
{
|
|
m_Vertices[i][0] = bounds[(i & 1) ? 1 : 0][0]; // X
|
|
m_Vertices[i][1] = bounds[(i & 2) ? 1 : 0][1]; // Y
|
|
m_Vertices[i][2] = bounds[(i & 4) ? 1 : 0][2]; // Z
|
|
}
|
|
|
|
// construct cube face indices, 5 vertex indices per face (start vertex included twice)
|
|
|
|
m_Faces.resize(30);
|
|
|
|
m_Faces[0] = 0; m_Faces[1] = 1; m_Faces[2] = 3; m_Faces[3] = 2; m_Faces[4] = 0; // Z = min
|
|
m_Faces[5] = 4; m_Faces[6] = 5; m_Faces[7] = 7; m_Faces[8] = 6; m_Faces[9] = 4; // Z = max
|
|
|
|
m_Faces[10] = 0; m_Faces[11] = 2; m_Faces[12] = 6; m_Faces[13] = 4; m_Faces[14] = 0; // X = min
|
|
m_Faces[15] = 1; m_Faces[16] = 3; m_Faces[17] = 7; m_Faces[18] = 5; m_Faces[19] = 1; // X = max
|
|
|
|
m_Faces[20] = 0; m_Faces[21] = 1; m_Faces[22] = 5; m_Faces[23] = 4; m_Faces[24] = 0; // Y = min
|
|
m_Faces[25] = 2; m_Faces[26] = 3; m_Faces[27] = 7; m_Faces[28] = 6; m_Faces[29] = 2; // Y = max
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Calculate bounds of this brush
|
|
void CBrush::Bounds(CBoundingBoxAligned& result) const
|
|
{
|
|
result.SetEmpty();
|
|
|
|
for(size_t i = 0; i < m_Vertices.size(); ++i)
|
|
result += m_Vertices[i];
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Cut the brush according to a given plane
|
|
|
|
/// Holds information about what happens to a single vertex in a brush during a slicing operation.
|
|
struct SliceOpVertexInfo
|
|
{
|
|
float planeDist; ///< Signed distance from this vertex to the slicing plane.
|
|
size_t resIdx; ///< Index of this vertex in the resulting brush (or NO_VERTEX if cut away)
|
|
};
|
|
|
|
/// Holds information about a newly introduced vertex on an edge in a brush as the result of a slicing operation.
|
|
struct SliceOpNewVertexInfo
|
|
{
|
|
/// Indices of adjacent edge vertices in original brush
|
|
size_t edgeIdx1, edgeIdx2;
|
|
/// Index of newly introduced vertex in resulting brush
|
|
size_t resIdx;
|
|
|
|
/**
|
|
* Index into SliceOpInfo.nvInfo; hold the indices of this new vertex's direct neighbours in the slicing plane face,
|
|
* with no consistent winding direction around the face for either field (e.g., the neighb1 of X can point back to
|
|
* X with either its neighb1 or neighb2).
|
|
*/
|
|
size_t neighbIdx1, neighbIdx2;
|
|
};
|
|
|
|
/// Holds support information during a CBrush/CPlane slicing operation.
|
|
struct SliceOpInfo
|
|
{
|
|
CBrush* result;
|
|
const CBrush* original;
|
|
|
|
/**
|
|
* Holds information about what happens to each vertex in the original brush after the slice operation.
|
|
* Same size as m_Vertices of the brush getting sliced.
|
|
*/
|
|
std::vector<SliceOpVertexInfo> ovInfo;
|
|
|
|
/// Holds information about newly inserted vertices during a slice operation.
|
|
std::vector<SliceOpNewVertexInfo> nvInfo;
|
|
|
|
/**
|
|
* Indices into nvInfo; during the execution of the slicing algorithm, holds the previously inserted new vertex on
|
|
* one of the edges of the face that's currently being evaluated for slice points, or NO_VERTEX if no such vertex
|
|
* exists.
|
|
*/
|
|
size_t thisFaceNewVertexIdx;
|
|
};
|
|
|
|
struct CBrush::Helper
|
|
{
|
|
/**
|
|
* Creates a new vertex between the given two vertices (indexed into the original brush).
|
|
* Returns the index of the new vertex in the resulting brush.
|
|
*/
|
|
static size_t SliceNewVertex(SliceOpInfo& sliceInfo, size_t v1, size_t v2);
|
|
};
|
|
|
|
size_t CBrush::Helper::SliceNewVertex(SliceOpInfo& sliceOp, size_t edgeIdx1, size_t edgeIdx2)
|
|
{
|
|
// check if a new vertex has already been inserted on this edge
|
|
size_t idx;
|
|
for(idx = 0; idx < sliceOp.nvInfo.size(); ++idx)
|
|
{
|
|
if ((sliceOp.nvInfo[idx].edgeIdx1 == edgeIdx1 && sliceOp.nvInfo[idx].edgeIdx2 == edgeIdx2) ||
|
|
(sliceOp.nvInfo[idx].edgeIdx1 == edgeIdx2 && sliceOp.nvInfo[idx].edgeIdx2 == edgeIdx1))
|
|
break;
|
|
}
|
|
|
|
if (idx >= sliceOp.nvInfo.size())
|
|
{
|
|
// no previously inserted new vertex found on this edge; insert a new one
|
|
SliceOpNewVertexInfo nvi;
|
|
CVector3D newPos;
|
|
|
|
// interpolate between the two vertices based on their distance from the plane
|
|
float inv = 1.0 / (sliceOp.ovInfo[edgeIdx1].planeDist - sliceOp.ovInfo[edgeIdx2].planeDist);
|
|
newPos = sliceOp.original->m_Vertices[edgeIdx2] * ( sliceOp.ovInfo[edgeIdx1].planeDist * inv) +
|
|
sliceOp.original->m_Vertices[edgeIdx1] * (-sliceOp.ovInfo[edgeIdx2].planeDist * inv);
|
|
|
|
nvi.edgeIdx1 = edgeIdx1;
|
|
nvi.edgeIdx2 = edgeIdx2;
|
|
nvi.resIdx = sliceOp.result->m_Vertices.size();
|
|
nvi.neighbIdx1 = NO_VERTEX;
|
|
nvi.neighbIdx2 = NO_VERTEX;
|
|
|
|
sliceOp.result->m_Vertices.push_back(newPos);
|
|
sliceOp.nvInfo.push_back(nvi);
|
|
}
|
|
|
|
// at this point, 'idx' is the index into nvInfo of the vertex inserted onto the edge
|
|
|
|
if (sliceOp.thisFaceNewVertexIdx != NO_VERTEX)
|
|
{
|
|
// a vertex has been previously inserted onto another edge of this face; link them together as neighbours
|
|
// (using whichever one of the neighbIdx1 or -2 links is still available)
|
|
|
|
if (sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx1 == NO_VERTEX)
|
|
sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx1 = idx;
|
|
else
|
|
sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx2 = idx;
|
|
|
|
if (sliceOp.nvInfo[idx].neighbIdx1 == NO_VERTEX)
|
|
sliceOp.nvInfo[idx].neighbIdx1 = sliceOp.thisFaceNewVertexIdx;
|
|
else
|
|
sliceOp.nvInfo[idx].neighbIdx2 = sliceOp.thisFaceNewVertexIdx;
|
|
|
|
// a plane should slice a face only in two locations, so reset for the next face
|
|
sliceOp.thisFaceNewVertexIdx = NO_VERTEX;
|
|
}
|
|
else
|
|
{
|
|
// store the index of the inserted vertex on this edge, so that we can retrieve it when the plane slices
|
|
// this face again in another edge
|
|
sliceOp.thisFaceNewVertexIdx = idx;
|
|
}
|
|
|
|
return sliceOp.nvInfo[idx].resIdx;
|
|
}
|
|
|
|
void CBrush::Slice(const CPlane& plane, CBrush& result) const
|
|
{
|
|
ENSURE(&result != this);
|
|
|
|
SliceOpInfo sliceOp;
|
|
|
|
sliceOp.original = this;
|
|
sliceOp.result = &result;
|
|
sliceOp.thisFaceNewVertexIdx = NO_VERTEX;
|
|
sliceOp.ovInfo.resize(m_Vertices.size());
|
|
sliceOp.nvInfo.reserve(m_Vertices.size() / 2);
|
|
|
|
result.m_Vertices.resize(0); // clear any left-overs
|
|
result.m_Faces.resize(0);
|
|
result.m_Vertices.reserve(m_Vertices.size() + 2);
|
|
result.m_Faces.reserve(m_Faces.size() + 5);
|
|
|
|
// Copy vertices that weren't sliced away by the plane to the resulting brush.
|
|
for(size_t i = 0; i < m_Vertices.size(); ++i)
|
|
{
|
|
const CVector3D& vtx = m_Vertices[i]; // current vertex
|
|
SliceOpVertexInfo& vtxInfo = sliceOp.ovInfo[i]; // slicing operation info about current vertex
|
|
|
|
vtxInfo.planeDist = plane.DistanceToPlane(vtx);
|
|
if (vtxInfo.planeDist >= 0.0)
|
|
{
|
|
// positive side of the plane; not sliced away
|
|
vtxInfo.resIdx = result.m_Vertices.size();
|
|
result.m_Vertices.push_back(vtx);
|
|
}
|
|
else
|
|
{
|
|
// other side of the plane; sliced away
|
|
vtxInfo.resIdx = NO_VERTEX;
|
|
}
|
|
}
|
|
|
|
// Transfer faces. (Recall how faces are specified; see CBrush::m_Faces). The idea is to examine each face separately,
|
|
// and see where its edges cross the slicing plane (meaning that exactly one of the vertices of that edge was cut away).
|
|
// On those edges, new vertices are introduced where the edge intersects the plane, and the resulting brush's m_Faces
|
|
// array is updated to refer to the newly inserted vertices instead of the original one that got cut away.
|
|
|
|
size_t currentFaceStartIdx = NO_VERTEX; // index of the first vertex of the current face in the original brush
|
|
size_t resultFaceStartIdx = NO_VERTEX; // index of the first vertex of the current face in the resulting brush
|
|
|
|
for(size_t i = 0; i < m_Faces.size(); ++i)
|
|
{
|
|
if (currentFaceStartIdx == NO_VERTEX)
|
|
{
|
|
// starting a new face
|
|
ENSURE(sliceOp.thisFaceNewVertexIdx == NO_VERTEX);
|
|
|
|
currentFaceStartIdx = m_Faces[i];
|
|
resultFaceStartIdx = result.m_Faces.size();
|
|
continue;
|
|
}
|
|
|
|
size_t prevIdx = m_Faces[i-1]; // index of previous vertex in this face list
|
|
size_t curIdx = m_Faces[i]; // index of current vertex in this face list
|
|
|
|
if (sliceOp.ovInfo[prevIdx].resIdx == NO_VERTEX)
|
|
{
|
|
// previous face vertex got sliced away by the plane; see if the edge (prev,current) crosses the slicing plane
|
|
if (sliceOp.ovInfo[curIdx].resIdx != NO_VERTEX)
|
|
{
|
|
// re-entering the front side of the plane; insert vertex on intersection of plane and (prev,current) edge
|
|
result.m_Faces.push_back(Helper::SliceNewVertex(sliceOp, prevIdx, curIdx));
|
|
result.m_Faces.push_back(sliceOp.ovInfo[curIdx].resIdx);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// previous face vertex didn't get sliced away; see if the edge (prev,current) crosses the slicing plane
|
|
if (sliceOp.ovInfo[curIdx].resIdx != NO_VERTEX)
|
|
{
|
|
// perfectly normal edge; doesn't cross the plane
|
|
result.m_Faces.push_back(sliceOp.ovInfo[curIdx].resIdx);
|
|
}
|
|
else
|
|
{
|
|
// leaving the front side of the plane; insert vertex on intersection of plane and edge (prev, current)
|
|
result.m_Faces.push_back(Helper::SliceNewVertex(sliceOp, prevIdx, curIdx));
|
|
}
|
|
}
|
|
|
|
// if we're back at the first vertex of the current face, then we've completed the face
|
|
if (curIdx == currentFaceStartIdx)
|
|
{
|
|
// close the index loop
|
|
if (result.m_Faces.size() > resultFaceStartIdx)
|
|
result.m_Faces.push_back(result.m_Faces[resultFaceStartIdx]);
|
|
|
|
currentFaceStartIdx = NO_VERTEX; // start a new face
|
|
}
|
|
}
|
|
|
|
ENSURE(currentFaceStartIdx == NO_VERTEX);
|
|
|
|
// Create the face that lies in the slicing plane. Remember, all the intersections of the slicing plane with face
|
|
// edges of the brush have been stored in sliceOp.nvInfo by the SliceNewVertex function, and refer to their direct
|
|
// neighbours in the slicing plane face using the neighbIdx1 and neighbIdx2 fields (in no consistent winding order).
|
|
|
|
if (sliceOp.nvInfo.size())
|
|
{
|
|
// push the starting vertex
|
|
result.m_Faces.push_back(sliceOp.nvInfo[0].resIdx);
|
|
|
|
// At this point, there is no consistent winding order in the neighbX fields, so at each vertex we need to figure
|
|
// out whether neighb1 or neighb2 points 'onwards' along the face, according to an initially chosen winding direction.
|
|
// (or, equivalently, which one points back to the one we were just at). At each vertex, we then set neighb1 to be the
|
|
// one to point onwards, deleting any pointers which we no longer need to complete the trace.
|
|
|
|
size_t idx;
|
|
size_t prev = 0;
|
|
|
|
idx = sliceOp.nvInfo[0].neighbIdx2; // pick arbitrary starting direction
|
|
sliceOp.nvInfo[0].neighbIdx2 = NO_VERTEX;
|
|
|
|
while(idx != 0)
|
|
{
|
|
ENSURE(idx < sliceOp.nvInfo.size());
|
|
if (idx >= sliceOp.nvInfo.size())
|
|
break;
|
|
|
|
if (sliceOp.nvInfo[idx].neighbIdx1 == prev)
|
|
{
|
|
// neighb1 is pointing the wrong way; we want to normalize it to point onwards in the direction
|
|
// we initially chose, so swap it with neighb2 and delete neighb2 (no longer needed)
|
|
sliceOp.nvInfo[idx].neighbIdx1 = sliceOp.nvInfo[idx].neighbIdx2;
|
|
sliceOp.nvInfo[idx].neighbIdx2 = NO_VERTEX;
|
|
}
|
|
else
|
|
{
|
|
// neighb1 isn't pointing to the previous vertex, so neighb2 must be (otherwise a pair of vertices failed to
|
|
// get paired properly during face/plane slicing).
|
|
ENSURE(sliceOp.nvInfo[idx].neighbIdx2 == prev);
|
|
sliceOp.nvInfo[idx].neighbIdx2 = NO_VERTEX;
|
|
}
|
|
|
|
result.m_Faces.push_back(sliceOp.nvInfo[idx].resIdx);
|
|
|
|
// move to next vertex; neighb1 has been normalized to point onward
|
|
prev = idx;
|
|
idx = sliceOp.nvInfo[idx].neighbIdx1;
|
|
sliceOp.nvInfo[prev].neighbIdx1 = NO_VERTEX; // no longer needed, we've moved on
|
|
}
|
|
|
|
// push starting vertex again to close the shape
|
|
result.m_Faces.push_back(sliceOp.nvInfo[0].resIdx);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Intersect with frustum by repeated slicing
|
|
void CBrush::Intersect(const CFrustum& frustum, CBrush& result) const
|
|
{
|
|
ENSURE(&result != this);
|
|
|
|
if (!frustum.GetNumPlanes())
|
|
{
|
|
result = *this;
|
|
return;
|
|
}
|
|
|
|
CBrush buf;
|
|
const CBrush* prev = this;
|
|
CBrush* next;
|
|
|
|
// Repeatedly slice this brush with each plane of the frustum, alternating between 'result' and 'buf' to
|
|
// save intermediate results. Set up the starting brush so that the final version always ends up in 'result'.
|
|
|
|
if (frustum.GetNumPlanes() & 1)
|
|
next = &result;
|
|
else
|
|
next = &buf;
|
|
|
|
for(size_t i = 0; i < frustum.GetNumPlanes(); ++i)
|
|
{
|
|
prev->Slice(frustum[i], *next);
|
|
prev = next;
|
|
if (prev == &buf)
|
|
next = &result;
|
|
else
|
|
next = &buf;
|
|
}
|
|
|
|
ENSURE(prev == &result);
|
|
}
|
|
std::vector<CVector3D> CBrush::GetVertices() const
|
|
{
|
|
return m_Vertices;
|
|
}
|
|
|
|
void CBrush::GetFaces(std::vector<std::vector<size_t> >& out) const
|
|
{
|
|
// split the back-to-back faces into separate face vectors, so that they're in a
|
|
// user-friendlier format than the back-to-back vertex index array
|
|
// i.e. split 'x--xy------yz----z' into 'x--x', 'y-------y', 'z---z'
|
|
|
|
size_t faceStartIdx = 0;
|
|
while (faceStartIdx < m_Faces.size())
|
|
{
|
|
// start new face
|
|
std::vector<size_t> singleFace;
|
|
singleFace.push_back(m_Faces[faceStartIdx]);
|
|
|
|
// step over all the values in the face until we hit the starting value again (which closes the face)
|
|
size_t j = faceStartIdx + 1;
|
|
while (j < m_Faces.size() && m_Faces[j] != m_Faces[faceStartIdx])
|
|
{
|
|
singleFace.push_back(m_Faces[j]);
|
|
j++;
|
|
}
|
|
|
|
// each face must be closed by the same value that started it
|
|
ENSURE(m_Faces[faceStartIdx] == m_Faces[j]);
|
|
|
|
singleFace.push_back(m_Faces[j]);
|
|
out.push_back(singleFace);
|
|
|
|
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);
|
|
}
|