Merge pull request #3599 from ZappoMan/frustumCullModelParts

Frustum cull model parts
This commit is contained in:
samcake 2014-10-15 11:56:00 -07:00
commit 3d13792a9d
13 changed files with 170 additions and 26 deletions

View file

@ -423,6 +423,7 @@ Menu::Menu() :
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderFocusIndicator, 0, false);
QMenu* modelDebugMenu = developerMenu->addMenu("Models");
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DontCullMeshParts, 0, false);
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelBounds, 0, false);
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementProxy, 0, false);
addCheckableActionToQMenuAndActionHash(modelDebugMenu, MenuOption::DisplayModelElementChildProxies, 0, false);

View file

@ -367,6 +367,7 @@ namespace MenuOption {
const QString Collisions = "Collisions";
const QString Console = "Console...";
const QString ControlWithSpeech = "Control With Speech";
const QString DontCullMeshParts = "Don't Cull Mesh Parts";
const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DecreaseVoxelSize = "Decrease Voxel Size";
const QString DisableActivityLogger = "Disable Activity Logger";

View file

@ -15,9 +15,9 @@
#include "Joystick.h"
const float MAX_AXIS = 32768.0f;
#ifdef HAVE_SDL2
const float MAX_AXIS = 32768.0f;
Joystick::Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController) :
_instanceId(instanceId),

View file

@ -314,6 +314,7 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
glower = new Glower(entityItem->getGlowLevel());
}
entityItem->render(args);
args->_itemsRendered++;
if (glower) {
delete glower;
}

View file

@ -59,7 +59,7 @@ int RenderableModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned c
void RenderableModelEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableModelEntityItem::render");
PerformanceTimer perfTimer("RMEIrender");
assert(getType() == EntityTypes::Model);
bool drawAsModel = hasModel();
@ -119,7 +119,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
// TODO: this is the majority of model render time. And rendering of a cube model vs the basic Box render
// is significantly more expensive. Is there a way to call this that doesn't cost us as much?
PerformanceTimer perfTimer("model->render");
_model->render(alpha, modelRenderMode);
_model->render(alpha, modelRenderMode, args);
} else {
// if we couldn't get a model, then just draw a cube
glColor3ub(getColor()[RED_INDEX],getColor()[GREEN_INDEX],getColor()[BLUE_INDEX]);

View file

@ -18,6 +18,7 @@
#include <CapsuleShape.h>
#include <GeometryUtil.h>
#include <PerfStat.h>
#include <PhysicsEntity.h>
#include <ShapeCollider.h>
#include <SphereShape.h>
@ -44,7 +45,8 @@ Model::Model(QObject* parent) :
_pupilDilation(0.0f),
_url("http://invalid.com"),
_blendNumber(0),
_appliedBlendNumber(0) {
_appliedBlendNumber(0),
_calculatedMeshBoxesValid(false) {
// we may have been created in the network thread, but we live in the main thread
moveToThread(Application::getInstance()->thread());
@ -384,7 +386,7 @@ void Model::setJointStates(QVector<JointState> states) {
_boundingRadius = radius;
}
bool Model::render(float alpha, RenderMode mode) {
bool Model::render(float alpha, RenderMode mode, RenderArgs* args) {
// render the attachments
foreach (Model* attachment, _attachments) {
attachment->render(alpha, mode);
@ -392,6 +394,23 @@ bool Model::render(float alpha, RenderMode mode) {
if (_meshStates.isEmpty()) {
return false;
}
// if we don't have valid mesh boxes, calculate them now, this only matters in cases
// where our caller has passed RenderArgs which will include a view frustum we can cull
// against. We cache the results of these calculations so long as the model hasn't been
// simulated and the mesh hasn't changed.
if (args && !_calculatedMeshBoxesValid) {
PerformanceTimer perfTimer("calculatedMeshBoxes");
const FBXGeometry& geometry = _geometry->getFBXGeometry();
int numberOfMeshes = geometry.meshes.size();
_calculatedMeshBoxes.resize(numberOfMeshes);
for (int i = 0; i < numberOfMeshes; i++) {
const FBXMesh& mesh = geometry.meshes.at(i);
Extents scaledMeshExtents = calculateScaledOffsetExtents(mesh.meshExtents);
_calculatedMeshBoxes[i] = AABox(scaledMeshExtents);
}
_calculatedMeshBoxesValid = true;
}
// set up dilated textures on first render after load/simulate
const FBXGeometry& geometry = _geometry->getFBXGeometry();
@ -431,11 +450,12 @@ bool Model::render(float alpha, RenderMode mode) {
mode == DEFAULT_RENDER_MODE || mode == NORMAL_RENDER_MODE,
mode == DEFAULT_RENDER_MODE);
renderMeshes(mode, false);
const float DEFAULT_ALPHA_THRESHOLD = 0.5f;
renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, args);
// render translucent meshes afterwards
Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(false, true, true);
renderMeshes(mode, true, 0.75f);
renderMeshes(mode, true, 0.75f, args);
glDisable(GL_ALPHA_TEST);
glEnable(GL_BLEND);
@ -445,7 +465,7 @@ bool Model::render(float alpha, RenderMode mode) {
Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(true);
if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) {
renderMeshes(mode, true, 0.0f);
renderMeshes(mode, true, 0.0f, args);
}
glDepthMask(true);
@ -511,6 +531,22 @@ Extents Model::getUnscaledMeshExtents() const {
return scaledExtents;
}
Extents Model::calculateScaledOffsetExtents(const Extents& extents) const {
// we need to include any fst scaling, translation, and rotation, which is captured in the offset matrix
glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f));
glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f));
Extents scaledOffsetExtents = { ((minimum + _offset) * _scale),
((maximum + _offset) * _scale) };
Extents rotatedExtents = scaledOffsetExtents.getRotated(_rotation);
Extents translatedExtents = { rotatedExtents.minimum + _translation,
rotatedExtents.maximum + _translation };
return translatedExtents;
}
bool Model::getJointState(int index, glm::quat& rotation) const {
if (index == -1 || index >= _jointStates.size()) {
return false;
@ -790,6 +826,8 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
|| (_snapModelToRegistrationPoint && !_snappedToRegistrationPoint);
if (isActive() && fullUpdate) {
_calculatedMeshBoxesValid = false; // if we have to simulate, we need to assume our mesh boxes are all invalid
// check for scale to fit
if (_scaleToFit && !_scaledToFit) {
scaleToFit();
@ -1200,10 +1238,12 @@ void Model::deleteGeometry() {
_blendedBlendshapeCoefficients.clear();
}
void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold) {
void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, RenderArgs* args) {
updateVisibleJointStates();
const FBXGeometry& geometry = _geometry->getFBXGeometry();
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
bool cullMeshParts = args && !Menu::getInstance()->isOptionChecked(MenuOption::DontCullMeshParts);
for (int i = 0; i < networkMeshes.size(); i++) {
// exit early if the translucency doesn't match what we're drawing
@ -1221,6 +1261,23 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
continue;
}
// if we got here, then check to see if this mesh is in view
if (args) {
bool shouldRender = true;
args->_meshesConsidered++;
if (cullMeshParts && args->_viewFrustum) {
shouldRender = args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE;
}
if (shouldRender) {
args->_meshesRendered++;
} else {
args->_meshesOutOfView++;
continue; // skip this mesh
}
}
const_cast<QOpenGLBuffer&>(networkMesh.vertexBuffer).bind();
ProgramObject* program = &_program;
@ -1372,6 +1429,13 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(),
GL_UNSIGNED_INT, (void*)offset);
offset += part.triangleIndices.size() * sizeof(int);
if (args) {
const int INDICES_PER_TRIANGLE = 3;
const int INDICES_PER_QUAD = 4;
args->_trianglesRendered += part.triangleIndices.size() / INDICES_PER_TRIANGLE;
args->_quadsRendered += part.quadIndices.size() / INDICES_PER_QUAD;
}
}
if (!mesh.colors.isEmpty()) {

View file

@ -16,9 +16,9 @@
#include <QObject>
#include <QUrl>
#include <PhysicsEntity.h>
#include <AABox.h>
#include <AnimationCache.h>
#include <PhysicsEntity.h>
#include "GeometryCache.h"
#include "InterfaceConfig.h"
@ -30,6 +30,7 @@ class QScriptEngine;
class AnimationHandle;
class Shape;
class RenderArgs;
typedef QSharedPointer<AnimationHandle> AnimationHandlePointer;
typedef QWeakPointer<AnimationHandle> WeakAnimationHandlePointer;
@ -84,7 +85,7 @@ public:
enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE };
bool render(float alpha = 1.0f, RenderMode mode = DEFAULT_RENDER_MODE);
bool render(float alpha = 1.0f, RenderMode mode = DEFAULT_RENDER_MODE, RenderArgs* args = NULL);
/// Sets the URL of the model to render.
/// \param fallback the URL of a fallback model to render if the requested model fails to load
@ -107,6 +108,9 @@ public:
/// Returns the unscaled extents of the model's mesh
Extents getUnscaledMeshExtents() const;
/// Returns the scaled equivalent of some extents in model space.
Extents calculateScaledOffsetExtents(const Extents& extents) const;
/// Returns a reference to the shared geometry.
const QSharedPointer<NetworkGeometry>& getGeometry() const { return _geometry; }
@ -247,7 +251,7 @@ private:
void applyNextGeometry();
void deleteGeometry();
void renderMeshes(RenderMode mode, bool translucent, float alphaThreshold = 0.5f);
void renderMeshes(RenderMode mode, bool translucent, float alphaThreshold = 0.5f, RenderArgs* args = NULL);
QVector<JointState> createJointStates(const FBXGeometry& geometry);
void initJointTransforms();
@ -325,6 +329,9 @@ private:
static SkinLocations _skinTranslucentLocations;
static void initSkinProgram(ProgramObject& program, SkinLocations& locations, int specularTextureUnit = 1);
QVector<AABox> _calculatedMeshBoxes;
bool _calculatedMeshBoxesValid;
};
Q_DECLARE_METATYPE(QPointer<Model>)

View file

@ -391,7 +391,7 @@ void Stats::display(
VoxelSystem* voxels = Application::getInstance()->getVoxels();
lines = _expanded ? 11 : 3;
lines = _expanded ? 14 : 3;
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) {
lines += 9; // spatial audio processing adds 1 spacing line and 8 extra lines of info
}
@ -415,6 +415,24 @@ void Stats::display(
horizontalOffset += 5;
if (_expanded) {
// Model/Entity render details
EntityTreeRenderer* entities = Application::getInstance()->getEntities();
voxelStats.str("");
voxelStats << "Entity Items rendered: " << entities->getItemsRendered() << " Out of view:" << entities->getItemsOutOfView();
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
voxelStats.str("");
voxelStats << "Meshes rendered: " << entities->getMeshesRendered() << " Out of view:" << entities->getMeshesOutOfView();
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
voxelStats.str("");
voxelStats << "Triangles: " << entities->getTrianglesRendered() << " Quads:" << entities->getQuadsRendered();
verticalOffset += STATS_PELS_PER_LINE;
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
// Local Voxel Memory Usage
voxelStats.str("");
voxelStats << "Voxels Memory Nodes: " << VoxelTreeElement::getTotalMemoryUsage() / 1000000.f << "MB";

View file

@ -162,12 +162,23 @@ bool OctreeRenderer::renderOperation(OctreeElement* element, void* extraData) {
}
void OctreeRenderer::render(RenderMode renderMode) {
RenderArgs args = { this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode, 0, 0, 0 };
RenderArgs args = { this, _viewFrustum, getSizeScale(), getBoundaryLevelAdjust(), renderMode, 0, 0, 0, 0, 0, 0, 0, 0 };
if (_tree) {
_tree->lockForRead();
_tree->recurseTreeWithOperation(renderOperation, &args);
_tree->unlock();
}
_meshesConsidered = args._meshesConsidered;
_meshesRendered = args._meshesRendered;
_meshesOutOfView = args._meshesOutOfView;
_elementsTouched = args._elementsTouched;
_itemsRendered = args._itemsRendered;
_itemsOutOfView = args._itemsOutOfView;
_trianglesRendered = args._trianglesRendered;
_quadsRendered = args._quadsRendered;
}
void OctreeRenderer::clear() {

View file

@ -63,10 +63,33 @@ public:
/// clears the tree
virtual void clear();
int getElementsTouched() const { return _elementsTouched; }
int getItemsRendered() const { return _itemsRendered; }
int getItemsOutOfView() const { return _itemsOutOfView; }
int getMeshesConsidered() const { return _meshesConsidered; }
int getMeshesRendered() const { return _meshesRendered; }
int getMeshesOutOfView() const { return _meshesOutOfView; }
int getTrianglesRendered() const { return _trianglesRendered; }
int getQuadsRendered() const { return _quadsRendered; }
protected:
Octree* _tree;
bool _managedTree;
ViewFrustum* _viewFrustum;
int _elementsTouched;
int _itemsRendered;
int _itemsOutOfView;
int _meshesConsidered;
int _meshesRendered;
int _meshesOutOfView;
int _trianglesRendered;
int _quadsRendered;
};
class RenderArgs {
@ -80,6 +103,13 @@ public:
int _elementsTouched;
int _itemsRendered;
int _itemsOutOfView;
int _meshesConsidered;
int _meshesRendered;
int _meshesOutOfView;
int _trianglesRendered;
int _quadsRendered;
};

View file

@ -88,13 +88,11 @@ inline bool operator==(const AABox& a, const AABox& b) {
}
inline QDebug operator<<(QDebug debug, const AABox& box) {
const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of the 0.0 to 1.0 voxel universe
debug << "AABox[ ("
<< box.getCorner().x * (float)TREE_SCALE << "," << box.getCorner().y * (float)TREE_SCALE << "," << box.getCorner().z * (float)TREE_SCALE << " ) to ("
<< box.calcTopFarLeft().x * (float)TREE_SCALE << "," << box.calcTopFarLeft().y * (float)TREE_SCALE << "," << box.calcTopFarLeft().z * (float)TREE_SCALE << ") size: ("
<< box.getDimensions().x * (float)TREE_SCALE << "," << box.getDimensions().y * (float)TREE_SCALE << "," << box.getDimensions().z * (float)TREE_SCALE << ")"
<< " in meters]";
<< box.getCorner().x << "," << box.getCorner().y << "," << box.getCorner().z << " ) to ("
<< box.calcTopFarLeft().x << "," << box.calcTopFarLeft().y << "," << box.calcTopFarLeft().z << ") size: ("
<< box.getDimensions().x << "," << box.getDimensions().y << "," << box.getDimensions().z << ")"
<< "]";
return debug;
}

View file

@ -79,12 +79,11 @@ inline bool operator==(const AACube& a, const AACube& b) {
}
inline QDebug operator<<(QDebug debug, const AACube& cube) {
const int TREE_SCALE = 16384; // ~10 miles.. This is the number of meters of the 0.0 to 1.0 voxel universe
debug << "AACube[ ("
<< cube.getCorner().x * (float)TREE_SCALE << "," << cube.getCorner().y * (float)TREE_SCALE << "," << cube.getCorner().z * (float)TREE_SCALE << " ) to ("
<< cube.calcTopFarLeft().x * (float)TREE_SCALE << "," << cube.calcTopFarLeft().y * (float)TREE_SCALE << "," << cube.calcTopFarLeft().z * (float)TREE_SCALE << ") size: ("
<< cube.getDimensions().x * (float)TREE_SCALE << "," << cube.getDimensions().y * (float)TREE_SCALE << "," << cube.getDimensions().z * (float)TREE_SCALE << ")"
<< " in meters]";
<< cube.getCorner().x << "," << cube.getCorner().y << "," << cube.getCorner().z << " ) to ("
<< cube.calcTopFarLeft().x << "," << cube.calcTopFarLeft().y << "," << cube.calcTopFarLeft().z << ") size: ("
<< cube.getDimensions().x << "," << cube.getDimensions().y << "," << cube.getDimensions().z << ")"
<< "]";
return debug;
}

View file

@ -15,6 +15,9 @@
#include <glm/glm.hpp>
#include <QDebug>
#include "StreamUtils.h"
class Extents {
public:
/// set minimum and maximum to FLT_MAX and -FLT_MAX respectively
@ -54,4 +57,15 @@ public:
glm::vec3 maximum;
};
inline QDebug operator<<(QDebug debug, const Extents& extents) {
debug << "Extents[ ("
<< extents.minimum << " ) to ("
<< extents.maximum << ") size: ("
<< (extents.maximum - extents.minimum) << ")"
<< " ]";
return debug;
}
#endif // hifi_Extents_h