]> git.mxchange.org Git - simgear.git/commitdiff
Change trees code to use a faster OpenGL path
authortimoore <timoore>
Tue, 25 Aug 2009 07:19:39 +0000 (07:19 +0000)
committerTim Moore <timoore@redhat.com>
Tue, 25 Aug 2009 14:14:13 +0000 (16:14 +0200)
The (random) dimensions of a large number of trees is stored in an
array shared by all the tree geodes. The coordinates of the origin of
each tree are replicated in an another array. This allows an entire
block of trees to be rendered with a few OpenGL calls, instead of one
function call per tree.

simgear/scene/material/Effect.cxx
simgear/scene/tgdb/ShaderGeometry.cxx
simgear/scene/tgdb/ShaderGeometry.hxx
simgear/scene/tgdb/TreeBin.cxx
simgear/scene/tgdb/TreeBin.hxx
simgear/scene/tgdb/obj.cxx

index 4415fe35e71c8c908dc03eab95f9221cfe648ec4..f812f57a402d2664f17279710c400dc01ab58288 100644 (file)
@@ -763,4 +763,3 @@ osgDB::RegisterDotOsgWrapperProxy effectProxy
     );
 }
 }
-
index 089beb270c56e5ac56632406885d629ae1f9e564..36a3093471cb628e5e603c29ef4cec8861ba397f 100644 (file)
@@ -32,14 +32,15 @@ using namespace osgDB;
 
 namespace simgear
 {
-void ShaderGeometry::addTree(const TreeBin::Tree& t)
+void ShaderGeometry::addObject(const Vec3& position, float scale,
+                               int texture_index)
 {
     if (!_posScaleArray.valid()) {
         _posScaleArray = new Vec4Array();
         _vertexAttribArray = new FloatArray();
     }
-    _posScaleArray->push_back(Vec4(t.position.osg(), t.scale));
-    _vertexAttribArray->push_back((float)t.texture_index / varieties);
+    _posScaleArray->push_back(Vec4(position, scale));
+    _vertexAttribArray->push_back((float)texture_index / varieties);
     dirtyBound();
 }
 
index d13cd9d6d2e738bda70cee3e0444ea0721a061e5..30c72fbe0d3092a5f64d278f6d2ab054fcfb5ada 100644 (file)
@@ -33,8 +33,6 @@
 #include <osg/Vec3>
 #include <osg/Vec4>
 
-#include "TreeBin.hxx"
-
 namespace simgear
 {
 
@@ -67,7 +65,7 @@ class ShaderGeometry : public osg::Drawable
             _geometry = geometry;
         }
         
-        void addTree(const TreeBin::Tree& tree);
+    void addObject(const osg::Vec3& position, float scale, int texture_index);
 
         osg::ref_ptr<osg::Geometry> _geometry;
 
index c531d0ed9d70ab464eb97fec5abfb83018edbe73..b02307dbfc42b021788eb6229b1eb9f7c4c89e52 100644 (file)
 #include <osgDB/ReadFile>
 #include <osgDB/FileUtils>
 
+#include <simgear/debug/logstream.hxx>
+#include <simgear/math/sg_random.h>
 #include <simgear/misc/sg_path.hxx>
 #include <simgear/scene/util/QuadTreeBuilder.hxx>
 #include <simgear/scene/util/RenderConstants.hxx>
 #include <simgear/scene/util/StateAttributeFactory.hxx>
+#include <simgear/structure/OSGUtils.hxx>
 
 #include "ShaderGeometry.hxx"
 #include "TreeBin.hxx"
 
 #define SG_TREE_QUAD_TREE_DEPTH 3
 
-// Comments from Tim Moore:
-// Some work remains for this code. Stuart's enhancement for multiple
-// textures per forest should be integrated. We should try to use one
-// ShaderGeometry for *all* the trees in the scene graph and do the
-// rotation and scale with a MatrixTransform above the trees quad
-// tree. The positions would of course have to be transformed by the
-// inverse of that transform. Also, we should investigate whether it
-// would be better to instantiate trees as polygons in a osg::Geometry
-// object instead of using the ShaderGeometry instancing technique.
-
 using namespace osg;
 
 namespace simgear
 {
 
-// memoize geometry
-typedef boost::tuple<float, float, int> ForestTuple;
-typedef std::map<ForestTuple, ref_ptr<Geometry> > OrthQuadMap;
+// Tree instance scheme:
+// vertex - local position of quad vertex.
+// normal - x y scaling, z number of varieties
+// fog coord - rotation
+// color - xyz of tree quad origin, replicated 4 times.
+//
+// The tree quad is rendered twice, with different rotations, to
+// create the crossed tree geometry.
+
+struct TreesBoundingBoxCallback : public Drawable::ComputeBoundingBoxCallback
+{
+    TreesBoundingBoxCallback() {}
+    TreesBoundingBoxCallback(const TreesBoundingBoxCallback&, const CopyOp&) {}
+    META_Object(simgear, TreesBoundingBoxCallback);
+    virtual BoundingBox computeBound(const Drawable&) const;
+};
 
-osg::Geometry* createOrthQuads(float w, float h, int varieties, const osg::Matrix& rotate)
+BoundingBox
+TreesBoundingBoxCallback::computeBound(const Drawable& drawable) const
 {
-    static OrthQuadMap orthQuadMap;
-    OrthQuadMap::iterator giter
-        = orthQuadMap.find(ForestTuple(w, h, varieties));
-    if (giter != orthQuadMap.end())
-        return giter->second.get();
+    BoundingBox bb;
+    const Geometry* geom = static_cast<const Geometry*>(&drawable);
+    const Vec3Array* v = static_cast<const Vec3Array*>(geom->getVertexArray());
+    const Vec3Array* pos = static_cast<const Vec3Array*>(geom->getColorArray());
+    const Vec3Array* params
+        = static_cast<const Vec3Array*>(geom->getNormalArray());
+    const FloatArray* rot
+        = static_cast<const FloatArray*>(geom->getFogCoordArray());
+    float w = (*params)[0].x();
+    float h = (*params)[0].y();
+    Geometry::PrimitiveSetList primSets = geom->getPrimitiveSetList();
+    FloatArray::const_iterator rotitr = rot->begin();
+    for (Geometry::PrimitiveSetList::const_iterator psitr = primSets.begin(),
+             psend = primSets.end();
+         psitr != psend;
+         ++psitr, ++rotitr) {
+        Matrixd trnsfrm = (Matrixd::scale(w, w, h)
+                           * Matrixd::rotate(*rotitr, Vec3(0.0f, 0.0f, 1.0f)));
+        DrawArrays* da = static_cast<DrawArrays*>(psitr->get());
+        GLint psFirst = da->getFirst();
+        GLint psEndVert = psFirst + da->getCount();
+        for (GLint i = psFirst;i < psEndVert; ++i) {
+            Vec3 pt = (*v)[i];
+            pt = pt * trnsfrm;
+            pt += (*pos)[i];
+            bb.expandBy(pt);
+        }
+    }
+    return bb;
+}
 
-    //const osg::Vec3& pos = osg::Vec3(0.0f,0.0f,0.0f),
+Geometry* makeSharedTreeGeometry(int numQuads)
+{
+    // generate a repeatable random seed
+    mt seed;
+    mt_init(&seed, unsigned(123));
     // set up the coords
-    // Create front and back polygons so we don't need to screw around
-    // with two-sided lighting in the shader.
-    osg::Vec3Array& v = *(new osg::Vec3Array(8));
-    osg::Vec3Array& n = *(new osg::Vec3Array(8));
-    osg::Vec2Array& t = *(new osg::Vec2Array(8));
-    
-    float cw = w*0.5f;
-
-    v[0].set(0.0f,-cw,0.0f);
-    v[1].set(0.0f, cw,0.0f);
-    v[2].set(0.0f, cw,h);
-    v[3].set(0.0f,-cw,h);
-
-    v[4].set(-cw,0.0f,0.0f);
-    v[5].set( cw,0.0f,0.0f);
-    v[6].set( cw,0.0f,h);
-    v[7].set(-cw,0.0f,h);
-
-    // The texture coordinate range is not the
-    // entire coordinate space - as the texture
-    // has a number of different trees on it.
-    float tx = 1.0f/varieties;
-
-    t[0].set(0.0f,0.0f);
-    t[1].set(  tx,0.0f);
-    t[2].set(  tx,1.0f);
-    t[3].set(0.0f,1.0f);
-
-    t[4].set(0.0f,0.0f);
-    t[5].set(  tx,0.0f);
-    t[6].set(  tx,1.0f);
-    t[7].set(0.0f,1.0f);
-
-    // For now the normal is normal to the quad. If we want to get
-    // fancier and approximate a cylindrical tree or something, then
-    // we would really want more geometry.
-    std::fill(n.begin(), n.begin() + 4, Vec3f(1.0f, 0.0f, 0.0f));
-    std::fill(n.begin() + 4, n.end(), Vec3f(0.0f, -1.0f, 0.0f));
-    for (unsigned int i = 0; i < 8; i++) {
-        v[i] = v[i] * rotate;
-        // Should be the inverse transpose, but assume that rotate is
-        // orthonormal.
-        n[i] = n[i] * rotate;     
+    osg::Vec3Array* v = new osg::Vec3Array;
+    osg::Vec2Array* t = new osg::Vec2Array;
+    v->reserve(numQuads * 4);
+    t->reserve(numQuads * 4);
+    for (int i = 0; i < numQuads; ++i) {
+        // Apply a random scaling factor and texture index.
+        float h = (mt_rand(&seed) + mt_rand(&seed)) / 2.0f + 0.5f;
+        float cw = h * .5;
+        v->push_back(Vec3(0.0f, -cw, 0.0f));
+        v->push_back(Vec3(0.0f, cw, 0.0f));
+        v->push_back(Vec3(0.0f, cw, h));
+        v->push_back(Vec3(0.0f,-cw, h));
+        // The texture coordinate range is not the entire coordinate
+        // space, as the texture has a number of different trees on
+        // it. Here we assign random coordinates and let the shader
+        // choose the variety.
+        float variety = mt_rand(&seed);
+        t->push_back(Vec2(variety, 0.0f));
+        t->push_back(Vec2(variety + 1.0f, 0.0f));
+        t->push_back(Vec2(variety + 1.0f, 1.0f));
+        t->push_back(Vec2(variety, 1.0f));
     }
+    Geometry* result = new Geometry;
+    result->setVertexArray(v);
+    result->setTexCoordArray(0, t);
+    result->setComputeBoundingBoxCallback(new TreesBoundingBoxCallback);
+    result->setUseDisplayList(false);
+    return result;
+}
+
+ref_ptr<Geometry> sharedTreeGeometry;
 
-    osg::Geometry *geom = new osg::Geometry;
+Geometry* createTreeGeometry(float width, float height, int varieties)
+{
+    if (!sharedTreeGeometry)
+        sharedTreeGeometry = makeSharedTreeGeometry(1600);
+    Geometry* quadGeom = simgear::clone(sharedTreeGeometry.get(),
+                                        CopyOp::SHALLOW_COPY);
+    Vec3Array* params = new Vec3Array;
+    params->push_back(Vec3(width, height, (float)varieties));
+    quadGeom->setNormalArray(params);
+    quadGeom->setNormalBinding(Geometry::BIND_OVERALL);
+    // Positions
+    quadGeom->setColorArray(new Vec3Array);
+    quadGeom->setColorBinding(Geometry::BIND_PER_VERTEX);
+    FloatArray* rotation = new FloatArray(2);
+    (*rotation)[0] = 0.0;
+    (*rotation)[1] = PI_2;
+    quadGeom->setFogCoordArray(rotation);
+    quadGeom->setFogCoordBinding(Geometry::BIND_PER_PRIMITIVE_SET);
+    for (int i = 0; i < 2; ++i)
+        quadGeom->addPrimitiveSet(new DrawArrays(PrimitiveSet::QUADS));
+    return quadGeom;
+}
 
-    geom->setVertexArray(&v);
-    geom->setTexCoordArray(0, &t);
-    geom->setNormalArray(&n);
-    geom->setNormalBinding(Geometry::BIND_PER_VERTEX);
-    // No color for now; that's used to pass the position.
-    geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS,0,8));
+Geode* createTreeGeode(float width, float height, int varieties)
+{
+    Geode* result = new Geode;
+    result->addDrawable(createTreeGeometry(width, height, varieties));
+    return result;
+}
 
-    orthQuadMap.insert(std::make_pair(ForestTuple(w, h, varieties), geom));
-    return geom;
+void addTreeToLeafGeode(Geode* geode, const SGVec3f& p)
+{
+    const Vec3& pos = p.osg();
+    unsigned int numDrawables = geode->getNumDrawables();
+    Geometry* geom
+        = static_cast<Geometry*>(geode->getDrawable(numDrawables - 1));
+    Vec3Array* posArray = static_cast<Vec3Array*>(geom->getColorArray());
+    if (posArray->size()
+        >= static_cast<Vec3Array*>(geom->getVertexArray())->size()) {
+        Vec3Array* paramsArray
+            = static_cast<Vec3Array*>(geom->getNormalArray());
+        Vec3 params = (*paramsArray)[0];
+        geom = createTreeGeometry(params.x(), params.y(), params.z());
+        posArray = static_cast<Vec3Array*>(geom->getColorArray());
+        geode->addDrawable(geom);
+    }
+    posArray->insert(posArray->end(), 4, pos);
+    size_t numVerts = posArray->size();
+    for (int i = 0; i < 2; ++i) {
+        DrawArrays* primSet
+            = static_cast<DrawArrays*>(geom->getPrimitiveSet(i));
+        primSet->setCount(numVerts);
+    }
 }
 
  static char vertexShaderSource[] = 
     "varying float fogFactor;\n"
-    "attribute float textureIndex;\n"
     "\n"
     "void main(void)\n"
     "{\n"
-    "  gl_TexCoord[0] = gl_MultiTexCoord0 + vec4(textureIndex, 0.0, 0.0, 0.0);\n"
-    "  vec3 position = gl_Vertex.xyz * gl_Color.w + gl_Color.xyz;\n"
+    "  float numVarieties = gl_Normal.z;\n"
+    "  float texFract = floor(fract(gl_MultiTexCoord0.x) * numVarieties) / numVarieties;\n"
+    "  texFract += floor(gl_MultiTexCoord0.x) / numVarieties;\n"
+    "  float sr = sin(gl_FogCoord);\n"
+    "  float cr = cos(gl_FogCoord);\n"
+    "  gl_TexCoord[0] = vec4(texFract, gl_MultiTexCoord0.y, 0.0, 0.0);\n"
+    // scaling
+    "  vec3 position = gl_Vertex.xyz * gl_Normal.xxy;\n"
+    // Rotation of the generic quad to specific one for the tree.
+    "  position.xy = vec2(dot(position.xy, vec2(cr, sr)), dot(position.xy, vec2(-sr, cr)));\n"
+    "  position = position + gl_Color.xyz;\n"
     "  gl_Position   = gl_ModelViewProjectionMatrix * vec4(position,1.0);\n"
     "  vec3 ecPosition = vec3(gl_ModelViewMatrix * vec4(position, 1.0));\n"
     "  float n = dot(normalize(gl_LightSource[0].position.xyz), normalize(-ecPosition));\n"
@@ -179,24 +252,26 @@ namespace
 {
 struct MakeTreesLeaf
 {
-    MakeTreesLeaf(float range, Geometry* geometry, int varieties) :
-        _range(range), _varieties(varieties), _geometry(geometry)
-    {}
+    MakeTreesLeaf(float range, int varieties, float width, float height) :
+        _range(range),  _varieties(varieties),
+        _width(width), _height(height) {}
+
     MakeTreesLeaf(const MakeTreesLeaf& rhs) :
-        _range(rhs._range), _varieties(rhs._varieties), _geometry(rhs._geometry) {}
+        _range(rhs._range),
+        _varieties(rhs._varieties), _width(rhs._width), _height(rhs._height)
+    {}
+
     LOD* operator() () const
     {
         LOD* result = new LOD;
-        Geode* geode = new Geode;
-        ShaderGeometry* sg = new ShaderGeometry(_varieties);
-        sg->setGeometry(_geometry);
-        geode->addDrawable(sg);
+        Geode* geode = createTreeGeode(_width, _height, _varieties);
         result->addChild(geode, 0, _range);
         return result;
     }
     float _range;
     int _varieties;
-    Geometry* _geometry;
+    float _width;
+    float _height;
 };
 
 struct AddTreesLeafObject
@@ -204,9 +279,7 @@ struct AddTreesLeafObject
     void operator() (LOD* lod, const TreeBin::Tree& tree) const
     {
         Geode* geode = static_cast<Geode*>(lod->getChild(0));
-        ShaderGeometry* sg
-            = static_cast<ShaderGeometry*>(geode->getDrawable(0));
-        sg->addTree(tree);
+        addTreeToLeafGeode(geode, tree.position);
     }
 };
 
@@ -228,8 +301,7 @@ struct TreeTransformer
     TreeBin::Tree operator()(const TreeBin::Tree& tree) const
     {
         const Vec3& pos = tree.position.osg();
-        return TreeBin::Tree(SGVec3f(pos * mat), tree.texture_index,
-                             tree.scale);
+        return TreeBin::Tree(SGVec3f(pos * mat));
     }
     Matrix mat;
 };
@@ -242,12 +314,7 @@ osg::Group* createForest(TreeBin& forest, const osg::Matrix& transform)
 {
     Matrix transInv = Matrix::inverse(transform);
     static Matrix ident;
-    // Set up some shared structures. 
-    osg::Geometry* shared_geometry = createOrthQuads(forest.width, 
-                                                     forest.height, 
-                                                     forest.texture_varieties,
-                                                     ident);
-
+    // Set up some shared structures.
     ref_ptr<Group> group;
 
     osg::StateSet* stateset = 0;
@@ -271,10 +338,9 @@ osg::Group* createForest(TreeBin& forest, const osg::Matrix& transform)
             alphaFunc->setFunction(AlphaFunc::GEQUAL,0.33f);
             program  = new Program;
             baseTextureSampler = new osg::Uniform("baseTexture", 0);
-            Shader* vertex_shader = new Shader(Shader::VERTEX, vertexShaderSource);
+            Shader* vertex_shader = new Shader(Shader::VERTEX,
+                                               vertexShaderSource);
             program->addShader(vertex_shader);
-            program->addBindAttribLocation("textureIndex", 1);
-
             Shader* fragment_shader = new Shader(Shader::FRAGMENT,
                                                  fragmentShaderSource);
             program->addShader(fragment_shader);
@@ -299,12 +365,11 @@ osg::Group* createForest(TreeBin& forest, const osg::Matrix& transform)
     }
     // Now, create a quadtree for the forest.
     {
-        ShaderGeometryQuadtree quadtree(GetTreeCoord(),
-                                        AddTreesLeafObject(),
-                                        SG_TREE_QUAD_TREE_DEPTH,
-                                        MakeTreesLeaf(forest.range,
-                                                      shared_geometry,
-                                                      forest.texture_varieties));
+        ShaderGeometryQuadtree
+            quadtree(GetTreeCoord(), AddTreesLeafObject(),
+                     SG_TREE_QUAD_TREE_DEPTH,
+                     MakeTreesLeaf(forest.range, forest.texture_varieties,
+                                   forest.width, forest.height));
         // Transform tree positions from the "geocentric" positions we
         // get from the scenery polys into the local Z-up coordinate
         // system.
index 3c7970281005d2e83d1f9bd756aa801b1f19005f..bb542c230edbd80860cc4f9cbca13bd2f84fba0e 100644 (file)
@@ -36,12 +36,10 @@ namespace simgear
 class TreeBin {
 public:
   struct Tree {
-    Tree(const SGVec3f& p, int t, float s) :
-      position(p), texture_index(t), scale(s)
+    Tree(const SGVec3f& p) :
+      position(p)
     { }
     SGVec3f position;
-    int texture_index;
-    float scale;
   };
 
     typedef std::vector<Tree> TreeList;
@@ -55,7 +53,7 @@ public:
     void insert(const Tree& t)
     { _trees.push_back(t); }
     void insert(const SGVec3f& p, int t, float s)
-    { insert(Tree(p, t, s)); }
+    { insert(Tree(p)); }
 
     unsigned getNumTrees() const
     { return _trees.size(); }
@@ -64,7 +62,6 @@ public:
     TreeList _trees;
 };
 
-osg::Geometry* createOrthQuads(float w, float h, const osg::Matrix& rotate);
 osg::Group* createForest(TreeBin& forest, const osg::Matrix& transform);
 }
 #endif
index cdfd64f671c227d5151752f49d2349e4f0bb9063..54e3ef164735a99f0e205606b949a4cbf00a665d 100644 (file)
@@ -474,12 +474,7 @@ struct SGTileGeometryBin {
       
       std::vector<SGVec3f>::iterator j;
       for (j = randomPoints.begin(); j != randomPoints.end(); ++j) {
-
-        // Apply a random scaling factor and texture index.
-        float scale = (mt_rand(&seed) + mt_rand(&seed)) / 2.0f + 0.5f;
-        int v = (int) (mt_rand(&seed) * mat->get_tree_varieties());
-        if (v == mat->get_tree_varieties()) v--;         
-        randomForest.insert(*j, v, scale);
+        randomForest.insert(*j);
       }
     }
   }