Создание сферы (с помощью osg:: Geometry) в OpenSceneGraph

Я потратил довольно много времени, чтобы заставить это работать, но моя Сфера просто не будет отображаться.
Для выполнения моей функции использовался следующий код:
Создание 3D-сферы в Opengl с помощью Visual С++

И остальное простая OSG с osg:: Geometry.
(Примечание: не ShapeDrawable, так как вы не можете реализовать пользовательские фигуры, используя это.)
Добавлены вершины, нормали, текскоды в VecArrays.

Для одного, я подозреваю что-то плохое, поскольку мой сохраненный объект наполовину пуст.
Есть ли способ преобразовать существующее описание в OSG?
Причина? Я хочу понять, как создавать объекты позже.
В самом деле, это связано с более поздним назначением, но в настоящее время я только заранее подготовляю.

Sidenote: Поскольку я должен сделать это без индексов, я их оставил.
Но мой цилиндр отлично выглядит без них.

Ответ 1

Предостережение: Я не эксперт по OSG. Но я сделал некоторые исследования.

OSG требует, чтобы все грани определялись в порядке против часовой стрелки, так что отбраковка задней поверхности может отклонять грани, которые "обращены в сторону". Код, который вы используете для создания сферы, не генерирует все грани в порядке против часовой стрелки.

Вы можете сделать это несколькими способами:

  • Отрегулируйте, как код генерирует грани, вставив порядок CCW граней.
  • Снова удвоить свою модель и вставить каждую грань дважды, один раз с вершинами на каждой грани в их текущем порядке и один раз с вершинами в обратном порядке.

Вариант 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. Если у вас есть для них использование, они вычисляются и сохраняются и ждут, когда они будут подключены к чему-то.

Ответ 2

Этот код вызывает glutSolidSphere() для рисования сферы, но не имеет смысла называть его, если ваше приложение не использует GLUT для отображения окна с 3D-контекстом.

Существует еще один способ легко нарисовать сферу, вызвав gluSphere() (у вас, вероятно, есть GLU):

void gluSphere (GLUquadric * quad,                 Радиус GLdouble,                 Кусочки GLint,                 GLint);

Параметры

quad - указывает объект квадрик (созданный с помощью gluNewQuadric).

radius - определяет радиус сферы.

slices - определяет количество подразделений вокруг оси z (аналогично к линиям долготы).

stacks - определяет количество подразделений вдоль оси 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() в деструктор класса.

Ответ 3

@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. Более того, такие классы, как Geode и Geometry, защищены их деструкторами, поэтому единственный способ их создания - это динамическое распределение.

  • Удалено spherePrimitiveSets, поскольку оно не требуется в текущей версии кода.

  • Я помещаю код в свободную функцию, так как мне не нужен класс Sphere в моем коде. Я опустил getters и защищенные атрибуты. Они не нужны: если вам нужно получить доступ, скажем, к геометрии, вы можете получить ее через: sphereGeode->getDrawable(...). То же самое относится к остальным атрибутам.

[1] См. правило # 1 здесь. Это немного устарело, но совет поддерживает.