View frustrum culling done directly in chunk node tree update traversal

This commit is contained in:
Erik Broberg
2016-04-15 15:39:55 -04:00
parent db6246e7c8
commit 7ecae3003c
10 changed files with 187 additions and 78 deletions

View File

@@ -76,7 +76,7 @@ void ChunkNode::render(const RenderData& data, ChunkIndex traverseData) {
// Returns true or false wether this node can be merge or not
bool ChunkNode::internalUpdateChunkTree(const RenderData& data, ChunkIndex& traverseData) {
using namespace glm;
LatLon center = _patch.center;
LatLon center = _patch.center();
//LDEBUG("x: " << patch.x << " y: " << patch.y << " level: " << patch.level << " lat: " << center.lat << " lon: " << center.lon);
@@ -117,6 +117,7 @@ bool ChunkNode::internalUpdateChunkTree(const RenderData& data, ChunkIndex& trav
void ChunkNode::internalRender(const RenderData& data, ChunkIndex& traverseData) {
if (isLeaf()) {
PatchRenderer& patchRenderer = _owner.getPatchRenderer();
patchRenderer.renderPatch(_patch, data, _owner.globeRadius);
}
else {
@@ -130,7 +131,7 @@ void ChunkNode::internalRender(const RenderData& data, ChunkIndex& traverseData)
int ChunkNode::calculateDesiredLevel(const RenderData& data, const ChunkIndex& traverseData) {
Vec3 patchNormal = _patch.center.asUnitCartesian();
Vec3 patchNormal = _patch.center().asUnitCartesian();
Vec3 patchPosition = data.position.dvec3() + _owner.globeRadius * patchNormal;
Vec3 cameraPosition = data.camera.position().dvec3();
@@ -146,6 +147,14 @@ int ChunkNode::calculateDesiredLevel(const RenderData& data, const ChunkIndex& t
}
// Do frustrum culling
FrustrumCuller& culler = _owner.getFrustrumCuller();
if (!culler.isVisible(data, _patch, _owner.globeRadius)) {
return traverseData.level - 1;
}
// Calculate desired level based on distance
Scalar distance = glm::length(cameraToChunk);
_owner.minDistToCamera = fmin(_owner.minDistToCamera, distance);
@@ -161,8 +170,8 @@ void ChunkNode::split(int depth) {
if (depth > 0 && isLeaf()) {
// Defining short handles for center, halfSize and quarterSize
const LatLon& c = _patch.center;
const LatLon& hs = _patch.halfSize;
const LatLon& c = _patch.center();
const LatLon& hs = _patch.halfSize();
LatLon qs = LatLon(0.5 * hs.lat, 0.5 * hs.lon);
// Subdivide bounds

View File

@@ -55,7 +55,7 @@ namespace openspace {
}
Vec3 LatLon::asUnitCartesian() {
Vec3 LatLon::asUnitCartesian() const{
return Vec3(
glm::cos(lat) * glm::cos(lon),
glm::cos(lat) * glm::sin(lon),
@@ -75,47 +75,70 @@ namespace openspace {
//////////////////////////////////////////////////////////////////////////////////////////
LatLonPatch::LatLonPatch(Scalar centerLat, Scalar centerLon, Scalar halfSizeLat, Scalar halfSizeLon)
: center(LatLon(centerLat, centerLon))
, halfSize(LatLon(halfSizeLat, halfSizeLon))
: _center(LatLon(centerLat, centerLon))
, _halfSize(LatLon(halfSizeLat, halfSizeLon))
{
}
LatLonPatch::LatLonPatch(const LatLon& center, const LatLon& halfSize)
: center(center)
, halfSize(halfSize)
: _center(center)
, _halfSize(halfSize)
{
}
LatLonPatch::LatLonPatch(const LatLonPatch& patch)
: center(patch.center)
, halfSize(patch.halfSize)
: _center(patch._center)
, _halfSize(patch._halfSize)
{
}
void LatLonPatch::setCenter(const LatLon& center) {
_center = center;
}
void LatLonPatch::setHalfSize(const LatLon& halfSize) {
_halfSize = halfSize;
}
Scalar LatLonPatch::minimalBoundingRadius() const {
const LatLon& cornerNearEquator = _center.lat > 0 ? southWestCorner() : northWestCorner();
return glm::length(_center.asUnitCartesian() - cornerNearEquator.asUnitCartesian());
}
Scalar LatLonPatch::unitArea() const {
Scalar deltaTheta = 2 * halfSize.lon;
Scalar phiMin = center.lat - halfSize.lat;
Scalar phiMax = center.lat + halfSize.lat;
Scalar deltaTheta = 2 * _halfSize.lon;
Scalar phiMin = _center.lat - _halfSize.lat;
Scalar phiMax = _center.lat + _halfSize.lat;
return deltaTheta * (sin(phiMax) - sin(phiMin));
}
const LatLon& LatLonPatch::center() const {
return _center;
}
const LatLon& LatLonPatch::halfSize() const {
return _halfSize;
}
LatLon LatLonPatch::northWestCorner() const{
return LatLon(center.lat + halfSize.lat, center.lon - halfSize.lon);
return LatLon(_center.lat + _halfSize.lat, _center.lon - _halfSize.lon);
}
LatLon LatLonPatch::northEastCorner() const{
return LatLon(center.lat + halfSize.lat, center.lon + halfSize.lon);
return LatLon(_center.lat + _halfSize.lat, _center.lon + _halfSize.lon);
}
LatLon LatLonPatch::southWestCorner() const{
return LatLon(center.lat - halfSize.lat, center.lon - halfSize.lon);
return LatLon(_center.lat - _halfSize.lat, _center.lon - _halfSize.lon);
}
LatLon LatLonPatch::southEastCorner() const{
return LatLon(center.lat - halfSize.lat, center.lon + halfSize.lon);
return LatLon(_center.lat - _halfSize.lat, _center.lon + _halfSize.lon);
}
} // namespace openspace

View File

@@ -43,7 +43,7 @@ struct LatLon {
LatLon(const LatLon& src);
static LatLon fromCartesian(const Vec3& v);
Vec3 asUnitCartesian();
Vec3 asUnitCartesian() const;
Vec2 asVec2() const;
inline bool operator==(const LatLon& other);
@@ -53,21 +53,39 @@ struct LatLon {
Scalar lon;
};
struct LatLonPatch {
class LatLonPatch {
public:
LatLonPatch(Scalar, Scalar, Scalar, Scalar);
LatLonPatch(const LatLon& center, const LatLon& halfSize);
LatLonPatch(const LatLonPatch& patch);
void setCenter(const LatLon&);
void setHalfSize(const LatLon&);
// Returns the minimal bounding radius that together with the LatLonPatch's
// center point represents a sphere in which the patch is completely contained
Scalar minimalBoundingRadius() const;
// Returns the area of the patch with unit radius
Scalar unitArea() const;
LatLon northWestCorner() const;
LatLon northEastCorner() const;
LatLon southWestCorner() const;
LatLon southEastCorner() const;
LatLon center;
LatLon halfSize;
const LatLon& center() const;
const LatLon& halfSize() const;
private:
LatLon _center;
LatLon _halfSize;
};

View File

@@ -87,8 +87,7 @@ namespace openspace {
_patchRenderer.reset(new LatLonPatchRenderer(geometry));
std::shared_ptr<FrustrumCuller> fc(new FrustrumCuller(1.3f));
_patchRenderer->setFrustrumCuller(fc);
_frustrumCuller = std::shared_ptr<FrustrumCuller>(new FrustrumCuller());
}
@@ -113,12 +112,18 @@ namespace openspace {
return *_patchRenderer;
}
FrustrumCuller& ChunkLodGlobe::getFrustrumCuller() {
return *_frustrumCuller;
}
void ChunkLodGlobe::render(const RenderData& data){
minDistToCamera = INFINITY;
PatchIndex leftRootTileIndex = { 0, 0, 1 };
ChunkIndex leftRootTileIndex = { 0, 0, 1 };
_leftRoot->render(data, leftRootTileIndex);
PatchIndex rightRootTileIndex = { 1, 0, 1 };
ChunkIndex rightRootTileIndex = { 1, 0, 1 };
_rightRoot->render(data, rightRootTileIndex);
//LDEBUG("min distnace to camera: " << minDistToCamera);

View File

@@ -57,6 +57,7 @@ namespace openspace {
~ChunkLodGlobe();
PatchRenderer& getPatchRenderer();
FrustrumCuller& getFrustrumCuller();
bool initialize() override;
bool deinitialize() override;
@@ -81,6 +82,9 @@ namespace openspace {
// Covers all positive longitudes
std::unique_ptr<ChunkNode> _rightRoot;
// Frustrum culler
std::shared_ptr<FrustrumCuller> _frustrumCuller;
// the patch used for actual rendering
std::unique_ptr<PatchRenderer> _patchRenderer;

View File

@@ -104,7 +104,7 @@ namespace openspace {
// Set patches to follow camera
for (size_t i = 0; i < _patches.size(); i++)
{
_patches[i].center = LatLon::fromCartesian(data.camera.position().dvec3());
_patches[i].setCenter(LatLon::fromCartesian(data.camera.position().dvec3()));
}
// render patches
for (size_t i = 0; i < _patches.size(); i++)

View File

@@ -1,4 +1,4 @@
/*****************************************************************************************
/*****************************************************************************************
* *
* OpenSpace *
* *
@@ -34,8 +34,7 @@ namespace openspace {
//////////////////////////////////////////////////////////////////////////////////////
// PATCH RENDERER //
//////////////////////////////////////////////////////////////////////////////////////
FrustrumCuller::FrustrumCuller(float tolerance)
: _tolerance(tolerance)
FrustrumCuller::FrustrumCuller()
{
}
@@ -45,23 +44,66 @@ namespace openspace {
}
bool FrustrumCuller::isVisible(const vec3& point, const mat4x4& modelViewProjection) {
vec4 pointModelSpace(point, 1.0f);
vec4 pointProjectionSpace = modelViewProjection * pointModelSpace;
vec2 pointScreenSpace = (1.0f / pointProjectionSpace.w) * pointProjectionSpace.xy;
bool FrustrumCuller::isVisible(const RenderData& data, const vec3& point, const glm::vec2& marginScreenSpace) {
// just for readability
mat4 modelTransform = translate(mat4(1), data.position.vec3());
mat4 viewTransform = data.camera.combinedViewMatrix();
mat4 modelViewProjectionTransform = data.camera.projectionMatrix()
* viewTransform * modelTransform;
vec2 pointScreenSpace = transformToScreenSpace(point, modelViewProjectionTransform);
return testPoint(pointScreenSpace, marginScreenSpace);
}
bool FrustrumCuller::isVisible(const RenderData& data, const LatLonPatch& patch, double radius) {
// An axis aligned bounding box based on the patch's minimum boudning sphere is
// used for testnig
// Calculate the MVP matrix
mat4 modelTransform = translate(mat4(1), data.position.vec3());
mat4 viewTransform = data.camera.combinedViewMatrix();
mat4 modelViewProjectionTransform = data.camera.projectionMatrix()
* viewTransform * modelTransform;
// Calculate the patch's center point in screen space
vec4 patchCenterModelSpace = vec4(radius * patch.center().asUnitCartesian(), 1);
vec4 patchCenterProjectionSpace = modelViewProjectionTransform * patchCenterModelSpace;
vec2 pointScreenSpace = (1.0f / patchCenterProjectionSpace.w) * patchCenterProjectionSpace.xy;
// Calculate the screen space margin that represents an axis aligned bounding
// box based on the patch's minimum boudning sphere
double boundingRadius = radius * patch.minimalBoundingRadius();
vec4 marginProjectionSpace = vec4(vec3(boundingRadius), 0) * data.camera.projectionMatrix();
vec2 marginScreenSpace = (1.0f / patchCenterProjectionSpace.w) * marginProjectionSpace.xy;
// Test the bounding box by testing the center point and the corresponding margin
return testPoint(pointScreenSpace, marginScreenSpace);
}
bool FrustrumCuller::testPoint(const glm::vec2& pointScreenSpace,
const glm::vec2& marginScreenSpace) const
{
const vec2& p = pointScreenSpace;
return ((-_tolerance < p.x && p.x < _tolerance) &
(-_tolerance < p.y && p.y < _tolerance));
vec2 cullBounds = vec2(1) + marginScreenSpace;
return ((-cullBounds.x < p.x && p.x < cullBounds.x) &&
(-cullBounds.y < p.y && p.y < cullBounds.y));
}
bool FrustrumCuller::isVisible(const LatLonPatch& patch, double radius, const mat4x4& modelViewProjection) {
return isVisible(radius * patch.northWestCorner().asUnitCartesian(), modelViewProjection)
|| isVisible(radius * patch.northEastCorner().asUnitCartesian(), modelViewProjection)
|| isVisible(radius * patch.southWestCorner().asUnitCartesian(), modelViewProjection)
|| isVisible(radius * patch.southEastCorner().asUnitCartesian(), modelViewProjection);
glm::vec2 FrustrumCuller::transformToScreenSpace(const vec3& point,
const mat4x4& modelViewProjection) const
{
vec4 pointProjectionSpace = modelViewProjection * vec4(point, 1.0f);
vec2 pointScreenSpace = (1.0f / pointProjectionSpace.w) * pointProjectionSpace.xy;
return pointScreenSpace;
}
} // namespace openspace

View File

@@ -45,15 +45,38 @@ namespace openspace {
class FrustrumCuller {
public:
FrustrumCuller(float tolerance = 1.0f);
FrustrumCuller();
~FrustrumCuller();
bool isVisible(const vec3& point, const mat4x4& modelViewProjection);
bool isVisible(const LatLonPatch& patch, double radius, const mat4x4& modelViewProjection);
/// Returns true if the point is inside the view frustrum defined in RenderData.
/// The third argument marginScreenSpace is added to the default screen space
/// boundaries. E.g. for marginScreenSpace = {0.2, 0.2}, all points with
/// 1.2 < x,y < 1.2 would cause isVisible to return true.
bool isVisible(
const RenderData& data,
const vec3& point,
const vec2& marginScreenSpace = vec2(0));
/// Returns false if the patch element is guaranteed to be outside the view
/// frustrum, and true is the patch element MAY be inside the view frustrum.
bool isVisible(
const RenderData& data,
const LatLonPatch& patch,
double radius);
protected:
float _tolerance;
private:
/// Returns true if the point in screen space is inside the view frustrum.
/// The optional screen space margin vector is used to resize area defining
/// what is considered to be inside the view frustrum.
bool testPoint(
const glm::vec2& pointScreenSpace,
const glm::vec2& marginScreenSpace) const;
glm::vec2 transformToScreenSpace(const vec3& point, const mat4x4& modelViewProjection) const;
};

View File

@@ -67,9 +67,7 @@ namespace openspace {
}
}
void PatchRenderer::setFrustrumCuller(std::shared_ptr<FrustrumCuller> fc) {
_frustrumCuller = fc;
}
//////////////////////////////////////////////////////////////////////////////////////
// LATLON PATCH RENDERER //
@@ -104,23 +102,10 @@ namespace openspace {
* viewTransform * modelTransform;
// View frustrum culling
if (_frustrumCuller != nullptr) {
//LatLon center = patch.center;
//vec3 centerPoint = radius * center.asUnitCartesian();
if (!_frustrumCuller->isVisible(patch, radius, modelViewProjectionTransform)) {
// dont render the patch
return;
}
}
LatLon swCorner = patch.southWestCorner();
_programObject->setUniform("modelViewProjectionTransform", modelViewProjectionTransform);
_programObject->setUniform("minLatLon", vec2(swCorner.lat, swCorner.lon));
_programObject->setUniform("latLonScalingFactor", 2.0f * vec2(patch.halfSize.asVec2()));
_programObject->setUniform("latLonScalingFactor", 2.0f * vec2(patch.halfSize().asVec2()));
_programObject->setUniform("globeRadius", float(radius));
glEnable(GL_DEPTH_TEST);
@@ -157,25 +142,26 @@ namespace openspace {
_programObject->activate();
using namespace glm;
const mat4& viewTransform = data.camera.combinedViewMatrix();
mat4 viewTransform = data.camera.combinedViewMatrix();
// TODO : Model transform should be fetched as a matrix directly.
mat4 modelTransform = translate(mat4(1), data.position.vec3());
// Snap patch position
int segmentsPerPatch = 32;
LatLon halfSize = patch.halfSize();
LatLon stepSize = LatLon(
patch.halfSize.lat * 2 / segmentsPerPatch,
patch.halfSize.lon * 2 / segmentsPerPatch);
halfSize.lat * 2 / segmentsPerPatch,
halfSize.lon * 2 / segmentsPerPatch);
ivec2 patchesToCoverGlobe = ivec2(
M_PI / (patch.halfSize.lat * 2) + 0.5,
M_PI * 2 / (patch.halfSize.lon * 2) + 0.5);
M_PI / (halfSize.lat * 2) + 0.5,
M_PI * 2 / (halfSize.lon * 2) + 0.5);
ivec2 intSnapCoord = ivec2(
patch.center.lat / (M_PI * 2) * segmentsPerPatch * patchesToCoverGlobe.y,
patch.center.lon / (M_PI) * segmentsPerPatch * patchesToCoverGlobe.x);
patch.center().lat / (M_PI * 2) * segmentsPerPatch * patchesToCoverGlobe.y,
patch.center().lon / (M_PI) * segmentsPerPatch * patchesToCoverGlobe.x);
LatLon swCorner = LatLon(
stepSize.lat * intSnapCoord.x - patch.halfSize.lat,
stepSize.lon * intSnapCoord.y - patch.halfSize.lon);
stepSize.lat * intSnapCoord.x - halfSize.lat,
stepSize.lon * intSnapCoord.y - halfSize.lon);
ivec2 contraction = ivec2(intSnapCoord.y % 2, intSnapCoord.x % 2);
@@ -185,7 +171,7 @@ namespace openspace {
_programObject->setUniform("modelViewProjectionTransform", data.camera.projectionMatrix() * viewTransform * modelTransform);
_programObject->setUniform("minLatLon", vec2(swCorner.lat, swCorner.lon));
_programObject->setUniform("latLonScalingFactor", 2.0f * vec2(patch.halfSize.lat, patch.halfSize.lon));
_programObject->setUniform("latLonScalingFactor", 2.0f * vec2(halfSize.lat, halfSize.lon));
_programObject->setUniform("globeRadius", float(radius));
_programObject->setUniform("contraction", contraction);

View File

@@ -65,7 +65,6 @@ namespace openspace {
unique_ptr<ProgramObject> _programObject;
shared_ptr<Geometry> _geometry;
shared_ptr<FrustrumCuller> _frustrumCuller;
};