Рисование сферы в OpenGL без использования gluSphere ()?


81

Есть ли какие-нибудь учебные пособия, в которых объясняется, как я могу нарисовать сферу в OpenGL без необходимости использования gluSphere()?

Многие учебники по 3D для OpenGL написаны только на кубах. Я искал, но большинство решений для рисования сферы нужно использовать gluSphere(). Существует также сайт , который имеет код для рисования сферы на этом сайте , но это не объясняет математику за рисунок сферы. У меня также есть другие версии того, как рисовать сферу в многоугольнике вместо четырехугольника в этой ссылке. Но опять же, я не понимаю, как с помощью кода рисуются сферы. Я хочу иметь возможность визуализировать, чтобы при необходимости изменить сферу.


3
найдите сферические координаты для математического объяснения (в частности, преобразование сферических координат в декартовы координаты).
Нед Бингхэм,

Ответы:


271

Один из способов сделать это - начать с платонового твердого тела с треугольными сторонами - например, октаэдра . Затем возьмите каждый треугольник и рекурсивно разбейте его на более мелкие треугольники, например:

рекурсивно нарисованные треугольники

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

Нормализация здесь означает перемещение точки так, чтобы ее угол по отношению к другой точке был таким же, но расстояние между ними другое. Вот двухмерный пример.

введите описание изображения здесь

A и B находятся на расстоянии 6 единиц. Но предположим, что мы хотим найти точку на линии AB, удаленную на 12 единиц от A.

введите описание изображения здесь

Мы можем сказать, что C - это нормализованная форма B относительно A с расстоянием 12. Мы можем получить C с помощью такого кода:

#returns a point collinear to A and B, a given distance away from A. 
function normalize(a, b, length):
    #get the distance between a and b along the x and y axes
    dx = b.x - a.x
    dy = b.y - a.y
    #right now, sqrt(dx^2 + dy^2) = distance(a,b).
    #we want to modify them so that sqrt(dx^2 + dy^2) = the given length.
    dx = dx * length / distance(a,b)
    dy = dy * length / distance(a,b)
    point c =  new point
    c.x = a.x + dx
    c.y = a.y + dy
    return c

Если мы сделаем этот процесс нормализации для множества точек, все относительно одной и той же точки A и с одинаковым расстоянием R, тогда все нормализованные точки будут лежать на дуге окружности с центром A и радиусом R.

выпуклый линейный сегмент

Здесь черные точки начинаются на линии и «выпирают» в дугу.

Этот процесс можно расширить до трех измерений, и в этом случае вы получите сферу, а не круг. Просто добавьте компонент dz к функции нормализации.

нормализованные многоугольники

выпуклый октаэдр 1 уровня выпуклый октаэдр 3-го уровня

Если вы посмотрите на сферу в Epcot , вы увидите, как работает эта техника. это додекаэдр с выпуклыми гранями, чтобы он выглядел более округлым.


1
Лучше уберу ссылку на сферу epcot. Это может сбить с толку новичков, потому что каждый треугольник снова разделен на три равнобедренных треугольника (аналогично первой части sqrt (3) -subdivision). Я уверен, что ты найдешь лучший пример.
Кристиан Рау

У меня есть хорошая реализация этого на моей домашней машине. Буду рад отредактировать некоторые скриншоты после работы.
Кевин

Спасибо за идею. Но я не понимаю, как, нормализуя векторы, я мог бы выпучить стороны в форму, напоминающую сферу? Как мне выпучить стороны?
Carven

1
@xEnOn, я отредактировал свой ответ, чтобы немного подробнее объяснить нормализацию. Я думаю, проблема в том, что нормализация - это не настоящий технический термин для процесса, который я пытался объяснить, поэтому вам будет сложно найти больше информации о нем где-либо еще. Прости за это.
Кевин

1
Возможно, лучший способ объяснить здесь процесс «нормализации» - это то, что точки проецируются на сферу. Также обратите внимание, что результаты различаются в зависимости от того, применяется ли нормализация / проекция только один раз в конце (после всего подразделения, которое, кажется, предлагается здесь) или чередуется с шагами (рекурсивного) подразделения. Похоже, что проецирование только один раз в конце дает вершины, сгруппированные рядом с вершинами исходного октаэдра, тогда как чередование подразделений и проекций дает одинаковые расстояния между вершинами.
Тайлер Стритер

26

Далее я объясню популярный способ создания сферы с использованием широты и долготы (другой способ, икосферы , уже объяснялся в самом популярном ответе на момент написания этой статьи).

Сфера может быть выражена следующим параметрическим уравнением:

F ( u , v ) = [cos (u) * sin (v) * r, cos (v) * r, sin (u) * sin (v) * r]

Где:

  • r - радиус;
  • u - долгота от 0 до 2π; и
  • v - широта от 0 до π.

Затем создание сферы включает в себя оценку параметрической функции через фиксированные интервалы.

Например, чтобы сгенерировать 16 линий долготы, будет 17 линий сетки вдоль оси u с шагом π / 8 (2π / 16) (17-я линия оборачивается).

Следующий псевдокод генерирует треугольную сетку, оценивая параметрическую функцию через равные промежутки времени (это работает для любой параметрической функции поверхности, а не только для сфер).

В псевдокоде ниже UResolution - это количество точек сетки по оси U (здесь - линии долготы), а VResolution - это количество точек сетки по оси V (здесь, линии широты).

var startU=0
var startV=0
var endU=PI*2
var endV=PI
var stepU=(endU-startU)/UResolution // step size between U-points on the grid
var stepV=(endV-startV)/VResolution // step size between V-points on the grid
for(var i=0;i<UResolution;i++){ // U-points
 for(var j=0;j<VResolution;j++){ // V-points
 var u=i*stepU+startU
 var v=j*stepV+startV
 var un=(i+1==UResolution) ? EndU : (i+1)*stepU+startU
 var vn=(j+1==VResolution) ? EndV : (j+1)*stepV+startV
 // Find the four points of the grid
 // square by evaluating the parametric
 // surface function
 var p0=F(u, v)
 var p1=F(u, vn)
 var p2=F(un, v)
 var p3=F(un, vn)
 // NOTE: For spheres, the normal is just the normalized
 // version of each vertex point; this generally won't be the case for
 // other parametric surfaces.
 // Output the first triangle of this grid square
 triangle(p0, p2, p1)
 // Output the other triangle of this grid square
 triangle(p3, p1, p2)
 }
}

Голосование против кажется немного резким. Это один из немногих ответов и примеров, в которых упоминается дискретное построение с помощью параметрического уравнения сферы. Это также может быть легче понять на основании того, что сферу можно рассматривать как стопку кругов, которые сжимаются по мере приближения к полюсам.
Spacen Jasset

2
Здравствуйте, я просто хотел указать, что второе значение каждого значения p0, p1, p2, p3 должно быть либо v, либо vn, а не u или un.
Николь

9

Код в примере быстро объясняется. Вы должны изучить функцию void drawSphere(double r, int lats, int longs):

void drawSphere(double r, int lats, int longs) {
    int i, j;
    for(i = 0; i <= lats; i++) {
        double lat0 = M_PI * (-0.5 + (double) (i - 1) / lats);
        double z0  = sin(lat0);
        double zr0 =  cos(lat0);

        double lat1 = M_PI * (-0.5 + (double) i / lats);
        double z1 = sin(lat1);
        double zr1 = cos(lat1);

        glBegin(GL_QUAD_STRIP);
        for(j = 0; j <= longs; j++) {
            double lng = 2 * M_PI * (double) (j - 1) / longs;
            double x = cos(lng);
            double y = sin(lng);

            glNormal3f(x * zr0, y * zr0, z0);
            glVertex3f(r * x * zr0, r * y * zr0, r * z0);
            glNormal3f(x * zr1, y * zr1, z1);
            glVertex3f(r * x * zr1, r * y * zr1, r * z1);
        }
        glEnd();
    }
}

Параметры latопределяют, сколько горизонтальных линий вы хотите иметь в своей сфере и lonсколько вертикальных линий. rэто радиус вашей сферы.

Теперь выполняется двойная итерация по lat/ lonи координаты вершины вычисляются с помощью простой тригонометрии.

Вычисленные вершины теперь отправляются на ваш графический процессор с использованием в glVertex...()качестве a GL_QUAD_STRIP, что означает, что вы отправляете каждые две вершины, которые образуют четырехугольник с двумя ранее отправленными.

Все, что вам теперь нужно понять, это то, как работают тригонометрические функции, но я думаю, вы легко сможете это понять.


@PintoDoido: Это было из исходной ссылки OP, которая в какой-то момент умерла; Я Archive.org добавил ссылку и отредактировал функцию в этом ответе для ясности.
genpfault

2
Радиус отсутствует.
tomasantunes

1
Первый параметр «двойное r» не используется.
ollydbg23

1
Это правильно. Образец кода не является частью моего первоначального ответа. @genpfault: вы добавили образец кода в правку. Не могли бы вы исправить пример?
Constantinius

1
Большое спасибо :)
Константиниус


1

Если вы хотите быть хитрым, как лис, вы можете сократить код из GLU на полдюйма. Ознакомьтесь с исходным кодом MesaGL (http://cgit.freedesktop.org/mesa/mesa/).


4
Хотя я понял значение слова «полдюйма» в этом контексте, я думаю, вы, возможно, захотите отредактировать его для остальных 95% читателей, которые плохо владеют рифмованным сленгом кокни !
Flexo

1

Мой пример того, как использовать «полосу треугольников» для рисования «полярной» сферы, состоит в рисовании точек попарно:

const float PI = 3.141592f;
GLfloat x, y, z, alpha, beta; // Storage for coordinates and angles        
GLfloat radius = 60.0f;
int gradation = 20;

for (alpha = 0.0; alpha < GL_PI; alpha += PI/gradation)
{        
    glBegin(GL_TRIANGLE_STRIP);
    for (beta = 0.0; beta < 2.01*GL_PI; beta += PI/gradation)            
    {            
        x = radius*cos(beta)*sin(alpha);
        y = radius*sin(beta)*sin(alpha);
        z = radius*cos(alpha);
        glVertex3f(x, y, z);
        x = radius*cos(beta)*sin(alpha + PI/gradation);
        y = radius*sin(beta)*sin(alpha + PI/gradation);
        z = radius*cos(alpha + PI/gradation);            
        glVertex3f(x, y, z);            
    }        
    glEnd();
}

Первая введенная точка (glVertex3f) представляет собой параметрическое уравнение, а вторая точка сдвигается на один шаг альфа-угла (от следующей параллели).


1

Хотя принятый ответ решает вопрос, в конце есть небольшое заблуждение. Додекаэдры - это (или могут быть) правильные многогранники, у которых все грани имеют одинаковую площадь. Похоже, так обстоит дело с Эпкотом (который, кстати, не является додекаэдром ). Поскольку решение, предложенное @Kevin, не обеспечивает этой характеристики, я подумал, что могу добавить подход, который это делает.

Хороший способ сгенерировать N-гранный многогранник, где все вершины лежат в одной сфере и все его грани имеют одинаковую площадь / поверхность, - это начать с икосаэдра и итеративно подразделить и нормализовать его треугольные грани (как предлагается в принятом ответе. ). Додекаэдры, например, на самом деле являются усеченными икосаэдрами .

Обычные икосаэдры имеют 20 граней (12 вершин) и могут быть легко построены из 3 золотых прямоугольников; просто нужно использовать это в качестве отправной точки вместо октаэдра. Вы можете найти здесь пример .

Я знаю, что это немного не по теме, но я считаю, что это может помочь, если кто-то придет сюда в поисках этого конкретного случая.


0

Один из способов - создать четырехугольник, обращенный к камере, и написать шейдер вершин и фрагментов, который рендерит что-то похожее на сферу. Вы можете использовать уравнения для круга / сферы, которые вы можете найти в Интернете.

Приятно то, что силуэт шара выглядит одинаково под любым углом. Однако, если сфера не находится в центре перспективного вида, она, возможно, больше похожа на эллипс. Вы можете разработать уравнения для этого и поместить их в штриховку фрагмента. Затем необходимо изменять световое затенение по мере движения игрока, если у вас действительно есть игрок, перемещающийся в трехмерном пространстве вокруг сферы.

Может ли кто-нибудь прокомментировать, пробовали ли они это или это было бы слишком дорого, чтобы быть практичным?


Это верно только при параллельной проекции. Если вы используете перспективную проекцию, силуэт сферы на выходе рендеринга обычно не является кругом.
Reto Koradi

0

Адаптация Python ответа @Constantinius:

lats = 10
longs = 10
r = 10

for i in range(lats):
    lat0 = pi * (-0.5 + i / lats)
    z0 = sin(lat0)
    zr0 = cos(lat0)

    lat1 = pi * (-0.5 + (i+1) / lats)
    z1 = sin(lat1)
    zr1 = cos(lat1)

    glBegin(GL_QUAD_STRIP)
    for j in range(longs+1):
        lng = 2 * pi * (j+1) / longs
        x = cos(lng)
        y = sin(lng)

        glNormal(x * zr0, y * zr0, z0)
        glVertex(r * x * zr0, r * y * zr0, r * z0)
        glNormal(x * zr1, y * zr1, z1)
        glVertex(r * x * zr1, r * y * zr1, r * z1)

    glEnd()

0
void draw_sphere()
{

    //              z
    //              |
    //               __
    //             /|          
    //              |           
    //              |           
    //              |    *      \
    //              | _ _| _ _ _ |    _y
    //             / \c  |n     /                    p3 --- p2
    //            /   \o |i                           |     |
    //           /     \s|s      z=sin(v)            p0 --- p1
    //         |/__              y=cos(v) *sin(u)
    //                           x=cos(v) *cos(u) 
    //       /
    //      x
    //


    double pi = 3.141592;
    double di =0.02;
    double dj =0.04;
    double du =di*2*pi;
    double dv =dj*pi;


    for (double i = 0; i < 1.0; i+=di)  //horizonal
    for (double j = 0; j < 1.0; j+=dj)  //vertical
    {       
        double u = i*2*pi;      //0     to  2pi
        double v = (j-0.5)*pi;  //-pi/2 to pi/2

        double  p[][3] = { 
            cos(v)     * cos(u)      ,cos(v)     * sin(u)       ,sin(v),
            cos(v)     * cos(u + du) ,cos(v)     * sin(u + du)  ,sin(v),
            cos(v + dv)* cos(u + du) ,cos(v + dv)* sin(u + du)  ,sin(v + dv),
            cos(v + dv)* cos(u)      ,cos(v + dv)* sin(u)       ,sin(v + dv)};

        //normal
        glNormal3d(cos(v+dv/2)*cos(u+du/2),cos(v+dv/2)*sin(u+du/2),sin(v+dv/2));

        glBegin(GL_POLYGON);
            glTexCoord2d(i,   j);    glVertex3dv(p[0]);
            glTexCoord2d(i+di,j);    glVertex3dv(p[1]);
            glTexCoord2d(i+di,j+dj); glVertex3dv(p[2]);
            glTexCoord2d(i,   j+dj); glVertex3dv(p[3]);
        glEnd();
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.