¿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.
Respuestas:
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í:
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.
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.
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.
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.
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.
fuente
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:
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) } }
fuente
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
lat
definen cuántas líneas horizontales desea tener en su esfera ylon
cuántas líneas verticales.r
es el radio de tu esfera.Ahora hay una iteración doble sobre
lat
/lon
y 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 aGL_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.
fuente
Vea el libro rojo de OpenGL: http://www.glprogramming.com/red/chapter02.html#name8 Resuelve el problema por subdivisión de polígonos.
fuente
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/).
fuente
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).
fuente
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.
fuente
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?
fuente
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()
fuente
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(); } }
fuente