Малювання сфери в OpenGL без використання gluSphere ()?


81

Чи є там підручники, які пояснюють, як я можу намалювати сферу в OpenGL без необхідності використовувати gluSphere()?

Багато 3D-підручників для OpenGL є лише на кубах. Я шукав, але більшість рішень для малювання кулі мають бути використані gluSphere(). Існує також сайт , який має код для малювання сфери на цьому сайті , але це не пояснює математику за малюнок сфери. У мене також є інші версії того, як намалювати сферу багатокутником замість квадратиків у цьому посиланні. Але знову ж таки, я не розумію, як сфери намальовані за допомогою коду. Я хочу мати можливість візуалізації, щоб я міг модифікувати сферу, якщо мені потрібно.


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

Відповіді:


270

Один із способів це зробити - це почати з платонічного твердого тіла з трикутними сторонами - октаедра , наприклад. Потім візьміть кожен трикутник і рекурсивно розбийте його на менші трикутники, приблизно так:

рекурсивно намальовані трикутники

Отримавши достатню кількість точок, ви нормалізуєте їх вектори так, щоб усі вони знаходились на постійній відстані від центру твердого тіла. Це призводить до того, що сторони випирають у форму, що нагадує сферу, із збільшенням плавності при збільшенні кількості точок.

Нормалізація тут означає переміщення точки так, щоб її кут по відношенню до іншої точки був однаковим, але відстань між ними інша. Ось двовимірний приклад.

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

А і В розташовані на відстані 6 одиниць. Але припустимо, ми хочемо знайти точку на прямій АВ, яка знаходиться на відстані 12 одиниць від точки А.

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

Можна сказати, що 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) -підрозділу). Я впевнений, що ви знайдете кращий приклад.
Крістіан Рау

У мене це добре реалізовано на домашній машині. Я з радістю відредагую деякі скріншоти після роботи.
Кевін

Дякую за ідею. Але я не розумію частини про те, як нормалізуючи вектори, я міг би випнути боки назовні у форму, що нагадує сферу? Як випнути боки?
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 ліній довготи, уздовж осі u буде 17 ліній сітки з кроком π / 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: ви додали зразок коду під час редагування. Ви можете виправити приклад?
Костянтин

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

Пітонова адаптація відповіді @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.