Browse Source

Added StaticObjectGroup component for optimizing culling, light and drawcall processing. Update HugeObjectCount to use it. Not yet exposed to script.

Lasse Öörni 12 years ago
parent
commit
168b69c09d

+ 1 - 1
Source/Engine/Graphics/BillboardSet.h

@@ -34,7 +34,7 @@ class IndexBuffer;
 class VertexBuffer;
 class VertexBuffer;
 
 
 /// One billboard in the billboard set.
 /// One billboard in the billboard set.
-struct Billboard
+struct URHO3D_API Billboard
 {
 {
     /// Position.
     /// Position.
     Vector3 position_;
     Vector3 position_;

+ 2 - 0
Source/Engine/Graphics/Direct3D9/D3D9Graphics.cpp

@@ -42,6 +42,7 @@
 #include "Shader.h"
 #include "Shader.h"
 #include "ShaderVariation.h"
 #include "ShaderVariation.h"
 #include "Skybox.h"
 #include "Skybox.h"
+#include "StaticModelGroup.h"
 #include "StringUtils.h"
 #include "StringUtils.h"
 #include "Technique.h"
 #include "Technique.h"
 #include "Terrain.h"
 #include "Terrain.h"
@@ -2448,6 +2449,7 @@ void RegisterGraphicsLibrary(Context* context)
     Drawable::RegisterObject(context);
     Drawable::RegisterObject(context);
     Light::RegisterObject(context);
     Light::RegisterObject(context);
     StaticModel::RegisterObject(context);
     StaticModel::RegisterObject(context);
+    StaticModelGroup::RegisterObject(context);
     Skybox::RegisterObject(context);
     Skybox::RegisterObject(context);
     AnimatedModel::RegisterObject(context);
     AnimatedModel::RegisterObject(context);
     AnimationController::RegisterObject(context);
     AnimationController::RegisterObject(context);

+ 2 - 0
Source/Engine/Graphics/OpenGL/OGLGraphics.cpp

@@ -46,6 +46,7 @@
 #include "ShaderProgram.h"
 #include "ShaderProgram.h"
 #include "ShaderVariation.h"
 #include "ShaderVariation.h"
 #include "Skybox.h"
 #include "Skybox.h"
+#include "StaticModelGroup.h"
 #include "StringUtils.h"
 #include "StringUtils.h"
 #include "Technique.h"
 #include "Technique.h"
 #include "Terrain.h"
 #include "Terrain.h"
@@ -2706,6 +2707,7 @@ void RegisterGraphicsLibrary(Context* context)
     Drawable::RegisterObject(context);
     Drawable::RegisterObject(context);
     Light::RegisterObject(context);
     Light::RegisterObject(context);
     StaticModel::RegisterObject(context);
     StaticModel::RegisterObject(context);
+    StaticModelGroup::RegisterObject(context);
     Skybox::RegisterObject(context);
     Skybox::RegisterObject(context);
     AnimatedModel::RegisterObject(context);
     AnimatedModel::RegisterObject(context);
     AnimationController::RegisterObject(context);
     AnimationController::RegisterObject(context);

+ 171 - 0
Source/Engine/Graphics/StaticModelGroup.cpp

@@ -0,0 +1,171 @@
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "Precompiled.h"
+#include "Batch.h"
+#include "Camera.h"
+#include "Context.h"
+#include "Scene.h"
+#include "SceneEvents.h"
+#include "StaticModelGroup.h"
+
+#include "DebugNew.h"
+
+namespace Urho3D
+{
+
+extern const char* GEOMETRY_CATEGORY;
+
+StaticModelGroup::StaticModelGroup(Context* context) :
+    StaticModel(context),
+    transformsDirty_(true)
+{
+}
+
+StaticModelGroup::~StaticModelGroup()
+{
+}
+
+void StaticModelGroup::RegisterObject(Context* context)
+{
+    context->RegisterFactory<StaticModelGroup>(GEOMETRY_CATEGORY);
+
+    COPY_BASE_ATTRIBUTES(StaticModelGroup, StaticModel);
+    /// \todo Define an attribute for instance node ID's
+}
+
+void StaticModelGroup::UpdateBatches(const FrameInfo& frame)
+{
+    // Getting the world bounding box ensures the transforms are updated
+    const BoundingBox& worldBoundingBox = GetWorldBoundingBox();
+    const Matrix3x4& worldTransform = node_->GetWorldTransform();
+    distance_ = frame.camera_->GetDistance(worldBoundingBox.Center());
+    
+    if (batches_.Size() > 1)
+    {
+        for (unsigned i = 0; i < batches_.Size(); ++i)
+        {
+            batches_[i].distance_ = frame.camera_->GetDistance(worldTransform * geometryData_[i].center_);
+            batches_[i].worldTransform_ = worldTransforms_.Size() ? &worldTransforms_[0] : &Matrix3x4::IDENTITY;
+            batches_[i].numWorldTransforms_ = worldTransforms_.Size();
+        }
+    }
+    else if (batches_.Size() == 1)
+    {
+        batches_[0].distance_ = distance_;
+        batches_[0].worldTransform_ = worldTransforms_.Size() ? &worldTransforms_[0] : &Matrix3x4::IDENTITY;
+        batches_[0].numWorldTransforms_ = worldTransforms_.Size();
+    }
+    
+    float scale = worldBoundingBox.Size().DotProduct(DOT_SCALE);
+    float newLodDistance = frame.camera_->GetLodDistance(distance_, scale, lodBias_);
+    
+    if (newLodDistance != lodDistance_)
+    {
+        lodDistance_ = newLodDistance;
+        CalculateLodLevels();
+    }
+}
+
+void StaticModelGroup::AddInstanceNode(Node* node)
+{
+    if (!node)
+        return;
+
+    WeakPtr<Node> instanceWeak(node);
+    if (instanceNodes_.Contains(instanceWeak))
+        return;
+    
+    // Add as a transform listener, so that we know to dirty the instance transforms
+    node->AddListener(this);
+    instanceNodes_.Push(instanceWeak);
+}
+
+void StaticModelGroup::RemoveInstanceNode(Node* node)
+{
+    if (!node)
+        return;
+
+    WeakPtr<Node> instanceWeak(node);
+    node->RemoveListener(this);
+    instanceNodes_.Remove(instanceWeak);
+}
+
+Node* StaticModelGroup::GetInstanceNode(unsigned index) const
+{
+    return index < instanceNodes_.Size() ? instanceNodes_[index] : (Node*)0;
+}
+
+void StaticModelGroup::OnNodeSet(Node* node)
+{
+    Drawable::OnNodeSet(node);
+
+    if (node)
+    {
+        Scene* scene = GetScene();
+        /// \todo It is non-optimal to listen to enabled/disabled change events from the whole scene. The nodes themselves should send an event
+        if (scene)
+            SubscribeToEvent(scene, E_NODEENABLEDCHANGED, HANDLER(StaticModelGroup, HandleNodeEnabledChanged));
+    }
+}
+
+void StaticModelGroup::OnMarkedDirty(Node* node)
+{
+    Drawable::OnMarkedDirty(node);
+    transformsDirty_ = true;
+}
+
+void StaticModelGroup::OnWorldBoundingBoxUpdate()
+{
+    if (transformsDirty_)
+        UpdateTransforms();
+    
+    worldBoundingBox_.defined_ = false;
+    for (unsigned i = 0; i < worldTransforms_.Size(); ++i)
+        worldBoundingBox_.Merge(boundingBox_.Transformed(worldTransforms_[i]));
+}
+
+void StaticModelGroup::UpdateTransforms()
+{
+    worldTransforms_.Clear();
+
+    for (unsigned i = 0; i < instanceNodes_.Size(); ++i)
+    {
+        Node* instanceNode = instanceNodes_[i];
+        if (!instanceNode || !instanceNode->IsEnabled())
+            continue;
+        worldTransforms_.Push(instanceNode->GetWorldTransform());
+    }
+    
+    transformsDirty_ = false;
+}
+
+void StaticModelGroup::HandleNodeEnabledChanged(StringHash eventType, VariantMap& eventData)
+{
+    using namespace NodeEnabledChanged;
+
+    WeakPtr<Node> instanceWeak((Node*)eventData[P_NODE].GetPtr());
+    if (instanceNodes_.Contains(instanceWeak))
+        OnMarkedDirty(instanceWeak);
+}
+
+}

+ 84 - 0
Source/Engine/Graphics/StaticModelGroup.h

@@ -0,0 +1,84 @@
+//
+// Copyright (c) 2008-2013 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "StaticModel.h"
+
+namespace Urho3D
+{
+
+/// Renders several instances of static models while culling and receiving light as one unit. Using this class is not necessary for instanced rendering, but can be used as a CPU-side optimization.
+class URHO3D_API StaticModelGroup : public StaticModel
+{
+    OBJECT(StaticModelGroup);
+    
+public:
+    /// Construct.
+    StaticModelGroup(Context* context);
+    /// Destruct.
+    virtual ~StaticModelGroup();
+    /// Register object factory. StaticModel must be registered first.
+    static void RegisterObject(Context* context);
+    
+    /// Process octree raycast. May be called from a worker thread.
+    //virtual void ProcessRayQuery(const RayOctreeQuery& query, PODVector<RayQueryResult>& results);
+    /// Calculate distance and prepare batches for rendering. May be called from worker thread(s), possibly re-entrantly.
+    virtual void UpdateBatches(const FrameInfo& frame);
+    /// Return number of occlusion geometry triangles.
+    //virtual unsigned GetNumOccluderTriangles();
+    /// Draw to occlusion buffer. Return true if did not run out of triangles.
+    //virtual bool DrawOcclusion(OcclusionBuffer* buffer);
+    
+    /// Add an instance scene node. It does not need any drawable components of its own.
+    void AddInstanceNode(Node* node);
+    /// Remove an instance scene node.
+    void RemoveInstanceNode(Node* node);
+    
+    /// Return number of instance nodes.
+    unsigned GetNumInstanceNodes() const { return instanceNodes_.Size(); }
+    /// Return instance node by index.
+    Node* GetInstanceNode(unsigned index) const;
+    
+protected:
+    /// Handle node being assigned.
+    virtual void OnNodeSet(Node* node);
+   /// Handle node transform being dirtied.
+    virtual void OnMarkedDirty(Node* node);
+    /// Recalculate the world-space bounding box.
+    virtual void OnWorldBoundingBoxUpdate();
+    
+private:
+    /// Gather the instances' transforms.
+    void UpdateTransforms();
+    /// Handle an instance node being enabled/disabled.
+    void HandleNodeEnabledChanged(StringHash eventType, VariantMap& eventData);
+    
+    /// Instance nodes.
+    Vector<WeakPtr<Node> > instanceNodes_;
+    /// World transforms of enabled instances.
+    PODVector<Matrix3x4> worldTransforms_;
+    /// Instance transforms dirty flag.
+    bool transformsDirty_;
+};
+
+}

+ 3 - 3
Source/Engine/Graphics/View.cpp

@@ -878,7 +878,7 @@ void View::GetBatches()
                             const SourceBatch& srcBatch = batches[l];
                             const SourceBatch& srcBatch = batches[l];
                             
                             
                             Technique* tech = GetTechnique(drawable, srcBatch.material_);
                             Technique* tech = GetTechnique(drawable, srcBatch.material_);
-                            if (!srcBatch.geometry_ || !tech)
+                            if (!srcBatch.geometry_ || !srcBatch.numWorldTransforms_ || !tech)
                                 continue;
                                 continue;
                             
                             
                             Pass* pass = tech->GetPass(PASS_SHADOW);
                             Pass* pass = tech->GetPass(PASS_SHADOW);
@@ -987,7 +987,7 @@ void View::GetBatches()
                     CheckMaterialForAuxView(srcBatch.material_);
                     CheckMaterialForAuxView(srcBatch.material_);
                 
                 
                 Technique* tech = GetTechnique(drawable, srcBatch.material_);
                 Technique* tech = GetTechnique(drawable, srcBatch.material_);
-                if (!srcBatch.geometry_ || !tech)
+                if (!srcBatch.geometry_ || !srcBatch.numWorldTransforms_ || !tech)
                     continue;
                     continue;
                 
                 
                 Batch destBatch(srcBatch);
                 Batch destBatch(srcBatch);
@@ -1164,7 +1164,7 @@ void View::GetLitBatches(Drawable* drawable, LightBatchQueue& lightQueue, BatchQ
         const SourceBatch& srcBatch = batches[i];
         const SourceBatch& srcBatch = batches[i];
         
         
         Technique* tech = GetTechnique(drawable, srcBatch.material_);
         Technique* tech = GetTechnique(drawable, srcBatch.material_);
-        if (!srcBatch.geometry_ || !tech)
+        if (!srcBatch.geometry_ || !srcBatch.numWorldTransforms_ || !tech)
             continue;
             continue;
         
         
         // Do not create pixel lit forward passes for materials that render into the G-buffer
         // Do not create pixel lit forward passes for materials that render into the G-buffer

+ 68 - 20
Source/Samples/20_HugeObjectCount/HugeObjectCount.cpp

@@ -33,7 +33,7 @@
 #include "Renderer.h"
 #include "Renderer.h"
 #include "ResourceCache.h"
 #include "ResourceCache.h"
 #include "Scene.h"
 #include "Scene.h"
-#include "StaticModel.h"
+#include "StaticModelGroup.h"
 #include "Text.h"
 #include "Text.h"
 #include "UI.h"
 #include "UI.h"
 #include "Zone.h"
 #include "Zone.h"
@@ -48,7 +48,8 @@ HugeObjectCount::HugeObjectCount(Context* context) :
     Sample(context),
     Sample(context),
     yaw_(0.0f),
     yaw_(0.0f),
     pitch_(0.0f),
     pitch_(0.0f),
-    animate_(false)
+    animate_(false),
+    useGroups_(false)
 {
 {
 }
 }
 
 
@@ -65,7 +66,7 @@ void HugeObjectCount::Start()
     
     
     // Setup the viewport for displaying the scene
     // Setup the viewport for displaying the scene
     SetupViewport();
     SetupViewport();
-
+    
     // Hook up to the frame update events
     // Hook up to the frame update events
     SubscribeToEvents();
     SubscribeToEvents();
 }
 }
@@ -74,7 +75,13 @@ void HugeObjectCount::CreateScene()
 {
 {
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     ResourceCache* cache = GetSubsystem<ResourceCache>();
     
     
-    scene_ = new Scene(context_);
+    if (!scene_)
+        scene_ = new Scene(context_);
+    else
+    {
+        scene_->Clear();
+        boxNodes_.Clear();
+    }
     
     
     // Create the Octree component to the scene so that drawable objects can be rendered. Use default volume
     // Create the Octree component to the scene so that drawable objects can be rendered. Use default volume
     // (-1000, -1000, -1000) to (1000, 1000, 1000)
     // (-1000, -1000, -1000) to (1000, 1000, 1000)
@@ -95,25 +102,58 @@ void HugeObjectCount::CreateScene()
     light->SetLightType(LIGHT_DIRECTIONAL);
     light->SetLightType(LIGHT_DIRECTIONAL);
     light->SetColor(Color(0.7f, 0.35f, 0.0f));
     light->SetColor(Color(0.7f, 0.35f, 0.0f));
 
 
-    // Create box StaticModels in the scene
-    for (int y = -125; y < 125; ++y)
+    if (!useGroups_)
     {
     {
-        for (int x = -125; x < 125; ++x)
+        // Create individual box StaticModels in the scene
+        for (int y = -125; y < 125; ++y)
         {
         {
-            Node* boxNode = scene_->CreateChild("Box");
-            boxNode->SetPosition(Vector3(x * 0.3f, 0.0f, y * 0.3f));
-            boxNode->SetScale(0.25f);
-            StaticModel* boxObject = boxNode->CreateComponent<StaticModel>();
-            boxObject->SetModel(cache->GetResource<Model>("Models/Box.mdl"));
-            boxNodes_.Push(SharedPtr<Node>(boxNode));
+            for (int x = -125; x < 125; ++x)
+            {
+                Node* boxNode = scene_->CreateChild("Box");
+                boxNode->SetPosition(Vector3(x * 0.3f, 0.0f, y * 0.3f));
+                boxNode->SetScale(0.25f);
+                StaticModel* boxObject = boxNode->CreateComponent<StaticModel>();
+                boxObject->SetModel(cache->GetResource<Model>("Models/Box.mdl"));
+                boxNodes_.Push(SharedPtr<Node>(boxNode));
+            }
         }
         }
     }
     }
-    
-    // Create the camera
-    cameraNode_ = scene_->CreateChild("Camera");
-    cameraNode_->SetPosition(Vector3(0.0f, 10.0f, -100.0f));
-    Camera* camera = cameraNode_->CreateComponent<Camera>();
-    camera->SetFarClip(300.0f);
+    else
+    {
+        // Create StaticModelGroups in the scene
+        StaticModelGroup* lastGroup = 0;
+
+        for (int y = -125; y < 125; ++y)
+        {
+            for (int x = -125; x < 125; ++x)
+            {
+                // Create new group if no group yet, or the group has already "enough" objects. The tradeoff is between culling
+                // accuracy and the amount of CPU processing needed for all the objects. Note that the group's own transform
+                // does not matter, and it does not render anything if instance nodes are not added to it
+                if (!lastGroup || lastGroup->GetNumInstanceNodes() >= 25* 25)
+                {
+                    Node* boxGroupNode = scene_->CreateChild("BoxGroup");
+                    lastGroup = boxGroupNode->CreateComponent<StaticModelGroup>();
+                    lastGroup->SetModel(cache->GetResource<Model>("Models/Box.mdl"));
+                }
+                
+                Node* boxNode = scene_->CreateChild("Box");
+                boxNode->SetPosition(Vector3(x * 0.3f, 0.0f, y * 0.3f));
+                boxNode->SetScale(0.25f);
+                boxNodes_.Push(SharedPtr<Node>(boxNode));
+                lastGroup->AddInstanceNode(boxNode);
+            }
+        }
+    }
+
+    // Create the camera. Create it outside the scene so that we can clear the whole scene without affecting it
+    if (!cameraNode_)
+    {
+        cameraNode_ = new Node(context_);
+        cameraNode_->SetPosition(Vector3(0.0f, 10.0f, -100.0f));
+        Camera* camera = cameraNode_->CreateComponent<Camera>();
+        camera->SetFarClip(300.0f);
+    }
 }
 }
 
 
 void HugeObjectCount::CreateInstructions()
 void HugeObjectCount::CreateInstructions()
@@ -125,7 +165,8 @@ void HugeObjectCount::CreateInstructions()
     Text* instructionText = ui->GetRoot()->CreateChild<Text>();
     Text* instructionText = ui->GetRoot()->CreateChild<Text>();
     instructionText->SetText(
     instructionText->SetText(
         "Use WASD keys and mouse to move\n"
         "Use WASD keys and mouse to move\n"
-        "Space to toggle animation"
+        "Space to toggle animation\n"
+        "G to toggle object group optimization\n"
     );
     );
     instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
     instructionText->SetFont(cache->GetResource<Font>("Fonts/Anonymous Pro.ttf"), 15);
     // The text has multiple rows. Center them in relation to each other
     // The text has multiple rows. Center them in relation to each other
@@ -209,6 +250,13 @@ void HugeObjectCount::HandleUpdate(StringHash eventType, VariantMap& eventData)
     if (input->GetKeyPress(KEY_SPACE))
     if (input->GetKeyPress(KEY_SPACE))
         animate_ = !animate_;
         animate_ = !animate_;
 
 
+    // Toggle grouped / ungrouped mode
+    if (input->GetKeyPress('G'))
+    {
+        useGroups_ = !useGroups_;
+        CreateScene();
+    }
+
     // Move the camera, scale movement with time step
     // Move the camera, scale movement with time step
     MoveCamera(timeStep);
     MoveCamera(timeStep);
     
     

+ 2 - 0
Source/Samples/20_HugeObjectCount/HugeObjectCount.h

@@ -77,4 +77,6 @@ private:
     float pitch_;
     float pitch_;
     /// Animation flag.
     /// Animation flag.
     bool animate_;
     bool animate_;
+    /// Group optimization flag.
+    bool useGroups_;
 };
 };