Change trees code to use a faster OpenGL path

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.
This commit is contained in:
timoore
2009-08-25 07:19:39 +00:00
committed by Tim Moore
parent 0c17c5440a
commit d4289c5d54
6 changed files with 179 additions and 124 deletions

View File

@@ -763,4 +763,3 @@ osgDB::RegisterDotOsgWrapperProxy effectProxy
);
}
}

View 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();
}

View 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;

View File

@@ -42,111 +42,184 @@
#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.
osg::Geometry* createOrthQuads(float w, float h, int varieties, const osg::Matrix& rotate)
struct TreesBoundingBoxCallback : public Drawable::ComputeBoundingBoxCallback
{
static OrthQuadMap orthQuadMap;
OrthQuadMap::iterator giter
= orthQuadMap.find(ForestTuple(w, h, varieties));
if (giter != orthQuadMap.end())
return giter->second.get();
TreesBoundingBoxCallback() {}
TreesBoundingBoxCallback(const TreesBoundingBoxCallback&, const CopyOp&) {}
META_Object(simgear, TreesBoundingBoxCallback);
virtual BoundingBox computeBound(const Drawable&) const;
};
//const osg::Vec3& pos = osg::Vec3(0.0f,0.0f,0.0f),
// 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;
BoundingBox
TreesBoundingBoxCallback::computeBound(const Drawable& drawable) const
{
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;
}
osg::Geometry *geom = new osg::Geometry;
Geometry* makeSharedTreeGeometry(int numQuads)
{
// generate a repeatable random seed
mt seed;
mt_init(&seed, unsigned(123));
// set up the coords
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;
}
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));
ref_ptr<Geometry> sharedTreeGeometry;
orthQuadMap.insert(std::make_pair(ForestTuple(w, h, varieties), geom));
return geom;
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;
}
Geode* createTreeGeode(float width, float height, int varieties)
{
Geode* result = new Geode;
result->addDrawable(createTreeGeometry(width, height, varieties));
return result;
}
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.

View 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

View 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);
}
}
}