Rysowanie sfery w OpenGL bez użycia gluSphere ()?

81

Czy są dostępne samouczki, które wyjaśniają, jak narysować kulę w OpenGL bez konieczności używania gluSphere()?

Wiele samouczków 3D dla OpenGL dotyczy tylko kostek. Szukałem, ale większość rozwiązań do rysowania kuli ma zastosowanie gluSphere(). Istnieje również witryna, która ma kod do rysowania kuli w tej witrynie, ale nie wyjaśnia matematyki związanej z rysowaniem kuli. Mam również inne wersje tego, jak narysować kulę w wielokącie zamiast quadów w tym łączu. Ale znowu nie rozumiem, jak kule są rysowane za pomocą kodu. Chcę mieć możliwość wizualizacji, abym mógł zmodyfikować kulę, jeśli zajdzie taka potrzeba.

Carven
źródło
3
poszukaj współrzędnych sferycznych dla wyjaśnienia matematycznego (w szczególności konwersji ze współrzędnych sferycznych na współrzędne kartezjańskie).
Ned Bingham

Odpowiedzi:

271

Jednym ze sposobów jest rozpoczęcie od bryły platońskiej o trójkątnych bokach - na przykład ośmiościanu . Następnie weź każdy trójkąt i rekurencyjnie podziel go na mniejsze trójkąty, na przykład:

rekurencyjnie narysowane trójkąty

Gdy masz wystarczającą liczbę punktów, normalizujesz ich wektory, tak aby wszystkie znajdowały się w stałej odległości od środka bryły. Powoduje to, że boki wybrzuszają się do kształtu przypominającego kulę, z rosnącą gładkością w miarę zwiększania liczby punktów.

Normalizacja oznacza tutaj przesunięcie punktu tak, aby jego kąt w stosunku do innego punktu był taki sam, ale odległość między nimi była inna. Oto dwuwymiarowy przykład.

wprowadź opis obrazu tutaj

A i B są oddalone od siebie o 6 jednostek. Ale przypuśćmy, że chcemy znaleźć punkt na linii AB, który jest oddalony o 12 jednostek od punktu A.

wprowadź opis obrazu tutaj

Możemy powiedzieć, że C jest znormalizowaną postacią B w odniesieniu do A, z odległością 12. Możemy otrzymać C za pomocą następującego kodu:

#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

Jeśli wykonamy ten proces normalizacji na wielu punktach, wszystkie w odniesieniu do tego samego punktu A i przy tej samej odległości R, wówczas znormalizowane punkty będą leżeć na łuku koła o środku A i promieniu R.

wybrzuszony odcinek linii

Tutaj czarne punkty zaczynają się na linii i „wybrzuszają” w łuk.

Ten proces można rozszerzyć do trzech wymiarów, w którym to przypadku otrzymujesz kulę zamiast koła. Po prostu dodaj składnik dz do funkcji normalizacji.

znormalizowane wielokąty

wybrzuszony oktaedr poziomu 1 wypukły oktaedr poziomu 3

Jeśli spojrzysz na kulę w Epcot , możesz w pewnym sensie zobaczyć, jak działa ta technika. to dwunastościan z wybrzuszonymi twarzami, aby wyglądał bardziej okrągło.

Kevin
źródło
1
Wolałbym raczej usunąć połączenie ze sferą epcot. Może to zmylić początkujących, ponieważ tam każdy trójkąt jest ponownie podzielony na trzy trójkąty równoramienne (podobnie jak w pierwszej części podziału sqrt (3)). Na pewno znajdziesz lepszy przykład.
Christian Rau,
Mam niezłą implementację tego na moim domowym komputerze. Z przyjemnością edytuję kilka zrzutów ekranu po pracy.
Kevin,
Dzięki za pomysł. Ale nie rozumiem części, w jaki sposób normalizując wektory, mógłbym wybrzuszać boki do kształtu przypominającego kulę? Jak wybrzuszać boki?
Carven
1
@xEnOn, zredagowałem moją odpowiedź, aby trochę bardziej wyjaśnić normalizację. Myślę, że problem polega na tym, że normalizacja nie jest faktycznym terminem technicznym dla procesu, który próbowałem wyjaśnić, więc trudno byłoby ci znaleźć więcej informacji na ten temat gdziekolwiek indziej. Przepraszam za to.
Kevin
1
Być może lepszym sposobem wyjaśnienia procesu „normalizacji” jest rzutowanie punktów na kulę. Należy również zauważyć, że wyniki różnią się w zależności od tego, czy normalizacja / projekcja jest stosowana tylko raz na końcu (po wszystkich podziale, co wydaje się być tym, co jest tutaj sugerowane), czy przeplatana (rekurencyjnymi) krokami podziału. Wydaje się, że rzutowanie tylko raz na końcu daje wierzchołki skupione w pobliżu wierzchołków początkowego ośmiościanu, podczas gdy przeplatany podział i rzut daje jednakowe odległości między wierzchołkami.
Tyler Streeter
26

Wyjaśnię dalej popularny sposób generowania kuli za pomocą szerokości i długości geograficznej (inny sposób, icosfery , został już wyjaśniony w najpopularniejszej odpowiedzi w czasie tego pisania).

Kulę można wyrazić za pomocą następującego równania parametrycznego:

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

Gdzie:

  • r jest promieniem;
  • u jest długością geograficzną w zakresie od 0 do 2π; i
  • v jest szerokością geograficzną w zakresie od 0 do π.

Generowanie sfery obejmuje następnie ocenę funkcji parametrycznej w ustalonych odstępach czasu.

Na przykład, aby wygenerować 16 linii długości geograficznej, wzdłuż osi u będzie 17 linii siatki z krokiem π / 8 (2π / 16) (17 linia jest zawijana).

Poniższy pseudokod generuje siatkę trójkątów, oceniając funkcję parametryczną w regularnych odstępach czasu (działa to dla każdej parametrycznej funkcji powierzchni, nie tylko dla sfer).

W poniższym pseudokodzie UResolution to liczba punktów siatki wzdłuż osi U (tutaj linie długości geograficznej), a VRrozwiązanie to liczba punktów siatki wzdłuż osi V (tutaj linie szerokości geograficznej)

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)
 }
}
Peter O.
źródło
Głosowanie negatywne wydaje się nieco surowe. Jest to jedyna odpowiedź i przykład, który wspomina o konstrukcji dyskretnej poprzez równanie parametryczne kuli. Może być również łatwiejsze do zrozumienia na tej podstawie, że kulę można traktować jako stos okręgów, które kurczą się, gdy zbliżają się do biegunów.
Spacen Jasset
2
Witam, chciałem tylko zaznaczyć, że druga z wartości p0, p1, p2, p3 powinna być albo v albo vn, w przeciwieństwie do u lub un.
nicole
9

Kod w przykładzie jest szybko wyjaśniony. Powinieneś przyjrzeć się funkcji 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();
    }
}

Parametry latokreślają, ile poziomych linii chcesz mieć w swojej sferze ilon ile linii pionowych. rto promień twojej kuli.

Teraz jest podwójna iteracja nad lat/lon i współrzędne wierzchołków są obliczane za pomocą prostej trygonometrii.

Obliczone wierzchołki są teraz wysyłane do twojego GPU glVertex...()jako plikGL_QUAD_STRIP , co oznacza, że ​​wysyłasz każde dwa wierzchołki, które tworzą quad z poprzednimi dwoma wysłanymi.

Teraz musisz tylko zrozumieć, jak działają funkcje trygonometryczne, ale myślę, że możesz to łatwo zrozumieć.

Konstantyniusz
źródło
@PintoDoido: To było z oryginalnego linku OP, który w pewnym momencie umarł; Zarchiwizowałem link i zredagowałem funkcję w tę odpowiedź dla jasności.
genpfault
2
Brak promienia.
tomasantunes
1
Pierwszy parametr „podwójne r” nie jest używany.
ollydbg23
1
To jest poprawne. Przykładowy kod nie jest częścią mojej oryginalnej odpowiedzi. @genpfault: dodałeś przykładowy kod w edycji. Czy możesz naprawić przykład?
Constantinius,
1
Wielkie dzięki :)
Constantinius
1

Jeśli chcesz być przebiegły jak lis, możesz o pół cala kod z GLU. Sprawdź kod źródłowy MesaGL (http://cgit.freedesktop.org/mesa/mesa/).

blockchaindev
źródło
4
Chociaż rozumiałem znaczenie słowa „pół cala” w tym kontekście, myślę, że możesz chcieć go edytować dla pozostałych 95% czytelników, którzy nie biegle posługują się rymowanym slangiem Cockney !
Flexo
1

Mój przykład użycia 'paska trójkąta' do narysowania sfery „biegunowej” polega na rysowaniu punktów parami:

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();
}

Pierwszy wprowadzony punkt (glVertex3f) jest następujący po równaniu parametrycznym, a drugi jest przesuwany o jeden krok kąta alfa (od następnego równoległego).

krwawy
źródło
1

Chociaż zaakceptowana odpowiedź rozwiązuje pytanie, na końcu jest małe nieporozumienie. Dwunastościany są (lub mogą być) regularnymi wielościanami, w których wszystkie ściany mają ten sam obszar. Wydaje się, że tak jest w przypadku Epcot (który, nawiasem mówiąc, wcale nie jest dwunastościanem ). Ponieważ rozwiązanie zaproponowane przez @Kevin nie zapewnia tej cechy, pomyślałem, że mógłbym dodać podejście, które to robi.

Dobrym sposobem na wygenerowanie wielościanu o powierzchni N, w której wszystkie wierzchołki leżą w tej samej sferze, a wszystkie jej ściany mają podobne pole / powierzchnię, jest rozpoczęcie od dwudziestościanu i iteracyjne dzielenie i normalizowanie jego trójkątnych ścian (zgodnie z przyjętą odpowiedzią ). Na przykład dwunastościany są w rzeczywistości ściętymi dwudziestościanami .

Regularne dwudziestościany mają 20 ścian (12 wierzchołków) i można je łatwo zbudować z 3 złotych prostokątów; chodzi tylko o to, aby to było punktem wyjścia zamiast ośmiościanu. Możesz znaleźć przykład tutaj .

Wiem, że to trochę nie na temat, ale myślę, że może pomóc, jeśli ktoś przyjdzie tutaj, szukając tego konkretnego przypadku.

Carles Araguz
źródło
0

Jednym ze sposobów jest zrobienie quada skierowanego w stronę kamery i napisanie Vertex and Fragment Shader, który renderuje coś, co wygląda jak sfera. Możesz użyć równań dla koła / kuli, które możesz znaleźć w Internecie.

Jedną fajną rzeczą jest to, że sylwetka kuli wygląda tak samo pod każdym kątem. Jeśli jednak kula nie znajduje się w środku widoku perspektywicznego, może wyglądać bardziej jak elipsa. Możesz opracować równania do tego i umieścić je w cieniowaniu fragmentów. Następnie cieniowanie światła musi się zmieniać wraz z ruchem gracza, jeśli rzeczywiście gracz porusza się w przestrzeni 3D wokół kuli.

Czy ktoś może skomentować, czy tego próbował, czy też byłoby to zbyt drogie, aby było praktyczne?

Steven2163712
źródło
To prawda tylko w przypadku równoległej projekcji. Jeśli używasz rzutowania perspektywicznego, sylwetka sfery w wyniku renderowania zazwyczaj nie jest okręgiem.
Reto Koradi
0

Adaptacja Pythona odpowiedzi @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()
PepeElMago33
źródło
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();
    }
}
ismail akın çelik
źródło