¿Dibujar Sphere en OpenGL sin usar gluSphere ()?

81

¿Existen tutoriales que expliquen cómo puedo dibujar una esfera en OpenGL sin tener que usar gluSphere()?

Muchos de los tutoriales 3D para OpenGL están solo en cubos. He buscado, pero la mayoría de las soluciones para dibujar una esfera son para usar gluSphere(). También hay un sitio que tiene el código para dibujar una esfera en este sitio, pero no explica las matemáticas detrás del dibujo de la esfera. También tengo otras versiones de cómo dibujar la esfera en un polígono en lugar de quads en ese enlace. Pero, de nuevo, no entiendo cómo se dibujan las esferas con el código. Quiero poder visualizar para poder modificar la esfera si es necesario.

Carven
fuente
3
busque coordenadas esféricas para la explicación matemática (específicamente la conversión de coordenadas esféricas a coordenadas cartesianas).
Ned Bingham

Respuestas:

270

Una forma de hacerlo es comenzar con un sólido platónico con lados triangulares, un octaedro , por ejemplo. Luego, toma cada triángulo y divídelo recursivamente en triángulos más pequeños, así:

triángulos dibujados de forma recursiva

Una vez que tenga una cantidad suficiente de puntos, normalice sus vectores para que todos estén a una distancia constante del centro del sólido. Esto hace que los lados se abulten en una forma que se asemeja a una esfera, con una suavidad creciente a medida que aumenta el número de puntos.

La normalización aquí significa mover un punto para que su ángulo en relación con otro punto sea el mismo, pero la distancia entre ellos sea diferente. Aquí hay un ejemplo bidimensional.

ingrese la descripción de la imagen aquí

A y B están separados por 6 unidades. Pero suponga que queremos encontrar un punto en la línea AB que esté a 12 unidades de A.

ingrese la descripción de la imagen aquí

Podemos decir que C es la forma normalizada de B con respecto a A, con distancia 12. Podemos obtener C con código como este:

#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

Si hacemos este proceso de normalización en muchos puntos, todos con respecto al mismo punto A y con la misma distancia R, entonces los puntos normalizados estarán todos en el arco de un círculo con centro A y radio R.

segmento de línea abultado

Aquí, los puntos negros comienzan en una línea y "sobresalen" en un arco.

Este proceso se puede extender a tres dimensiones, en cuyo caso se obtiene una esfera en lugar de un círculo. Simplemente agregue un componente dz a la función normalizar.

polígonos normalizados

octaedro abultado de nivel 1 octaedro abultado de nivel 3

Si miras la esfera en Epcot , puedes ver esta técnica en funcionamiento. es un dodecaedro con caras abultadas para que parezca más redondo.

Kevin
fuente
1
Prefiero eliminar el enlace a la esfera de Epcot. Puede confundir a los principiantes porque allí cada triángulo se subdivide nuevamente en tres triángulos isósceles (similar a la primera parte de sqrt (3) -subdivision). Seguro que encuentras un mejor ejemplo.
Christian Rau
Tengo una buena implementación de esto en mi máquina doméstica. Estaré encantado de editar algunas capturas de pantalla después del trabajo.
Kevin
Gracias por la idea. Pero no entiendo la parte sobre cómo al normalizar los vectores, podría abombar los lados en una forma que se parezca a la esfera. ¿Cómo abrocho los lados?
Carven
1
@xEnOn, he editado mi respuesta para explicar un poco más la normalización. Creo que el problema es que normalización no es el término técnico real para el proceso que estaba tratando de explicar, por lo que sería difícil para usted encontrar más información sobre él en cualquier otro lugar. Lo siento por eso.
Kevin
1
Quizás una mejor manera de explicar el proceso de "normalización" aquí es que los puntos se proyectan en una esfera. Además, tenga en cuenta que los resultados difieren dependiendo de si la normalización / proyección se aplica solo una vez al final (después de toda la subdivisión, que parece ser lo que se sugiere aquí) o intercalada con los pasos de subdivisión (recursivos). Parece que proyectar solo una vez al final produce vértices agrupados cerca de los vértices del octaedro inicial, mientras que la subdivisión y proyección entrelazadas produce distancias uniformes entre vértices.
Tyler Streeter
26

Explicaré con más detalle una forma popular de generar una esfera usando la latitud y la longitud (otra forma, las icosferas , ya se explicó en la respuesta más popular en el momento de escribir este artículo).

Una esfera se puede expresar mediante la siguiente ecuación paramétrica:

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

Dónde:

  • r es el radio;
  • u es la longitud, que va de 0 a 2π; y
  • v es la latitud, que va de 0 a π.

Entonces, generar la esfera implica evaluar la función paramétrica a intervalos fijos.

Por ejemplo, para generar 16 líneas de longitud, habrá 17 líneas de cuadrícula a lo largo del eje u , con un paso de π / 8 (2π / 16) (la línea 17 se envuelve).

El siguiente pseudocódigo genera una malla triangular al evaluar una función paramétrica a intervalos regulares (esto funciona para cualquier función de superficie paramétrica, no solo esferas).

En el pseudocódigo siguiente, UResolution es el número de puntos de la cuadrícula a lo largo del eje U (aquí, líneas de longitud), y VResolution es el número de puntos de la cuadrícula a lo largo del eje V (aquí, líneas de latitud)

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.
fuente
El voto negativo parece un poco duro. Es una de las únicas respuestas con un ejemplo que menciona la construcción discreta a través de la ecuación paramétrica de la esfera. También puede ser más fácil de entender sobre la base de que una esfera puede considerarse como una pila de círculos que se encogen a medida que se acercan a los polos.
Spacen Jasset
2
Hola, solo quería señalar que el segundo de cada valor de p0, p1, p2, p3 debe ser v o vn, a diferencia de u o un.
nicole
9

El código de la muestra se explica rápidamente. Deberías mirar en la función 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();
    }
}

Los parámetros latdefinen cuántas líneas horizontales desea tener en su esfera y loncuántas líneas verticales. res el radio de tu esfera.

Ahora hay una iteración doble sobre lat/ lony se calculan las coordenadas del vértice, usando trigonometría simple.

Los vértices calculados ahora se envían a su GPU usando glVertex...()como a GL_QUAD_STRIP, lo que significa que está enviando cada uno dos vértices que forman un cuádruple con los dos enviados anteriormente.

Todo lo que tienes que entender ahora es cómo funcionan las funciones de trigonometría, pero supongo que puedes resolverlo fácilmente.

Constantinius
fuente
@PintoDoido: Era del enlace original de OP que murió en algún momento; Archivé el enlace y edité la función en esta respuesta para mayor claridad.
genpfault
2
Falta el radio.
tomasantunes
1
El primer parámetro "doble r" no se utiliza.
ollydbg23
1
Eso es correcto. El ejemplo de código no es parte de mi respuesta original. @genpfault: agregó la muestra de código en una edición. ¿Puedes arreglar el ejemplo?
Constantinius
1
Muchas gracias :)
Constantinius
1

Si quisieras ser astuto como un zorro, podrías usar media pulgada del código de GLU. Consulte el código fuente de MesaGL (http://cgit.freedesktop.org/mesa/mesa/).

blockchaindev
fuente
4
Si bien entendí el significado de "media pulgada" en este contexto, creo que es posible que desee editarlo para el otro 95% de los lectores que no dominan el argot de rima cockney .
Flexo
1

Mi ejemplo de cómo usar 'tira triangular' para dibujar una esfera "polar", consiste en dibujar puntos en pares:

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

El primer punto introducido (glVertex3f) es la siguiente ecuación paramétrica y el segundo se desplaza en un solo paso del ángulo alfa (desde el siguiente paralelo).

sangriento
fuente
1

Aunque la respuesta aceptada resuelve la pregunta, hay un pequeño error al final. Los dodecaedros son (o podrían ser) poliedros regulares donde todas las caras tienen la misma área. Ese parece ser el caso del Epcot (que, por cierto, no es un dodecaedro en absoluto). Dado que la solución propuesta por @Kevin no proporciona esta característica, pensé que podría agregar un enfoque que sí lo haga.

Una buena manera de generar un poliedro de N caras donde todos los vértices se encuentran en la misma esfera y todas sus caras tienen un área / superficie similar es comenzar con un icosaedro y subdividir y normalizar iterativamente sus caras triangulares (como se sugiere en la respuesta aceptada ). Los dodecaedros, por ejemplo, son en realidad icosaedros truncados .

Los icosaedros regulares tienen 20 caras (12 vértices) y pueden construirse fácilmente a partir de 3 rectángulos áureos; es solo cuestión de tener esto como punto de partida en lugar de un octaedro. Puede encontrar un ejemplo aquí .

Sé que esto está un poco fuera de tema, pero creo que puede ayudar si alguien viene buscando este caso específico.

Carles Araguz
fuente
0

Una forma es hacer un cuadrante que mire hacia la cámara y escribir un sombreador de vértices y fragmentos que represente algo que parezca una esfera. Puede utilizar ecuaciones para un círculo / esfera que puede encontrar en Internet.

Una cosa buena es que la silueta de una esfera se ve igual desde cualquier ángulo. Sin embargo, si la esfera no está en el centro de una vista en perspectiva, tal vez se parezca más a una elipse. Podrías resolver las ecuaciones para esto y ponerlas en el sombreado de fragmentos. Luego, el sombreado de la luz debe cambiar a medida que el jugador se mueve, si es que tiene un jugador moviéndose en el espacio 3D alrededor de la esfera.

¿Alguien puede comentar si ha probado esto o si sería demasiado caro para ser práctico?

Steven2163712
fuente
Eso solo es cierto bajo una proyección paralela. Si usa una proyección en perspectiva, la silueta de la esfera en la salida de renderizado generalmente no es un círculo.
Reto Koradi
0

Adaptación de Python de la respuesta de @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
fuente
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
fuente