Рисование сферы в OpenGL без использования gluSphere ()? - PullRequest
77 голосов
/ 07 октября 2011

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

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

Ответы [ 9 ]

253 голосов
/ 07 октября 2011

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

recursively drawn triangles

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

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

enter image description here

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

enter image description here

Мы можем сказать, что 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.

bulging line segment

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

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

normalized polygons

level 1 bulging octahedron level 3 bulging octahedron

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

21 голосов
/ 09 июля 2015

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

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

F ( u , v ) = [cos (u) * sin (v) * r, cos (v) * r, sin ( и) * грех (v) * г]

Где:

  • r - радиус;
  • u - долгота в диапазоне от 0 до 2 & pi ;; и
  • v - широта в диапазоне от 0 до & pi;

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

Например, чтобы сгенерировать 16 линий долготы, вдоль оси u будет 17 линий сетки с шагом & pi; / 8 (2 & pi; / 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)
 }
}
3 голосов
/ 07 октября 2011

Код в образце быстро объясняется.Вы должны взглянуть на функцию 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...() как GL_QUAD_STRIP, что означает, что вы отправляете каждые две вершины, которые образуют квад, с двумя ранее отправленными.функции работают, но я думаю, вы можете понять это легко.

1 голос
/ 15 декабря 2016

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

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

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

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

1 голос
/ 16 сентября 2016

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

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 голос
/ 24 марта 2013

См. Красную книгу OpenGL: http://www.glprogramming.com/red/chapter02.html#name8 Она решает проблему путем разбиения на полигоны.

1 голос
/ 07 октября 2011

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

0 голосов
/ 03 мая 2019
struct v3
{
    double x,y,z;
    v3(double _x=0, double _y=0, double _z=0){x=_x;y=_y;z=_z;  }
    v3   operator +  ( v3 v)     {return { x+v.x, y+v.y, z+v.z };}
    v3   operator *  ( double k) {return { x*k, y*k, z*k };}
    v3   operator /  ( double k) {return { x/k, y/k, z/k };}
    v3 normalize(){
       double L=sqrt( x*x+y*y+z*z);
       return { x/L , y/L , z/L };}
};

void draw_spheree(double r,int adim)
{

    //              z
    //              |
    //               __
    //             /|          
    //              |           
    //              |           
    //              |    *      \
    //              | _ _| _ _ _ |    _y
    //             / \c  |n     /                    a4 --- a3
    //            /   \o |i                           |     |
    //           /     \s|s      z=sin(v)            a1 --- a2
    //         |/__              y=cos(v) *sin(u)
    //                           x=cos(v) *cos(u) 
    //       /
    //      x
    //

    //glEnable(GL_LIGHTING);
    //glEnable(GL_LIGHT0);
    //glEnable(GL_TEXTURE_2D); 

    double pi=3.141592;
    double d=pi/adim;

    for(double u=-pi  ; u<pi  ; u+=d)   //horizonal  xy düzlemi     Longitude -180  -180
    for(double v=-pi/2; v<pi/2; v+=d)   //vertical   z aks          Latitude  -90     90
    {
        v3  a1 = {  cos(v)*cos(u)       ,cos(v)*sin(u)      ,sin(v)     },
            a2 = {  cos(v)*cos(u+d)     ,cos(v)*sin(u+d)    ,sin(v)     },
            a3 = {  cos(v+d)*cos(u+d)   ,cos(v+d)*sin(u+d)  ,sin(v+d)   },
            a4 = {  cos(v+d)*cos(u)     ,cos(v+d)*sin(u)    ,sin(v+d)   };

        v3 normal=(a1+a2+a3+a4)/4.0;   //normal vector

        a1=a1*r;
        a2=a2*r;
        a3=a3*r;
        a4=a4*r;

        double tu=(u+pi)  / (2*pi);  //0 to 1  horizonal
        double tv=(v+pi/2)/ pi;      //0 to 1  vertical

        double td=1.0/2./adim;

        glNormal3dv((double *)&normal);

        glBegin(GL_POLYGON);
            glTexCoord2d(tu    ,tv      ); glVertex3dv((double *) &a1);
            glTexCoord2d(tu+td ,tv      ); glVertex3dv((double *) &a2);
            glTexCoord2d(tu+td ,tv+2*td ); glVertex3dv((double *) &a3);
            glTexCoord2d(tu    ,tv+2*td ); glVertex3dv((double *) &a4);
        glEnd();                
    } 
 }
0 голосов
/ 17 сентября 2016

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...