在OpenSceneGraph中创建球体(使用osg::Geometry)

Creating a Sphere (using osg::Geometry) in OpenSceneGraph

本文关键字:osg 使用 Geometry OpenSceneGraph 创建      更新时间:2023-10-16

我花了相当长的时间来实现这一点,但我的Sphere无法显示
使用以下代码生成我的函数:
使用Visual C++在Opengl中创建三维球体

其余的是简单OSG,带有OSG::Geometry
(注意:不可ShapeDrawable,因为您无法使用它实现自定义形状。)
将顶点、法线和纹理坐标添加到VecArray中。

首先,我怀疑有什么不正常的地方,因为我保存的对象有一半是空的
有没有办法将现有描述转换为OSG
原因我想了解以后如何创建对象。
事实上,这与以后的任务有关,但目前我只是提前做好准备。

旁注:由于我必须在没有索引的情况下制作,所以我将它们排除在外
但我的圆柱体在没有它们的情况下显示得很好。

注意:我不是OSG专家。但是,我确实做了一些研究。

OSG要求以逆时针顺序定义所有面,以便背面剔除可以拒绝"面朝外"的面。用于生成球体的代码不会按逆时针顺序生成所有面。

你可以通过两种方式来解决这个问题:

  1. 通过按CCW顺序插入面,调整代码生成面的方式
  2. 将模型加倍,并将每个面插入两次,一次是每个面上的顶点按当前顺序插入,另一次是顶点按相反顺序插入

上面的选项1将限制您的多边形总数。选项2将为您提供一个从球体外部和内部都可见的球体。

要实现选项2,您只需要从链接到的代码中修改此循环:

    indices.resize(rings * sectors * 4);
    std::vector<GLushort>::iterator i = indices.begin();
    for(r = 0; r < rings-1; r++) 
        for(s = 0; s < sectors-1; s++) {
            *i++ = r * sectors + s;
            *i++ = r * sectors + (s+1);
            *i++ = (r+1) * sectors + (s+1);
            *i++ = (r+1) * sectors + s;
        }

将四边形集合加倍,如下所示:

    indices.resize(rings * sectors * 8);
    std::vector<GLushort>::iterator i = indices.begin();
    for(r = 0; r < rings-1; r++) 
        for(s = 0; s < sectors-1; s++) {
            *i++ = r * sectors + s;
            *i++ = r * sectors + (s+1);
            *i++ = (r+1) * sectors + (s+1);
            *i++ = (r+1) * sectors + s;
            *i++ = (r+1) * sectors + s;
            *i++ = (r+1) * sectors + (s+1);
            *i++ = r * sectors + (s+1);
            *i++ = r * sectors + s;
        }

不过,这确实是"更大的锤子"解决方案。

就我个人而言,我很难弄清楚为什么最初的循环不够;通过我对几何体的直觉,感觉它已经生成了CCW面,因为每个连续的环都在前一个环的上方,而每个连续的扇区都是围绕前一个球面的CCW面。因此,原始顺序本身应该是相对于离观看者最近的面的CCW。


EDIT使用您之前链接的OpenGL代码和今天链接的OSG教程,我编写了一个我认为正确的程序来生成球体的osg::Geometry/osg::Geode。我没有办法测试下面的代码,但通过桌面检查,它看起来是正确的,或者至少基本上是正确的。

#include <vector>
class SolidSphere
{
protected:
    osg::Geode      sphereGeode;
    osg::Geometry   sphereGeometry;
    osg::Vec3Array  sphereVertices;
    osg::Vec3Array  sphereNormals;
    osg::Vec2Array  sphereTexCoords;
    std::vector<osg::DrawElementsUInt> spherePrimitiveSets;
public:
    SolidSphere(float radius, unsigned int rings, unsigned int sectors)
    {
        float const R = 1./(float)(rings-1);
        float const S = 1./(float)(sectors-1);
        int r, s;
        sphereGeode.addDrawable( &sphereGeometry );
        // Establish texture coordinates, vertex list, and normals
        for(r = 0; r < rings; r++)
            for(s = 0; s < sectors; s++)
            {
                float const y = sin( -M_PI_2 + M_PI * r * R );
                float const x = cos(2*M_PI * s * S) * sin( M_PI * r * R );
                float const z = sin(2*M_PI * s * S) * sin( M_PI * r * R );
                sphereTexCoords.push_back( osg::Vec2(s*R, r*R) );
                sphereVertices.push_back ( osg::Vec3(x * radius,
                                                     y * radius,
                                                     z * radius) );
                sphereNormals.push_back  ( osg::Vec3(x, y, z) );
            }
        sphereGeometry.setVertexArray  ( &spehreVertices  );
        sphereGeometry.setTexCoordArray( &sphereTexCoords );
        // Generate quads for each face.  
        for(r = 0; r < rings-1; r++)
            for(s = 0; s < sectors-1; s++)
            {
                spherePrimitiveSets.push_back(
                    DrawElementUint( osg::PrimitiveSet::QUADS, 0 )
                );
                osg::DrawElementsUInt& face = spherePrimitiveSets.back();
                // Corners of quads should be in CCW order.
                face.push_back( (r + 0) * sectors + (s + 0) );
                face.push_back( (r + 0) * sectors + (s + 1) );
                face.push_back( (r + 1) * sectors + (s + 1) );
                face.push_back( (r + 1) * sectors + (s + 0) );
                sphereGeometry.addPrimitveSet( &face );
            }
    }
    osg::Geode     *getGeode()     const { return &sphereGeode;     }
    osg::Geometry  *getGeometry()  const { return &sphereGeometry;  }
    osg::Vec3Array *getVertices()  const { return &sphereVertices;  }
    osg::Vec3Array *getNormals()   const { return &sphereNormals;   }
    osg::Vec2Array *getTexCoords() const { return &sphereTexCoords; }
};

您可以使用getXXX方法来获得各种工件。我没有看到如何将曲面法线挂接到任何东西,但我确实将它们存储在Vec2Array中。如果你有它们的用途,它们会被计算和存储,等待被钩住。

该代码调用glutSolidSphere()来绘制球体,但如果您的应用程序没有使用GLUT来显示具有3D上下文的窗口,则调用它是没有意义的。

还有另一种简单绘制球体的方法,那就是调用gluSphere()(您可能安装了GLU):

voidgluSphere(GLUquadri*quad,GLdouble半径,GLint切片,GLint堆栈);

参数

quad-指定二次曲面对象(使用gluNewQuadric创建)。

radius-指定球体的半径。

切片-指定z轴周围的细分数目(类似到经线)。

堆栈-指定沿z轴的细分数目(类似到纬度线)。

用法:

// If you also need to include glew.h, do it before glu.h
#include <glu.h>
GLUquadric* _quadratic = gluNewQuadric();
if (_quadratic == NULL)
{
    std::cerr << "!!! Failed gluNewQuadric" << std::endl;
    return;
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0, 0.0, -5.0);
glColor3ub(255, 97, 3);
gluSphere(_quadratic, 1.4f, 64, 64);
glFlush();
gluDeleteQuadric(_quadratic);

gluNewQuadric()调用移动到类的构造函数可能更明智,因为它只需要分配一次,并将对gluDeleteQuadric()的调用移动到该类的析构函数。

@JoeZ的回答很好,但OSG代码存在一些错误/不良做法。这是更新后的代码。经过测试,它显示出一个非常漂亮的球体。

    osg::ref_ptr<osg::Geode> buildSphere( const double radius,
                                          const unsigned int rings,
                                          const unsigned int sectors )
    {
        osg::ref_ptr<osg::Geode>      sphereGeode = new osg::Geode;
        osg::ref_ptr<osg::Geometry>   sphereGeometry = new osg::Geometry;
        osg::ref_ptr<osg::Vec3Array>  sphereVertices = new osg::Vec3Array;
        osg::ref_ptr<osg::Vec3Array>  sphereNormals = new osg::Vec3Array;
        osg::ref_ptr<osg::Vec2Array>  sphereTexCoords = new osg::Vec2Array;
        float const R = 1. / static_cast<float>( rings - 1 );
        float const S = 1. / static_cast<float>( sectors - 1 );
        sphereGeode->addDrawable( sphereGeometry );
        // Establish texture coordinates, vertex list, and normals
        for( unsigned int r( 0 ); r < rings; ++r ) {
            for( unsigned int s( 0) ; s < sectors; ++s ) {
                float const y = sin( -M_PI_2 + M_PI * r * R );
                float const x = cos( 2 * M_PI * s * S) * sin( M_PI * r * R );
                float const z = sin( 2 * M_PI * s * S) * sin( M_PI * r * R );
                sphereTexCoords->push_back( osg::Vec2( s * R, r * R ) );
                sphereVertices->push_back ( osg::Vec3( x * radius,
                                                       y * radius,
                                                       z * radius) )
                ;
                sphereNormals->push_back  ( osg::Vec3( x, y, z ) );
            }
        }
        sphereGeometry->setVertexArray  ( sphereVertices  );
        sphereGeometry->setTexCoordArray( 0, sphereTexCoords );
        // Generate quads for each face.
        for( unsigned int r( 0 ); r < rings - 1; ++r ) {
            for( unsigned int s( 0 ); s < sectors - 1; ++s ) {
                osg::ref_ptr<osg::DrawElementsUInt> face =
                        new osg::DrawElementsUInt( osg::PrimitiveSet::QUADS,
                                                   4 )
                ;
                // Corners of quads should be in CCW order.
                face->push_back( ( r + 0 ) * sectors + ( s + 0 ) );
                face->push_back( ( r + 0 ) * sectors + ( s + 1 ) );
                face->push_back( ( r + 1 ) * sectors + ( s + 1 ) );
                face->push_back( ( r + 1 ) * sectors + ( s + 0 ) );
                sphereGeometry->addPrimitiveSet( face );
            }
        }
        return sphereGeode;
    }

更改:

  • 代码中使用的OSG元素现在是智能指针1。此外,像GeodeGeometry这样的类的析构函数受到保护,因此实例化它们的唯一方法是通过动态分配。

  • 删除了spherePrimitiveSets,因为在当前版本的代码中不需要它。

  • 我把代码放在一个免费函数中,因为我的代码中不需要Sphere类。我省略了getters和受保护的属性。它们是不需要的:如果你需要访问,比如说几何体,你可以通过:sphereGeode->getDrawable(...)获取。其余属性也是如此。

[1] 请参阅此处的经验法则#1。它有点旧,但建议仍然有效。