소스 검색

Fix terrain occlusion leaving holes in the occlusion geometry, and being over-aggressive with height reduction, which would result in poor occlusion. Allow to configure terrain number of LOD levels to be less than 4. Allow to configure the LOD level used for terrain occlusion (not recommended to be changed.) Closes #825.

Lasse Öörni 10 년 전
부모
커밋
a737bbb671

+ 70 - 17
Source/Urho3D/Graphics/Terrain.cpp

@@ -89,6 +89,8 @@ Terrain::Terrain(Context* context) :
     patchSize_(DEFAULT_PATCH_SIZE),
     lastPatchSize_(0),
     numLodLevels_(1),
+    maxLodLevels_(MAX_LOD_LEVELS),
+    occlusionLodLevel_(M_MAX_UNSIGNED),
     smoothing_(false),
     visible_(true),
     castShadows_(false),
@@ -122,6 +124,7 @@ void Terrain::RegisterObject(Context* context)
         AM_DEFAULT);
     ATTRIBUTE("Vertex Spacing", Vector3, spacing_, DEFAULT_SPACING, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Patch Size", GetPatchSize, SetPatchSizeAttr, int, DEFAULT_PATCH_SIZE, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Max LOD Levels", GetMaxLodLevels, SetMaxLodLevelsAttr, unsigned, MAX_LOD_LEVELS, AM_DEFAULT);
     ATTRIBUTE("Smooth Height Map", bool, smoothing_, false, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Is Occluder", IsOccluder, SetOccluder, bool, false, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Can Be Occluded", IsOccludee, SetOccludee, bool, true, AM_DEFAULT);
@@ -134,6 +137,7 @@ void Terrain::RegisterObject(Context* context)
     ACCESSOR_ATTRIBUTE("Light Mask", GetLightMask, SetLightMask, unsigned, DEFAULT_LIGHTMASK, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Shadow Mask", GetShadowMask, SetShadowMask, unsigned, DEFAULT_SHADOWMASK, AM_DEFAULT);
     ACCESSOR_ATTRIBUTE("Zone Mask", GetZoneMask, SetZoneMask, unsigned, DEFAULT_ZONEMASK, AM_DEFAULT);
+    ACCESSOR_ATTRIBUTE("Occlusion LOD level", GetOcclusionLodLevel, SetOcclusionLodLevelAttr, unsigned, M_MAX_UNSIGNED, AM_DEFAULT);
 }
 
 void Terrain::OnSetAttribute(const AttributeInfo& attr, const Variant& src)
@@ -187,6 +191,31 @@ void Terrain::SetSpacing(const Vector3& spacing)
     }
 }
 
+void Terrain::SetMaxLodLevels(unsigned levels)
+{
+    levels = Clamp((int)levels, 1, MAX_LOD_LEVELS);
+    if (levels != maxLodLevels_)
+    {
+        maxLodLevels_ = levels;
+        lastPatchSize_ = 0; // Force full recreate
+        
+        CreateGeometry();
+        MarkNetworkUpdate();
+    }
+}
+
+void Terrain::SetOcclusionLodLevel(unsigned level)
+{
+    if (level != occlusionLodLevel_)
+    {
+        occlusionLodLevel_ = level;
+        lastPatchSize_ = 0; // Force full recreate
+        
+        CreateGeometry();
+        MarkNetworkUpdate();
+    }
+}
+
 void Terrain::SetSmoothing(bool enable)
 {
     if (enable != smoothing_)
@@ -468,7 +497,7 @@ void Terrain::CreatePatchGeometry(TerrainPatch* patch)
     VertexBuffer* vertexBuffer = patch->GetVertexBuffer();
     Geometry* geometry = patch->GetGeometry();
     Geometry* maxLodGeometry = patch->GetMaxLodGeometry();
-    Geometry* minLodGeometry = patch->GetMinLodGeometry();
+    Geometry* occlusionGeometry = patch->GetOcclusionGeometry();
 
     if (vertexBuffer->GetVertexCount() != row * row)
         vertexBuffer->SetSize(row * row, MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1 | MASK_TANGENT);
@@ -481,14 +510,15 @@ void Terrain::CreatePatchGeometry(TerrainPatch* patch)
     float* occlusionData = (float*)occlusionCpuVertexData.Get();
     BoundingBox box;
 
+    unsigned occlusionLevel = occlusionLodLevel_;
+    if (occlusionLevel > numLodLevels_ - 1)
+        occlusionLevel = numLodLevels_ - 1;
+
     if (vertexData)
     {
         const IntVector2& coords = patch->GetCoordinates();
-        int patchMinX = coords.x_ * patchSize_;
-        int patchMaxX = (coords.x_ + 1) * patchSize_;
-        int patchMinZ = coords.y_ * patchSize_;
-        int patchMaxZ = (coords.y_ + 1) * patchSize_;
-        int lodExpand = (1 << (numLodLevels_ - 1)) - 1;
+        int lodExpand = (1 << (occlusionLevel)) - 1;
+        int halfLodExpand = (1 << (occlusionLevel)) / 2;
         
         for (int z = 0; z <= patchSize_; ++z)
         {
@@ -508,14 +538,15 @@ void Terrain::CreatePatchGeometry(TerrainPatch* patch)
 
                 box.Merge(position);
                 
-                // For vertices that are part of the lowest LOD, calculate the minimum height in the neighborhood for occlusion
+                // For vertices that are part of the occlusion LOD, calculate the minimum height in the neighborhood
+                // to prevent false positive occlusion due to inaccuracy between occlusion LOD & visible LOD
                 float minHeight = position.y_;
-                if (lodExpand > 0 && (x & lodExpand) == 0 && (z & lodExpand) == 0)
+                if (halfLodExpand > 0 && (x & lodExpand) == 0 && (z & lodExpand) == 0)
                 {
-                    int minX = Max(xPos - lodExpand, patchMinX);
-                    int maxX = Min(xPos + lodExpand, patchMaxX);
-                    int minZ = Max(zPos - lodExpand, patchMinZ);
-                    int maxZ = Min(zPos + lodExpand, patchMaxZ);
+                    int minX = Max(xPos - halfLodExpand, 0);
+                    int maxX = Min(xPos + halfLodExpand, numVertices_.x_ - 1);
+                    int minZ = Max(zPos - halfLodExpand, 0);
+                    int maxZ = Min(zPos + halfLodExpand, numVertices_.y_ - 1);
                     for (int nZ = minZ; nZ <= maxZ; ++nZ)
                     {
                         for (int nX = minX; nX <= maxX; ++nX)
@@ -554,7 +585,7 @@ void Terrain::CreatePatchGeometry(TerrainPatch* patch)
 
     if (drawRanges_.Size())
     {
-        unsigned lastDrawRange = drawRanges_.Size() - 1;
+        unsigned occlusionDrawRange = occlusionLevel << 4;
 
         geometry->SetIndexBuffer(indexBuffer_);
         geometry->SetDrawRange(TRIANGLE_LIST, drawRanges_[0].first_, drawRanges_[0].second_, false);
@@ -562,9 +593,9 @@ void Terrain::CreatePatchGeometry(TerrainPatch* patch)
         maxLodGeometry->SetIndexBuffer(indexBuffer_);
         maxLodGeometry->SetDrawRange(TRIANGLE_LIST, drawRanges_[0].first_, drawRanges_[0].second_, false);
         maxLodGeometry->SetRawVertexData(cpuVertexData, sizeof(Vector3), MASK_POSITION);
-        minLodGeometry->SetIndexBuffer(indexBuffer_);
-        minLodGeometry->SetDrawRange(TRIANGLE_LIST, drawRanges_[lastDrawRange].first_, drawRanges_[lastDrawRange].second_, false);
-        minLodGeometry->SetRawVertexData(occlusionCpuVertexData, sizeof(Vector3), MASK_POSITION);
+        occlusionGeometry->SetIndexBuffer(indexBuffer_);
+        occlusionGeometry->SetDrawRange(TRIANGLE_LIST, drawRanges_[occlusionDrawRange].first_, drawRanges_[occlusionDrawRange].second_, false);
+        occlusionGeometry->SetRawVertexData(occlusionCpuVertexData, sizeof(Vector3), MASK_POSITION);
     }
 
     patch->ResetLod();
@@ -623,6 +654,28 @@ void Terrain::SetPatchSizeAttr(int value)
     }
 }
 
+void Terrain::SetMaxLodLevelsAttr(unsigned value)
+{
+    value = Clamp((int)value, 1, MAX_LOD_LEVELS);
+    
+    if (value != maxLodLevels_)
+    {
+        maxLodLevels_ = value;
+        lastPatchSize_ = 0; // Force full recreate
+        recreateTerrain_ = true;
+    }
+}
+
+void Terrain::SetOcclusionLodLevelAttr(unsigned value)
+{
+    if (value != occlusionLodLevel_)
+    {
+        occlusionLodLevel_ = value;
+        lastPatchSize_ = 0; // Force full recreate
+        recreateTerrain_ = true;
+    }
+}
+
 ResourceRef Terrain::GetMaterialAttr() const
 {
     return GetResourceRef(material_, Material::GetTypeStatic());
@@ -647,7 +700,7 @@ void Terrain::CreateGeometry()
     // Determine number of LOD levels
     unsigned lodSize = (unsigned)patchSize_;
     numLodLevels_ = 1;
-    while (lodSize > MIN_PATCH_SIZE && numLodLevels_ < MAX_LOD_LEVELS)
+    while (lodSize > MIN_PATCH_SIZE && numLodLevels_ < maxLodLevels_)
     {
         lodSize >>= 1;
         ++numLodLevels_;

+ 19 - 1
Source/Urho3D/Graphics/Terrain.h

@@ -57,6 +57,10 @@ public:
     void SetPatchSize(int size);
     /// Set vertex (XZ) and height (Y) spacing.
     void SetSpacing(const Vector3& spacing);
+    /// Set maximum number of LOD levels for terrain patches. This can be between 1-4.
+    void SetMaxLodLevels(unsigned levels);
+    /// Set LOD level used for terrain patch occlusion. By default (M_MAX_UNSIGNED) the coarsest. Since the LOD level used needs to be fixed, using finer LOD levels may result in false positive occlusion in cases where the actual rendered geometry is coarser, so use with caution.
+    void SetOcclusionLodLevel(unsigned level);
     /// Set smoothing of heightmap.
     void SetSmoothing(bool enable);
     /// Set heightmap image. Dimensions should be a power of two + 1. Uses 8-bit grayscale, or optionally red as MSB and green as LSB for 16-bit accuracy. Return true if successful.
@@ -81,7 +85,7 @@ public:
     void SetMaxLights(unsigned num);
     /// Set shadowcaster flag for patches.
     void SetCastShadows(bool enable);
-    /// Set occlusion flag for patches. Occlusion uses the coarsest LOD and may potentially be too aggressive, so use with caution.
+    /// Set occlusion flag for patches. Occlusion uses the coarsest LOD by default.
     void SetOccluder(bool enable);
     /// Set occludee flag for patches.
     void SetOccludee(bool enable);
@@ -100,6 +104,12 @@ public:
     /// Return heightmap size in patches.
     const IntVector2& GetNumPatches() const { return numPatches_; }
 
+    /// Return maximum number of LOD levels for terrain patches. This can be between 1-4.
+    unsigned GetMaxLodLevels() const { return maxLodLevels_; }
+    
+    /// Return LOD level used for occlusion.
+    unsigned GetOcclusionLodLevel() const { return occlusionLodLevel_; }
+    
     /// Return whether smoothing is in use.
     bool GetSmoothing() const { return smoothing_; }
 
@@ -167,6 +177,10 @@ public:
     void SetMaterialAttr(const ResourceRef& value);
     /// Set patch size attribute.
     void SetPatchSizeAttr(int value);
+    /// Set max LOD levels attribute.
+    void SetMaxLodLevelsAttr(unsigned value);
+    /// Set occlusion LOD level attribute.
+    void SetOcclusionLodLevelAttr(unsigned value);
     /// Return heightmap attribute.
     ResourceRef GetHeightMapAttr() const;
     /// Return material attribute.
@@ -228,6 +242,10 @@ private:
     int lastPatchSize_;
     /// Number of terrain LOD levels.
     unsigned numLodLevels_;
+    /// Maximum number of LOD levels.
+    unsigned maxLodLevels_;
+    /// LOD level used for occlusion.
+    unsigned occlusionLodLevel_;
     /// Smoothing enable flag.
     bool smoothing_;
     /// Visible flag.

+ 10 - 10
Source/Urho3D/Graphics/TerrainPatch.cpp

@@ -24,6 +24,7 @@
 
 #include "../Core/Context.h"
 #include "../Graphics/Camera.h"
+#include "../Graphics/DebugRenderer.h"
 #include "../Graphics/Geometry.h"
 #include "../Graphics/IndexBuffer.h"
 #include "../Graphics/Material.h"
@@ -48,14 +49,14 @@ TerrainPatch::TerrainPatch(Context* context) :
     Drawable(context, DRAWABLE_GEOMETRY),
     geometry_(new Geometry(context)),
     maxLodGeometry_(new Geometry(context)),
-    minLodGeometry_(new Geometry(context)),
+    occlusionGeometry_(new Geometry(context)),
     vertexBuffer_(new VertexBuffer(context)),
     coordinates_(IntVector2::ZERO),
     lodLevel_(0)
 {
     geometry_->SetVertexBuffer(0, vertexBuffer_, MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1 | MASK_TANGENT);
     maxLodGeometry_->SetVertexBuffer(0, vertexBuffer_, MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1 | MASK_TANGENT);
-    minLodGeometry_->SetVertexBuffer(0, vertexBuffer_, MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1 | MASK_TANGENT);
+    occlusionGeometry_->SetVertexBuffer(0, vertexBuffer_, MASK_POSITION | MASK_NORMAL | MASK_TEXCOORD1 | MASK_TANGENT);
 
     batches_.Resize(1);
     batches_[0].geometry_ = geometry_;
@@ -175,7 +176,7 @@ unsigned TerrainPatch::GetNumOccluderTriangles()
     if (mat && !mat->GetOcclusion())
         return 0;
     else
-        return minLodGeometry_->GetIndexCount() / 3;
+        return occlusionGeometry_->GetIndexCount() / 3;
 }
 
 bool TerrainPatch::DrawOcclusion(OcclusionBuffer* buffer)
@@ -197,22 +198,21 @@ bool TerrainPatch::DrawOcclusion(OcclusionBuffer* buffer)
     unsigned indexSize;
     unsigned elementMask;
 
-    minLodGeometry_->GetRawData(vertexData, vertexSize, indexData, indexSize, elementMask);
+    occlusionGeometry_->GetRawData(vertexData, vertexSize, indexData, indexSize, elementMask);
     // Check for valid geometry data
     if (!vertexData || !indexData)
         return true;
 
     // Draw and check for running out of triangles
-    return buffer->Draw(node_->GetWorldTransform(), vertexData, vertexSize, indexData, indexSize, minLodGeometry_->GetIndexStart(),
-        minLodGeometry_->GetIndexCount());
+    return buffer->Draw(node_->GetWorldTransform(), vertexData, vertexSize, indexData, indexSize, occlusionGeometry_->GetIndexStart(),
+        occlusionGeometry_->GetIndexCount());
 }
 
 void TerrainPatch::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
-    // Intentionally no action
+    // Intentionally no operation
 }
 
-
 void TerrainPatch::SetOwner(Terrain* terrain)
 {
     owner_ = terrain;
@@ -257,9 +257,9 @@ Geometry* TerrainPatch::GetMaxLodGeometry() const
     return maxLodGeometry_;
 }
 
-Geometry* TerrainPatch::GetMinLodGeometry() const
+Geometry* TerrainPatch::GetOcclusionGeometry() const
 {
-    return minLodGeometry_;
+    return occlusionGeometry_;
 }
 
 VertexBuffer* TerrainPatch::GetVertexBuffer() const

+ 5 - 5
Source/Urho3D/Graphics/TerrainPatch.h

@@ -76,10 +76,10 @@ public:
 
     /// Return visible geometry.
     Geometry* GetGeometry() const;
-    /// Return max LOD geometry.
+    /// Return max LOD geometry. Used for decals.
     Geometry* GetMaxLodGeometry() const;
-    /// Return min LOD geometry.
-    Geometry* GetMinLodGeometry() const;
+    /// Return geometry used for occlusion.
+    Geometry* GetOcclusionGeometry() const;
     /// Return vertex buffer.
     VertexBuffer* GetVertexBuffer() const;
     /// Return owner terrain.
@@ -118,8 +118,8 @@ private:
     SharedPtr<Geometry> geometry_;
     /// Geometry that is locked to the max LOD level. Used for decals.
     SharedPtr<Geometry> maxLodGeometry_;
-    /// Geometry that is locked to the minimum LOD level. Used for occlusion.
-    SharedPtr<Geometry> minLodGeometry_;
+    /// Geometry that is used for occlusion.
+    SharedPtr<Geometry> occlusionGeometry_;
     /// Vertex buffer.
     SharedPtr<VertexBuffer> vertexBuffer_;
     /// Parent terrain.

+ 6 - 0
Source/Urho3D/LuaScript/pkgs/Graphics/Terrain.pkg

@@ -4,6 +4,8 @@ class Terrain : public Component
 {
     void SetPatchSize(int size);
     void SetSpacing(const Vector3& spacing);
+    void SetMaxLodLevels(unsigned levels);
+    void SetOcclusionLodLevel(unsigned level);
     void SetSmoothing(bool enable);
     bool SetHeightMap(Image* image);
     void SetMaterial(Material* material);
@@ -24,6 +26,8 @@ class Terrain : public Component
     const Vector3& GetSpacing() const;
     const IntVector2& GetNumVertices() const;
     const IntVector2& GetNumPatches() const;
+    unsigned GetMaxLodLevels() const;
+    unsigned GetOcclusionLodLevel() const;
     bool GetSmoothing() const;
     Image* GetHeightMap() const;
     Material* GetMaterial() const;
@@ -50,6 +54,8 @@ class Terrain : public Component
     tolua_property__get_set Vector3& spacing;
     tolua_readonly tolua_property__get_set IntVector2& numVertices;
     tolua_readonly tolua_property__get_set IntVector2& numPatches;
+    tolua_property__get_set unsigned maxLodLevels;
+    tolua_property__get_set unsigned occlusionLodLevel;
     tolua_property__get_set bool smoothing;
     tolua_property__get_set Image* heightMap;
     tolua_property__get_set Material* material;

+ 2 - 2
Source/Urho3D/LuaScript/pkgs/Graphics/TerrainPatch.pkg

@@ -11,7 +11,7 @@ class TerrainPatch : public Drawable
     
     Geometry* GetGeometry() const;
     Geometry* GetMaxLodGeometry() const;
-    Geometry* GetMinLodGeometry() const;
+    Geometry* GetOcclusionGeometry() const;
     VertexBuffer* GetVertexBuffer() const;
     Terrain* GetOwner() const;
     TerrainPatch* GetNorthPatch() const;
@@ -23,7 +23,7 @@ class TerrainPatch : public Drawable
 
     tolua_readonly tolua_property__get_set Geometry* geometry;
     tolua_readonly tolua_property__get_set Geometry* maxLodGeometry;
-    tolua_readonly tolua_property__get_set Geometry* minLodGeometry;
+    tolua_readonly tolua_property__get_set Geometry* occlusionGeometry;
     tolua_readonly tolua_property__get_set VertexBuffer* vertexBuffer;
     tolua_property__get_set Terrain* owner;
     tolua_readonly tolua_property__get_set TerrainPatch* northPatch;

+ 4 - 0
Source/Urho3D/Script/GraphicsAPI.cpp

@@ -1431,6 +1431,10 @@ static void RegisterTerrain(asIScriptEngine* engine)
     engine->RegisterObjectMethod("Terrain", "IntVector2 WorldToHeightMap(const Vector3&in) const", asMETHOD(Terrain, WorldToHeightMap), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "void set_material(Material@+)", asMETHOD(Terrain, SetMaterial), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "Material@+ get_material() const", asMETHOD(Terrain, GetMaterial), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "void set_maxLodLevels(uint)", asMETHOD(Terrain, SetMaxLodLevels), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "uint get_maxLodLevels() const", asMETHOD(Terrain, GetMaxLodLevels), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "void set_occlusionLodLevel(uint)", asMETHOD(Terrain, SetOcclusionLodLevel), asCALL_THISCALL);
+    engine->RegisterObjectMethod("Terrain", "uint get_occlusionLodLevel() const", asMETHOD(Terrain, GetOcclusionLodLevel), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "void set_smoothing(bool)", asMETHOD(Terrain, SetSmoothing), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "bool get_smoothing() const", asMETHOD(Terrain, GetSmoothing), asCALL_THISCALL);
     engine->RegisterObjectMethod("Terrain", "void set_heightMap(Image@+)", asMETHOD(Terrain, SetHeightMap), asCALL_THISCALL);