it-swarm.com.de

Zeichnen einer Kugel in OpenGL ohne Verwendung von gluSphere ()?

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

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

76
Carven

Sie können dies beispielsweise mit einem platonischen Körper mit dreieckigen Seiten beginnen - beispielsweise einem Oktaeder . Nehmen Sie dann jedes Dreieck und zerlegen Sie es rekursiv in kleinere Dreiecke.

recursively drawn triangles

Sobald Sie über eine ausreichende Anzahl von Punkten verfügen, normalisieren Sie ihre Vektoren so, dass sie alle einen konstanten Abstand vom Mittelpunkt des Volumens haben. Dadurch wölben sich die Seiten in eine Form, die einer Kugel ähnelt, mit zunehmender Glätte, 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 derselbe ist, der Abstand zwischen ihnen jedoch unterschiedlich ist. Hier ein zweidimensionales Beispiel.

enter image description here

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.

enter image description here

Wir können sagen, dass C die normalisierte Form von B in Bezug auf A mit Abstand 12 ist. Wir können C mit einem 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 dem gleichen Abstand R, dann liegen die normalisierten Punkte alle auf dem Kreisbogen mit Zentrum A und Radius R.

bulging line segment

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

Dieser Prozess kann in drei Dimensionen erweitert werden. In diesem Fall erhalten Sie eine Kugel statt einen Kreis. Fügen Sie einfach eine dz-Komponente zur Normalisierungsfunktion hinzu.

normalized polygons

level 1 bulging octahedronlevel 3 bulging octahedron

Wenn Sie die Kugel unter Epcot betrachten, können Sie diese Technik bei der Arbeit sehen. Es ist ein Dodekaeder mit gewölbten Gesichtern, um es runder aussehen zu lassen.

245
Kevin

Ich werde ferner eine beliebte Methode zum Erzeugen einer Kugel anhand von Längen- und Breitengrad erläutern (ein anderer .__-Weg, icospheres wurde bereits zum Zeitpunkt der Erstellung in der beliebtesten Antwort 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]

Woher:

  • r ist der Radius;
  • u ist die Länge zwischen 0 und 2π; und
  • v ist der Breitengrad zwischen 0 und π.

Bei der Erzeugung der Kugel wird dann die parametrische Funktion in festen Intervallen ausgewertet.

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

Der folgende Pseudocode erzeugt ein Dreiecksnetz durch Auswertung einer parametrischen Funktion regelmäßige Intervalle (dies funktioniert für any parametrische Oberflächenfunktionen, nicht nur für Kugeln).

Im nachfolgenden Pseudocode ist UResolution die Anzahl der Gitterpunkte entlang der U-Achse .__ (hier Längengrade) und VResolution die Anzahl der Gitterpunkte entlang der V-Achse (hier Breitengrade)

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)
 }
}
21
Peter O.

Der Code im Beispiel wird schnell erklärt. Sie sollten in die Funktion void drawSphere(double r, int lats, int longs) schauen. Die Parameter lat legen fest, wie viele horizontale Linien Sie in Ihrer Sphäre haben möchten und lon wie viele vertikale Linien. r ist der Radius Ihrer Kugel.

Jetzt gibt es eine doppelte Iteration über lat/lon und die Scheitelpunktkoordinaten werden unter Verwendung der einfachen Trigonometrie berechnet.

Die berechneten Scheitelpunkte werden jetzt mit glVertex...() als GL_QUAD_STRIP an Ihre GPU gesendet. Das bedeutet, dass Sie jeweils zwei Scheitelpunkte senden, die mit den zuvor gesendeten zwei Quadraten ein Quad bilden.

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

2
Constantinius

Wenn Sie wie ein Fuchs schlau sein wollten, könnten Sie den Code von GLU nur einen halben Zoll erreichen. Überprüfen Sie den MesaGL-Quellcode (http://cgit.freedesktop.org/mesa/mesa/).

1
blockchaindev

Obwohl die akzeptierte Antwort die Frage löst, gibt es am Ende ein kleines Missverständnis. Dodekaeder sind (oder könnten sein) regelmäßige Polyeder, bei denen alle Flächen die gleiche Fläche haben. Das scheint der Fall des Epcot 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 Flächen zu erzeugen, bei dem alle Eckpunkte in derselben Kugel liegen nd alle seine Flächen haben eine ähnliche Fläche/Oberfläche, beginnt mit einem Ikosaeder und dem iterativen Unterteilen und Normalisieren seiner dreieckigen Flächen (wie in der akzeptierten Antwort vorgeschlagen). Dodekaeder zum Beispiel sind eigentlich abgeschnitten Ikosaeder .

Reguläre 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. Sie können ein Beispiel finden hier .

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

1
Carles Araguz

Siehe das rote Buch zu OpenGL: http://www.glprogramming.com/red/chapter02.html#name8 Es löst das Problem durch eine Polygonunterteilung.

1
Walter

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

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 vielleicht eher wie eine Ellipse. Sie können die Gleichungen dafür ausarbeiten und sie in die Fragment-Schattierung setzen. 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?

0
Steven2163712
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

Mein Beispiel, wie man mit "Dreieckstreifen" eine "polare" Kugel zeichnet, 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) setzt sich wie folgt aus der Parametergleichung zusammen, und der zweite wird um einen einzelnen Schritt des Alphawinkels (von der nächsten Parallele) verschoben.

0
bloody