Sphere in OpenGL ohne Verwendung von gluSphere () zeichnen?


80

Gibt es Tutorials, die erklären, wie ich eine Kugel in OpenGL zeichnen kann, ohne sie verwenden zu müssen gluSphere()?

Viele der 3D-Tutorials für OpenGL befinden sich nur in Cubes. Ich habe gesucht, aber die meisten Lösungen zum Zeichnen einer Kugel sind zu verwenden gluSphere(). Es gibt auch eine Site, die den Code zum Zeichnen einer Kugel an dieser Site enthält, aber die Mathematik hinter dem Zeichnen der Kugel nicht erklärt. Ich habe auch andere Versionen, wie man die Kugel in Polygon anstelle von Quads in diesem Link zeichnet. Aber auch hier verstehe ich nicht, wie die Kugeln mit dem Code gezeichnet werden. Ich möchte in der Lage sein, zu visualisieren, damit ich die Kugel bei Bedarf ändern kann.


3
Suchen Sie nach sphärischen Koordinaten für die mathematische Erklärung (insbesondere die Konvertierung von sphärischen Koordinaten in kartesische Koordinaten).
Ned Bingham

Antworten:


269

Eine Möglichkeit besteht darin, mit einem platonischen Körper mit dreieckigen Seiten zu beginnen - beispielsweise einem Oktaeder . Nehmen Sie dann jedes Dreieck und teilen Sie es rekursiv in kleinere Dreiecke auf:

rekursiv gezeichnete Dreiecke

Sobald Sie eine ausreichende Anzahl von Punkten haben, normalisieren Sie ihre Vektoren so, dass sie alle einen konstanten Abstand vom Zentrum des Volumenkörpers haben. Dies führt dazu, dass sich die Seiten in eine Form wölben, die einer Kugel ähnelt, wobei die Glätte zunimmt, wenn Sie die Anzahl der Punkte erhöhen.

Normalisierung bedeutet hier, einen Punkt so zu verschieben, dass sein Winkel in Bezug auf einen anderen Punkt gleich ist, der Abstand zwischen ihnen jedoch unterschiedlich ist. Hier ist ein zweidimensionales Beispiel.

Geben Sie hier die Bildbeschreibung ein

A und B sind 6 Einheiten voneinander entfernt. Angenommen, wir möchten einen Punkt auf der Linie AB finden, der 12 Einheiten von A entfernt ist.

Geben Sie hier die Bildbeschreibung ein

Wir können sagen, dass C die normalisierte Form von B in Bezug auf A mit Abstand 12 ist. Wir können C mit Code wie folgt erhalten:

#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

Wenn wir diesen Normalisierungsprozess an vielen Punkten durchführen, alle in Bezug auf denselben Punkt A und mit demselben Abstand R, dann liegen die normalisierten Punkte alle auf dem Kreisbogen mit Mittelpunkt A und Radius R.

pralles Liniensegment

Hier beginnen die schwarzen Punkte auf einer Linie und "wölben" sich in einen Bogen.

Dieser Vorgang kann in drei Dimensionen erweitert werden. In diesem Fall erhalten Sie eher eine Kugel als einen Kreis. Fügen Sie der Normalisierungsfunktion einfach eine dz-Komponente hinzu.

normalisierte Polygone

gewölbtes Oktaeder der Stufe 1 gewölbtes Oktaeder der Stufe 3

Wenn Sie sich die Kugel bei Epcot ansehen , können Sie diese Technik bei der Arbeit sehen. Es ist ein Dodekaeder mit ausgebauchten Gesichtern, damit es runder aussieht.


1
Ich möchte lieber den Link zur Epcot-Kugel entfernen. Es kann Anfänger verwirren, da dort jedes Dreieck wieder in drei gleichschenklige Dreiecke unterteilt ist (ähnlich dem ersten Teil der sqrt (3) -Unterteilung). Ich bin sicher, Sie finden ein besseres Beispiel.
Christian Rau

Ich habe eine schöne Implementierung auf meinem Heimcomputer. Gerne bearbeite ich nach der Arbeit einige Screenshots.
Kevin

Danke für die Idee. Aber ich verstehe den Teil nicht, wie ich durch Normalisieren der Vektoren die Seiten in eine Form ausbeulen könnte, die der Kugel ähnelt? Wie wölbe ich die Seiten aus?
Carven

1
@xEnOn, ich habe meine Antwort bearbeitet, um die Normalisierung etwas näher zu erläutern. Ich denke, das Problem ist, dass Normalisierung nicht der eigentliche Fachbegriff für den Prozess ist, den ich erklären wollte. Daher wäre es für Sie schwierig, irgendwo anders mehr Informationen darüber zu finden. Das tut mir leid.
Kevin

1
Vielleicht ist eine bessere Möglichkeit, den "Normalisierungs" -Prozess hier zu erklären, dass die Punkte auf eine Kugel projiziert werden. Beachten Sie auch, dass die Ergebnisse unterschiedlich sind, je nachdem, ob die Normalisierung / Projektion am Ende nur einmal angewendet wird (nach jeder Unterteilung, wie es hier vorgeschlagen wird) oder mit den (rekursiven) Unterteilungsschritten verschachtelt ist. Es scheint, dass nur einmaliges Projizieren am Ende Scheitelpunkte ergibt, die in der Nähe der Scheitelpunkte des anfänglichen Oktaeders gruppiert sind, während verschachtelte Unterteilung und Projektion gleichmäßige Abstände zwischen Scheitelpunkten ergeben.
Tyler Streeter

25

Ich werde eine beliebte Methode zur Erzeugung einer Kugel anhand von Längen- und Breitengraden näher erläutern (eine andere Methode, Icosphären , wurde bereits in der beliebtesten Antwort zum Zeitpunkt dieses Schreibens erläutert.)

Eine Kugel kann durch die folgende parametrische Gleichung ausgedrückt werden:

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

Wo:

  • r ist der Radius;
  • u ist der Längengrad im Bereich von 0 bis 2π; und
  • v ist der Breitengrad im Bereich von 0 bis π.

Das Erzeugen der Kugel beinhaltet dann das Auswerten der parametrischen Funktion in festen Intervallen.

Um beispielsweise 16 Längengrade zu erzeugen, gibt es 17 Gitterlinien entlang der u- Achse mit einem Schritt von π / 8 (2π / 16) (die 17. Linie wird umbrochen).

Der folgende Pseudocode erzeugt ein Dreiecksnetz, indem eine parametrische Funktion in regelmäßigen Abständen ausgewertet wird (dies funktioniert für jede parametrische Oberflächenfunktion, nicht nur für Kugeln).

Im folgenden Pseudocode ist UResolution die Anzahl der Gitterpunkte entlang der U-Achse (hier Längengradlinien) und VResolution die Anzahl der Gitterpunkte entlang der V-Achse (hier Breitengradlinien).

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

Die Abwärtsabstimmung scheint etwas hart zu sein. Es ist eine der wenigen Antworten mit und Beispiel, die diskrete Konstruktion über die parametrische Gleichung der Kugel erwähnt. Es kann auch einfacher zu verstehen sein, dass eine Kugel als ein Stapel von Kreisen betrachtet werden kann, die schrumpfen, wenn sie sich den Polen nähern.
Spacen Jasset

2
Hallo, ich wollte nur darauf hinweisen, dass die Sekunde jedes Wertes von p0, p1, p2, p3 entweder v oder vn sein sollte, im Gegensatz zu u oder un.
Nicole

8

Der Code im Beispiel wird schnell erklärt. Sie sollten in die Funktion schauen 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();
    }
}

Die Parameter latdefinieren, wie viele horizontale Linien Sie in Ihrer Kugel haben möchten und lonwie viele vertikale Linien. rist der Radius Ihrer Kugel.

Jetzt gibt es eine doppelte Iteration über lat/ lonund die Scheitelpunktkoordinaten werden mithilfe einfacher Trigonometrie berechnet.

Die berechneten Scheitelpunkte werden jetzt mit glVertex...()als an Ihre GPU gesendet. Dies GL_QUAD_STRIPbedeutet, dass Sie jeweils zwei Scheitelpunkte senden, die mit den zuvor beiden gesendeten Scheitelpunkten ein Quad bilden.

Jetzt müssen Sie nur noch verstehen, wie die Trigonometrie funktioniert, aber ich denke, Sie können es leicht herausfinden.


@PintoDoido: Es war von OPs ursprünglichem Link, der irgendwann gestorben ist; Ich habe den Link auf Archive.org erstellt und die Funktion aus Gründen der Übersichtlichkeit in diese Antwort umgewandelt.
Genpfault

2
Der Radius fehlt.
Thomasantunes

1
Der erste Parameter "double r" wird nicht verwendet.
Ollydbg23

1
Das ist richtig. Das Codebeispiel ist nicht Teil meiner ursprünglichen Antwort. @genpfault: Sie haben das Codebeispiel in einer Bearbeitung hinzugefügt. Können Sie bitte das Beispiel beheben?
Constantinius

1
Vielen Dank :)
Constantinius

1

Wenn Sie schlau wie ein Fuchs sein wollen, können Sie den Code von GLU einen halben Zoll groß machen. Überprüfen Sie den MesaGL-Quellcode (http://cgit.freedesktop.org/mesa/mesa/).


4
Obwohl ich die Bedeutung von "halber Zoll" in diesem Zusammenhang verstanden habe, denke ich, dass Sie es vielleicht für die anderen 95% der Leser bearbeiten möchten, die nicht fließend Cockney-Reim-Slang sprechen !
Flexo


1

Mein Beispiel für die Verwendung eines Dreiecksstreifens zum Zeichnen einer "polaren" Kugel besteht darin, Punkte paarweise zu zeichnen:

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

Der erste eingegebene Punkt (glVertex3f) folgt der parametrischen Gleichung, und der zweite Punkt wird um einen einzelnen Schritt des Alpha-Winkels (von der nächsten Parallele) verschoben.


1

Obwohl die akzeptierte Antwort die Frage löst, gibt es am Ende ein kleines Missverständnis. Dodekaeder sind (oder könnten) reguläre Polyeder sein, bei denen alle Flächen die gleiche Fläche haben. Dies scheint beim Epcot der Fall zu sein (der übrigens überhaupt kein Dodekaeder ist ). Da die von @Kevin vorgeschlagene Lösung diese Eigenschaft nicht bietet, dachte ich, ich könnte einen Ansatz hinzufügen, der dies tut.

Ein guter Weg, um ein Polyeder mit N-Gesicht zu erzeugen, bei dem alle Eckpunkte in derselben Kugel liegen und alle Flächen eine ähnliche Fläche / Oberfläche haben, beginnt mit einem Ikosaeder und der iterativen Unterteilung und Normalisierung seiner dreieckigen Flächen (wie in der akzeptierten Antwort vorgeschlagen) ). Dodekaeder zum Beispiel sind tatsächlich abgeschnittene Ikosaeder .

Normale Ikosaeder haben 20 Flächen (12 Eckpunkte) und können leicht aus 3 goldenen Rechtecken konstruiert werden. Es geht nur darum, dies als Ausgangspunkt anstelle eines Oktaeders zu haben. Ein Beispiel finden Sie hier .

Ich weiß, dass dies ein wenig vom Thema abweicht, aber ich glaube, es kann hilfreich sein, wenn jemand hierher kommt und nach diesem speziellen Fall sucht.


0

Eine Möglichkeit besteht darin, ein Quad zu erstellen, das der Kamera zugewandt ist, und einen Scheitelpunkt- und Fragment-Shader zu schreiben, der etwas rendert, das wie eine Kugel aussieht. Sie können Gleichungen für einen Kreis / eine Kugel verwenden, die Sie im Internet finden.

Eine schöne Sache ist, dass die Silhouette einer Kugel aus jedem Winkel gleich aussieht. Befindet sich die Kugel jedoch nicht in der Mitte einer perspektivischen Ansicht, erscheint sie möglicherweise eher wie eine Ellipse. Sie könnten die Gleichungen dafür ausarbeiten und sie in die Fragmentschattierung einfügen. Dann muss sich die Lichtschattierung ändern, wenn sich der Spieler bewegt, wenn sich tatsächlich ein Spieler im 3D-Raum um die Kugel bewegt.

Kann jemand kommentieren, ob er dies versucht hat oder ob es zu teuer wäre, um praktisch zu sein?


Das gilt nur bei einer Parallelprojektion. Wenn Sie eine perspektivische Projektion verwenden, ist die Silhouette der Kugel in der Rendering - Ausgabe nicht im Allgemeinen ein Kreis.
Reto Koradi

0

Python-Adaption von @Constantinius Antwort:

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();
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.