Introducción
En este proyecto, crearemos una simulación del sistema solar utilizando OpenGL. La simulación incluirá el sol, los planetas y sus movimientos y rotaciones. Utilizaremos GLUT (OpenGL Utility Toolkit) para manejar las funciones de ventana y entrada, y OpenGL para la representación.
Al completar este proyecto, aprenderás:
- Conceptos básicos de programación gráfica utilizando OpenGL
- Cómo crear modelos 3D y representarlos en un entorno simulado
- Cómo manejar la entrada del usuario y actualizar la simulación en consecuencia
- Cómo implementar un sistema de iluminación básico para mejorar la calidad visual de la simulación
- Cómo organizar el código utilizando principios de programación orientada a objetos
Este proyecto asume un conocimiento básico de programación en C++ y alguna familiaridad con conceptos de programación gráfica. Proporcionará una experiencia práctica en la construcción de una aplicación gráfica simple utilizando OpenGL.
👀 Vista previa

🎯 Tareas
En este proyecto, aprenderás:
- Cómo instalar las bibliotecas necesarias y configurar el entorno de desarrollo.
- Cómo crear las clases necesarias e implementar la funcionalidad básica de rotación y revolución de los planetas.
- Cómo configurar la perspectiva y la proyección para la escena 3D.
- Cómo implementar el sistema de iluminación para mejorar la calidad visual de la simulación.
- Cómo manejar la entrada del usuario para permitir que el usuario controle la perspectiva de la simulación.
- Cómo probar y mejorar la simulación para asegurarse de que funcione como se espera.
🏆 Logros
Después de completar este proyecto, serás capaz de:
- Aplicar conceptos básicos de programación gráfica utilizando OpenGL.
- Crear modelos 3D y representarlos en un entorno simulado.
- Implementar un sistema de iluminación básico para mejorar la calidad visual de la simulación.
- Organizar el código utilizando principios de programación orientada a objetos.
- Demostrar habilidades de resolución de problemas y depuración.
Comprendiendo OpenGL y GLUT
OpenGL contiene muchas funciones de representación, pero su propósito de diseño es independiente de cualquier sistema de ventanas o sistema operativo. Por lo tanto, no incluye funciones para crear ventanas abiertas, leer eventos del teclado o del mouse, o incluso la funcionalidad más básica de mostrar ventanas. Por lo tanto, es completamente imposible crear un programa gráfico completo utilizando solo OpenGL. Además, la mayoría de los programas deben interactuar con el usuario (reaccionar a las operaciones del teclado y del mouse). GLUT ofrece esta conveniencia.
GLUT significa OpenGL Utility Toolkit. Es una biblioteca de herramientas para manejar programas OpenGL, principalmente responsable de manejar las llamadas al sistema operativo subyacente y las operaciones de E/S. Utilizar GLUT puede ocultar algunos de los detalles de la implementación de la interfaz gráfica de usuario del sistema operativo subyacente, y solo se necesita la API de GLUT para crear ventanas de aplicación, manejar eventos del mouse y del teclado, etc., logrando así la compatibilidad multiplataforma.
Vamos a instalar primero GLUT en el entorno de Experimentación:
sudo apt-get update && sudo apt-get install freeglut3 freeglut3-dev
La estructura de un programa GLUT estándar se muestra en el código siguiente:
// Encabezado básico para utilizar GLUT
#include <GL/glut.h>
// Macros básicas para crear una ventana gráfica
#define WINDOW_X_POS 50
#define WINDOW_Y_POS 50
#define WIDTH 700
#define HEIGHT 700
// Funciones de devolución de llamada registradas con GLUT
void onDisplay(void);
void onUpdate(void);
void onKeyboard(unsigned char key, int x, int y);
int main(int argc, char* argv[]) {
// Inicializar GLUT y procesar todos los argumentos de línea de comandos
glutInit(&argc, argv);
// Esta función especifica si se utiliza el modo RGBA o el modo de índice de color para la representación. También se puede especificar si se utiliza una ventana simple o doble búfer. Aquí, usamos el RGBA y una ventana doble búfer.
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
// Establece la posición de la esquina superior izquierda de la ventana cuando se crea en la pantalla
glutInitWindowPosition(WINDOW_X_POS, WINDOW_Y_POS);
// Establece el ancho y el alto de la ventana cuando se crea, por simplicidad
glutInitWindowSize(WIDTH, HEIGHT);
// Crea una ventana, y la cadena de entrada es el título de la ventana
glutCreateWindow("SolarSystem at LabEx");
// El prototipo de glutDisplayFunc es glutDisplayFunc(void (*func)(void))
// Esta es una función de devolución de llamada, y se ejecutará cada vez que GLUT determine que el contenido de una ventana necesita ser actualizado y representado.
//
// glutIdleFunc(void (*func)(void)) especifica una función a ejecutar cuando el bucle de eventos está inactivo. Esta función de devolución de llamada toma un puntero a función como único parámetro.
//
// glutKeyboardFunc(void (*func)(unsigned char key, int x, int y)) asocia una tecla del teclado con una función. Esta función se llama cuando se presiona o suelta la tecla.
//
// Por lo tanto, las tres líneas siguientes están en realidad registrando las tres funciones de devolución de llamada de teclado con GLUT
glutDisplayFunc(onDisplay);
glutIdleFunc(onUpdate);
glutKeyboardFunc(onKeyboard);
glutMainLoop();
return 0;
}
Crea un archivo main.cpp en el directorio ~/project/ y escribe el siguiente código:
//
// main.cpp
// solarsystem
//
#include <GL/glut.h>
#include "solarsystem.hpp"
#define WINDOW_X_POS 50
#define WINDOW_Y_POS 50
#define WIDTH 700
#define HEIGHT 700
SolarSystem solarsystem;
void onDisplay(void) {
solarsystem.onDisplay();
}
void onUpdate(void) {
solarsystem.onUpdate();
}
void onKeyboard(unsigned char key, int x, int y) {
solarsystem.onKeyboard(key, x, y);
}
int main(int argc, char* argv[]) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowPosition(WINDOW_X_POS, WINDOW_Y_POS);
glutCreateWindow("SolarSystem at LabEx");
glutDisplayFunc(onDisplay);
glutIdleFunc(onUpdate);
glutKeyboardFunc(onKeyboard);
glutMainLoop();
return 0;
}
Consejos
- El buffer simple implica ejecutar todos los comandos de dibujo directamente en la ventana, lo que es lento. Si la capacidad de procesamiento de la computadora no es suficiente y se utiliza el buffer simple, la pantalla puede parpadear.
- El buffer doble implica ejecutar los comandos de dibujo en un buffer en memoria, lo que es mucho más rápido. Una vez completados los comandos de dibujo, los resultados se copian a la pantalla a través de un intercambio de buffers. La ventaja de este enfoque es que si hacemos que las operaciones de dibujo se ejecuten en tiempo real con la tarjeta gráfica, cuando las tareas de dibujo son complejas, las operaciones de E/S se vuelven complejas, lo que resulta en un rendimiento más bajo. En contraste, con el buffer doble, los resultados de dibujo completados se envían directamente a la tarjeta gráfica para su representación cuando se intercambia el buffer, lo que reduce significativamente la E/S.
En OpenGL, se recomienda utilizar GLfloat para representar números de punto flotante.
Diseño de clases
En la programación orientada a objetos (OOP), es esencial aclarar primero qué objetos estamos manejando. Obviamente, en todo el sistema celestial, todos son planetas (Star), y la distinción entre planetas y estrellas solo depende de si tienen un nodo padre. Segundo, para diferentes planetas, generalmente tienen sus propios materiales, y diferentes materiales demostrarán si emiten luz o no. Por lo tanto, tenemos un modelo de objeto preliminar. Por lo tanto, dividimos los planetas en: planetas ordinarios que pueden rotar y girar alrededor de un punto (Star), planetas con materiales especiales (Planet) y planetas que pueden emitir luz (LightPlanet).
Además, para la conveniencia de la implementación de la programación, necesitamos hacer algunas suposiciones sobre el modelo de programación real en el mundo real:
- La órbita del planeta es circular;
- La velocidad de rotación permanece constante;
- Cada vez que se actualiza la pantalla, se asume que ha pasado un día.
En primer lugar, podemos considerar los siguientes pasos para implementar la lógica:
- Inicializar los objetos de los planetas;
- Inicializar el motor OpenGL, implementar onDraw y onUpdate;
- Cada planeta debe ser responsable de manejar sus propias propiedades, relación de revolución y dibujo relacionado con la transformación. Por lo tanto, al diseñar la clase del planeta, se debe proporcionar un método de dibujo draw();
- El planeta también debe manejar sus propias actualizaciones relacionadas con la rotación y la revolución para la visualización. Por lo tanto, al diseñar la clase del planeta, también se debe proporcionar un método de actualización update();
- Llamar al método draw() del planeta en onDraw();
- Llamar al método update() del planeta en onUpdate();
- Ajustar la visualización del sistema solar completo con el teclado en onKeyboard().
Además, para cada planeta, tienen los siguientes atributos:
- Color color
- Radio de revolución radius
- Velocidad de rotación selfSpeed
- Velocidad de revolución speed
- Distancia al centro del sol distance
- Planeta padre de revolución parentStar
- Ángulo de rotación actual alphaSelf
- Ángulo de revolución actual alpha
En el directorio ~/project/, cree un archivo stars.hpp. Basado en el análisis anterior, podemos diseñar el siguiente código de clase:
class Star {
public:
// Radio de revolución del planeta
GLfloat radius;
// Velocidad de revolución y rotación del planeta
GLfloat speed, selfSpeed;
// Distancia desde el centro del planeta hasta el centro del planeta padre
GLfloat distance;
// Color del planeta
GLfloat rgbaColor[4];
// Planeta padre
Star* parentStar;
// Constructor, cuando se construye un planeta, se deben proporcionar el radio de rotación, la velocidad de rotación, la velocidad de rotación y la revolución (planeta padre)
Star(GLfloat radius, GLfloat distance,
GLfloat speed, GLfloat selfSpeed,
Star* parentStar);
// Dibujar los movimientos, rotaciones y otras actividades del planeta común
void drawStar();
// Proporcionar una implementación predeterminada para llamar a drawStar()
virtual void draw() { drawStar(); }
// El parámetro es el intervalo de tiempo para cada actualización de pantalla
virtual void update(long timeSpan);
protected:
GLfloat alphaSelf, alpha;
};
class Planet : public Star {
public:
// Constructor
Planet(GLfloat radius, GLfloat distance,
GLfloat speed, GLfloat selfSpeed,
Star* parentStar, GLfloat rgbColor[3]);
// Agregar materiales a los planetas con sus propios materiales
void drawPlanet();
// Continuar abriendo la función de reescritura a sus subclases
virtual void draw() { drawPlanet(); drawStar(); }
};
class LightPlanet : public Planet {
public:
LightPlanet(GLfloat Radius, GLfloat Distance,
GLfloat Speed, GLfloat SelfSpeed,
Star* ParentStar, GLfloat rgbColor[]);
// Agregar iluminación a las estrellas que proporcionan fuentes de luz
void drawLight();
virtual void draw() { drawLight(); drawPlanet(); drawStar(); }
};
Además, también necesitamos considerar el diseño de la clase SolarSystem. En el sistema solar, es obvio que el sistema solar está compuesto por varios planetas. Para el sistema solar, la actualización de la vista después del movimiento del planeta debe ser manejada por el sistema solar. Por lo tanto, las variables miembro de SolarSystem deben incluir variables que contengan los planetas, y las funciones miembro deben manejar la actualización de la vista y los eventos de respuesta del teclado dentro del sistema solar. Por lo tanto, en el directorio ~/project/, cree un archivo solarsystem.hpp, y en él, podemos diseñar la clase SolarSystem:
class SolarSystem {
public:
SolarSystem();
~SolarSystem();
void onDisplay();
void onUpdate();
void onKeyboard(unsigned char key, int x, int y);
private:
Star *stars[STARS_NUM];
// Definir los parámetros del ángulo de visión
GLdouble viewX, viewY, viewZ;
GLdouble centerX, centerY, centerZ;
GLdouble upX, upY, upZ;
};
Consejos
- Aquí usamos la forma tradicional de matriz para administrar todos los planetas en lugar de usar el vector en C++, porque la forma tradicional de matriz es suficiente.
- Definir el ángulo de visión en OpenGL es un concepto complejo que requiere cierta extensión para explicar. Simplemente mencionaremos brevemente que definir el ángulo de visión requiere al menos nueve parámetros aquí. Explicaremos sus funciones en detalle cuando las implementemos más adelante en la siguiente sección.
Finalmente, también necesitamos considerar la configuración de los parámetros y variables básicos.
En SolarSystem, incluyendo el sol, hay un total de nueve planetas (excluyendo Plutón), pero en la clase Star que diseñamos, cada objeto Star tiene los atributos de un Star, por lo que podemos implementar adicionalmente los satélites de estos planetas, como la luna que gira alrededor de la tierra. Por lo tanto, consideramos implementar un total de diez planetas. Por lo tanto, podemos establecer el siguiente enum para indexar los planetas en una matriz:
#define STARS_NUM 10
enum STARS {
Sun, // Sol
Mercury, // Mercurio
Venus, // Venus
Earth, // Tierra
Moon, // Luna
Mars, // Marte
Jupiter, // Júpiter
Saturn, // Saturno
Uranus, // Urano
Neptune // Neptuno
};
Star * stars[STARS_NUM];
También asumimos que las velocidades de rotación son las mismas, por lo que establecemos su velocidad usando una macro:
#define TIMEPAST 1
#define SELFROTATE 3
En este momento, mueva las funciones miembro no implementadas al archivo .cpp correspondiente, y hemos completado el experimento en esta sección.
Resumen del código
Vamos a resumir el código que se necesita completar en los experimentos anteriores:
Primero, en main.cpp, creamos un objeto SolarSystem y luego delegamos la actualización de la visualización, la actualización en el estado ocioso y el manejo de eventos del teclado a glut:
Haga clic para ver el código completo
//
// main.cpp
// solarsystem
//
#include <GL/glut.h>
#include "solarsystem.hpp"
#define WINDOW_X_POS 50
#define WINDOW_Y_POS 50
#define WIDTH 700
#define HEIGHT 700
SolarSystem solarsystem;
void onDisplay(void) {
solarsystem.onDisplay();
}
void onUpdate(void) {
solarsystem.onUpdate();
}
void onKeyboard(unsigned char key, int x, int y) {
solarsystem.onKeyboard(key, x, y);
}
int main(int argc, char* argv[]) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
glutInitWindowPosition(WINDOW_X_POS, WINDOW_Y_POS);
glutCreateWindow("SolarSystem at LabEx");
glutDisplayFunc(onDisplay);
glutIdleFunc(onUpdate);
glutKeyboardFunc(onKeyboard);
glutMainLoop();
return 0;
}
Segundo, en stars.hpp, creamos las clases Star, Planet y LightPlanet:
Haga clic para ver el código completo
//
// stars.hpp
// solarsystem
//
#ifndef stars_hpp
#define stars_hpp
#include <GL/glut.h>
class Star {
public:
GLfloat radius;
GLfloat speed, selfSpeed;
GLfloat distance;
GLfloat rgbaColor[4];
Star* parentStar;
Star(GLfloat radius, GLfloat distance,
GLfloat speed, GLfloat selfSpeed,
Star* parentStar);
void drawStar();
virtual void draw() { drawStar(); }
virtual void update(long timeSpan);
protected:
GLfloat alphaSelf, alpha;
};
class Planet : public Star {
public:
Planet(GLfloat radius, GLfloat distance,
GLfloat speed, GLfloat selfSpeed,
Star* parentStar, GLfloat rgbColor[3]);
void drawPlanet();
virtual void draw() { drawPlanet(); drawStar(); }
};
class LightPlanet : public Planet {
public:
LightPlanet(GLfloat Radius, GLfloat Distance,
GLfloat Speed, GLfloat SelfSpeed,
Star* parentStar, GLfloat rgbColor[]);
void drawLight();
virtual void draw() { drawLight(); drawPlanet(); drawStar(); }
};
#endif /* star_hpp */
En el directorio ~/project/, cree un archivo stars.cpp y complete las implementaciones de las funciones miembro correspondientes de stars.hpp:
Haga clic para ver el código completo
//
// stars.cpp
// solarsystem
//
#include "stars.hpp"
#define PI 3.1415926535
Star::Star(GLfloat radius, GLfloat distance,
GLfloat speed, GLfloat selfSpeed,
Star* parentStar) {
// TODO:
}
void Star::drawStar() {
// TODO:
}
void Star::update(long timeSpan) {
// TODO:
}
Planet::Planet(GLfloat radius, GLfloat distance,
GLfloat speed, GLfloat selfSpeed,
Star* parentStar, GLfloat rgbColor[3]) :
Star(radius, distance, speed, selfSpeed, parentStar) {
// TODO:
}
void Planet::drawPlanet() {
// TODO:
}
LightPlanet::LightPlanet(GLfloat radius, GLfloat distance, GLfloat speed,
GLfloat selfSpeed, Star* parentStar, GLfloat rgbColor[3]) :
Planet(radius, distance, speed, selfSpeed, parentStar, rgbColor) {
// TODO:
}
void LightPlanet::drawLight() {
// TODO:
}
En solarsystem.hpp, se diseña la clase SolarSystem:
//
// solarsystem.hpp
// solarsystem
//
#include <GL/glut.h>
#include "stars.hpp"
#define STARS_NUM 10
class SolarSystem {
public:
SolarSystem();
~SolarSystem();
void onDisplay();
void onUpdate();
void onKeyboard(unsigned char key, int x, int y);
private:
Star *stars[STARS_NUM];
// Define the parameters of the viewing angle
GLdouble viewX, viewY, viewZ;
GLdouble centerX, centerY, centerZ;
GLdouble upX, upY, upZ;
};
En el directorio ~/project/, cree un archivo solarsystem.cpp e implemente las funciones miembro correspondientes de solarsystem.hpp:
Haga clic para ver el código completo
//
// solarsystem
//
#include "solarsystem.hpp"
#define TIMEPAST 1
#define SELFROTATE 3
enum STARS {Sun, Mercury, Venus, Earth, Moon,
Mars, Jupiter, Saturn, Uranus, Neptune};
void SolarSystem::onDisplay() {
// TODO:
}
void SolarSystem::onUpdate() {
// TODO:
}
void SolarSystem::onKeyboard(unsigned char key, int x, int y) {
// TODO:
}
SolarSystem::SolarSystem() {
// TODO:
}
SolarSystem::~SolarSystem() {
// TODO:
}
En el directorio ~/project/, cree un archivo Makefile y agregue el siguiente código a él:
Por favor, introduzca manualmente, no copie y pegue directamente, y recuerde usar la tecla <tab> en lugar de la tecla de espacio.
CXX = g++
EXEC = solarsystem
SOURCES = main.cpp stars.cpp solarsystem.cpp
OBJECTS = main.o stars.o solarsystem.o
LDFLAGS = -lglut -lGL -lGLU
all :
$(CXX) $(SOURCES) $(LDFLAGS) -o $(EXEC)
clean:
rm -f $(EXEC) *.gdb *.o
Cuando escriba el comando de compilación, preste atención a la ubicación de -lglut -lGLU -lGL. Esto es porque el uso de la opción -l en el compilador g++ es un poco especial.
Por ejemplo: foo1.cpp -lz foo2.cpp, si el archivo objetivo foo2.cpp utiliza funciones de la biblioteca z, estas funciones no se cargarán directamente. Sin embargo, si foo1.o utiliza funciones de la biblioteca z, no se producirá ningún error de compilación.
En otras palabras, todo el proceso de enlace va de izquierda a derecha. Cuando se encuentra un símbolo de función no resuelto en foo1.cpp, busca la biblioteca de enlace derecha. Cuando encuentra la opción z, la busca en z y encuentra la función, completando así el enlace sin problemas. Por lo tanto, la biblioteca con la opción -l debe estar ubicada a la derecha de todos los archivos compilados.
Finalmente, ejecute en la terminal:
make && ./solarsystem
Podrá ver que se ha creado la ventana, pero no hay nada en ella (muestra lo que hay detrás de la ventana). Esto es porque aún no hemos implementado el mecanismo de actualización de la gráfica en la ventana. Continuaremos completando el código restante en el siguiente experimento para hacer que funcione la simulación completa del sistema solar.
El concepto de matrices en OpenGL
En álgebra lineal, estamos familiarizados con el concepto de matrices, pero nuestra comprensión de su propósito y función específicos puede ser limitada. Entonces, ¿qué es exactamente una matriz?
Comencemos viendo la siguiente ecuación:
x = Ab
Donde A es una matriz, y x, b son vectores.
Perspectiva Uno:
Si x y b son ambos vectores en nuestro espacio tridimensional, ¿qué hace A? Transforma b en (o hacia) x. Desde esta perspectiva, una matriz A se puede entender como una transformación.
Ahora considere otra ecuación:
Ax = By
Donde A, B son matrices, y x, y son vectores.
Perspectiva Dos:
Para dos vectores diferentes x y y, son esencialmente iguales porque se pueden hacer iguales multiplicándolos por las matrices A y B. Desde esta perspectiva, una matriz A se puede entender como un sistema de coordenadas. En otras palabras, los vectores por sí mismos son únicos, pero definimos un sistema de coordenadas para describirlos. Dado que se usan diferentes sistemas de coordenadas, las coordenadas de los vectores variarán. En este caso, para el mismo vector, tiene diferentes coordenadas en diferentes sistemas de coordenadas.
La matriz A describe precisamente un sistema de coordenadas, y la matriz B describe otro sistema de coordenadas. Cuando estos dos sistemas de coordenadas se aplican a x y y, producen el mismo resultado, lo que significa que x y y son esencialmente el mismo vector pero con diferentes sistemas de coordenadas.
Combinando estas dos perspectivas, podemos concluir que la esencia de una matriz es describir movimiento.
En el contexto de OpenGL, hay una matriz responsable de las transformaciones de representación, conocida como el modo de matriz en OpenGL.
Como se mencionó anteriormente, una matriz puede describir tanto la transformación de un objeto como el sistema de coordenadas en el que existe. Por lo tanto, cuando se tratan diferentes operaciones, necesitamos establecer diferentes modos de matriz en OpenGL. Esto se logra usando la función glMatrixMode().
Esta función acepta tres modos diferentes: GL_PROJECTION para operaciones de proyección, GL_MODELVIEW para operaciones de vista de modelo y GL_TEXTURE para operaciones de textura.
GL_PROJECTION le dice a OpenGL que se realizarán operaciones de proyección, proyectando el objeto sobre un plano. Cuando se habilita este modo, la matriz debe ser establecida como la matriz identidad usando glLoadIdentity(), seguido de operaciones como establecer la perspectiva usando gluPerspective (explicaremos esta función en más detalle más adelante cuando discutamos el concepto de vista en OpenGL).
GL_MODELVIEW le dice a OpenGL que las siguientes declaraciones se usarán para describir una operación basada en un modelo, como establecer la perspectiva de la cámara. Del mismo modo, después de habilitar este modo, necesitamos establecer el modo de matriz de OpenGL como la matriz identidad.
GL_TEXTURE se utiliza para operaciones relacionadas con texturas, que no profundizaremos en este momento.
Si no está familiarizado con el concepto de matrices, puede entender simplemente
glMatrixMode()como declaraciones a OpenGL sobre las operaciones que vendrán. Antes de representar o rotar un objeto, debemos usarglPushMatrixpara guardar el entorno de la matriz actual; de lo contrario, pueden ocurrir errores de representación misteriosos.
APIs comunes de dibujo de imágenes en OpenGL
OpenGL proporciona muchas APIs comúnmente utilizadas relacionadas con el dibujo gráfico. Aquí, presentaremos brevemente algunas de ellas y las usaremos en el código siguiente:
- glEnable(GLenum cap): Esta función se utiliza para activar las diversas funcionalidades proporcionadas por OpenGL. El parámetro cap es una macro interna a OpenGL, que proporciona efectos como iluminación, niebla y dithering.
- glPushMatrix() y glPopMatrix(): Estas funciones guardan la matriz actual en la cima de la pila (guardando la matriz actual).
- glRotatef(alpha, x, y, z): Representa la rotación de la forma actual en sentido antihorario por alpha grados alrededor del eje
(x, y, z). - glTranslatef(distance, x, y): Representa la traslación de la forma actual por distance en la dirección
(x, y). - glutSolidSphere(GLdouble radius, GLint slices, GLint stacks): Dibuja una esfera, donde radius es el radio, slices es el número de líneas longitudinales y stacks es el número de líneas latitudinales.
- glBegin() y glEnd(): Cuando queremos dibujar una forma, necesitamos llamar a estas dos funciones antes y después del dibujo.
glBegin()especifica el tipo de forma que se va a dibujar. Por ejemplo,GL_POINTSrepresenta el dibujo de puntos,GL_LINESrepresenta el dibujo de puntos conectados por líneas,GL_TRIANGLEScompleta un triángulo con cada tres puntos yGL_POLYGONdibuja un polígono desde el primer punto hasta el n-ésimo punto, y así sucesivamente. Por ejemplo, cuando necesitamos dibujar un círculo, podemos simularlo con un polígono con muchos lados:
// r es el radio, n es el número de lados
glBegin(GL_POLYGON);
for(i=0; i<n; ++i)
glVertex2f(r*cos(2*PI/n*i), r*sin(2*PI/n*i));
glEnd();
Coordenadas de perspectiva en OpenGL
En la sección anterior, definimos nueve variables miembro en la clase SolarSystem en OpenGL:
GLdouble viewX, viewY, viewZ;
GLdouble centerX, centerY, centerZ;
GLdouble upX, upY, upZ;
Para entender estas nueve variables, primero necesitamos establecer el concepto de perspectiva de la cámara en la programación 3D con OpenGL.
Imagina que las escenas que normalmente vemos en las películas se graban desde la perspectiva de la cámara. Por lo tanto, OpenGL también tiene un concepto similar. Si imaginamos la cámara como nuestra propia cabeza, entonces:
viewX,viewY,viewZcorresponden a las coordenadas de la cabeza (cámara) en el sistema de coordenadas mundial de OpenGL;centerX,centerY,centerZcorresponden a las coordenadas del objeto que se está viendo (tal como se ve desde la cámara);upX,upY,upZcorresponden al vector de dirección que apunta hacia arriba desde la parte superior de la cabeza (parte superior de la cámara) (ya que podemos inclinar la cabeza para observar un objeto).
Con esta comprensión, ahora tienes un concepto del sistema de coordenadas en OpenGL.
Para este experimento, asumamos que la perspectiva inicial está en las coordenadas (x, -x, x). Por lo tanto, tenemos:
#define REST 700
#define REST_Y (-REST)
#define REST_Z (REST)
La posición del objeto observado (sol) está en (0,0,0), por lo que en el constructor de la clase SolarSystem, inicializamos la perspectiva de la siguiente manera:
viewX = 0;
viewY = REST_Y;
viewZ = REST_Z;
centerX = centerY = centerZ = 0;
upX = upY = 0;
upZ = 1;
Luego, podemos establecer los nueve parámetros de la perspectiva usando la función gluLookAt:
gluLookAt(viewX, viewY, viewZ, centerX, centerY, centerZ, upX, upY, upZ);
A continuación, echemos un vistazo a gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear,GLdouble zFar).
Esta función creará un volumen de visualización de perspectiva simétrico, y antes de usar esta función, el modo de matriz de OpenGL debe ser establecido a GL_PROJECTION.
Como se muestra en la siguiente figura:

La imagen en la ventana es capturada por la cámara, y el contenido real que se está capturando está en el plano lejano, mientras que el contenido mostrado está en el plano cercano. Por lo tanto, esta función requiere cuatro parámetros:
- El primer parámetro es el tamaño del ángulo de perspectiva.
- El segundo parámetro es la relación de aspecto de la ventana real, como se muestra en la figura
aspect=w/h. - El tercer parámetro es la distancia al plano cercano.
- El cuarto parámetro es la distancia al plano lejano.
Efectos de iluminación en OpenGL
OpenGL divide el sistema de iluminación en tres partes: fuentes de luz, materiales y entorno de iluminación.
Como su nombre indica, una fuente de luz es la fuente de luz, como el sol; Los materiales se refieren a las superficies de varios objetos que reciben luz, como los planetas y satélites del sistema solar aparte del sol; El entorno de iluminación incluye parámetros adicionales que determinan el efecto de iluminación final, como la reflexión de los rayos de luz, que se puede controlar estableciendo un parámetro llamado "brillo ambiente" para que la imagen final se acerque más a la realidad.
En física, cuando la luz paralela golpea una superficie lisa, la luz reflejada sigue siendo paralela. Este tipo de reflexión se llama "reflexión especular". Por otro lado, la reflexión causada por una superficie irregular se llama "reflexión difusa".

Fuentes de luz
Para implementar el sistema de iluminación en OpenGL, lo primero que debemos hacer es configurar las fuentes de luz. Cabe mencionar que OpenGL admite un número limitado de fuentes de luz (ocho en total), representadas por las macros GL_LIGHT0 a GL_LIGHT7. Pueden ser habilitadas con la función glEnable y deshabilitadas con la función glDisable. Por ejemplo: glEnable(GL_LIGHT0);
La posición de la fuente de luz se establece usando la función glLightfv, por ejemplo:
GLfloat light_position[] = {0.0f, 0.0f, 0.0f, 1.0f};
glLightfv(GL_LIGHT0, GL_POSITION, light_position); // Especifica la posición de la fuente de luz 0
La posición está representada por cuatro valores, (x, y, z, w). Cuando w es 0, significa que la fuente de luz está a una distancia infinita. Los valores de x, y y z especifican la dirección de esta fuente de luz a distancia infinita. Cuando w no es 0, representa una fuente de luz posicional, y su posición es (x/w, y/w, z/w).
Materiales
Para configurar el material de un objeto, generalmente se requieren cinco atributos:
- La intensidad de la luz que queda en el entorno después de múltiples reflexiones.
- La intensidad de la luz después de la reflexión difusa.
- La intensidad de la luz después de la reflexión especular.
- La intensidad de la luz emitida por objetos no emisores de luz en OpenGL, que es débil y no afecta a otros objetos.
- El exponente especular, que representa la rugosidad del material. Un valor más pequeño significa un material más rugoso, y cuando la luz emitida por una fuente puntual de luz brilla sobre él, se producen manchas brillantes más grandes. Por el contrario, un valor más grande significa que el material es más similar a una superficie especular, produciendo manchas brillantes más pequeñas.
OpenGL proporciona dos versiones de funciones para configurar materiales:
void glMaterialf(GLenum face, GLenum pname, TYPE param);
void glMaterialfv(GLenum face, GLenum pname, TYPE *param);
La diferencia entre ellas es que solo se necesita establecer un valor para el exponente especular, por lo que se utiliza glMaterialf. Para otras configuraciones de materiales que requieren múltiples valores, se utiliza una matriz, y se utiliza la versión con parámetros de vector de puntero, glMaterialfv. Por ejemplo:
GLfloat mat_ambient[] = {0.0f, 0.0f, 0.5f, 1.0f};
GLfloat mat_diffuse[] = {0.0f, 0.0f, 0.5f, 1.0f};
GLfloat mat_specular[] = {0.0f, 0.0f, 1.0f, 1.0f};
GLfloat mat_emission[] = {0.5f, 0.5f, 0.5f, 0.5f};
GLfloat mat_shininess = 90.0f;
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
glMaterialf (GL_FRONT, GL_SHININESS, mat_shininess);
Entorno de iluminación
Por defecto, OpenGL no maneja la iluminación. Para habilitar la funcionalidad de iluminación, se debe usar la macro GL_LIGHTING, es decir, glEnable(GL_LIGHTING);
Dibujando planetas
Cuando se dibuja un planeta, primero debemos considerar su ángulo de revolución y ángulo de rotación. Por lo tanto, podemos primero implementar la función miembro Star::update(long timeSpan) de la clase Star en el archivo ~/project/stars.cpp:
void Star::update(long timeSpan) {
alpha += timeSpan * speed; // actualizar el ángulo de revolución
alphaSelf += selfSpeed; // actualizar el ángulo de rotación
}
Después de actualizar los ángulos de revolución y rotación, podemos dibujar el planeta específico según los parámetros:
void Star::drawStar() {
glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
int n = 1440;
// Guardar el entorno de la matriz OpenGL actual
glPushMatrix();
{
// Revolución
// Si es un planeta y la distancia no es 0, entonces trasladarlo al origen por un radio
// Esta parte se utiliza para los satélites
if (parentStar!= 0 && parentStar->distance > 0) {
// Rotar la gráfica de dibujo alrededor del eje z por alpha
glRotatef(parentStar->alpha, 0, 0, 1);
// Transladar en la dirección del eje x por distance, mientras que las direcciones y y z permanecen inalteradas
glTranslatef(parentStar->distance, 0.0, 0.0);
}
// Dibujar la órbita
glBegin(GL_LINES);
for(int i=0; i<n; ++i)
glVertex2f(distance * cos(2 * PI * i / n),
distance * sin(2 * PI * i / n));
glEnd();
// Rotar alrededor del eje z por alpha
glRotatef(alpha, 0, 0, 1);
// Transladar en la dirección del eje x por distance, mientras que las direcciones y y z permanecen inalteradas
glTranslatef(distance, 0.0, 0.0);
// Rotación
glRotatef(alphaSelf, 0, 0, 1);
// Dibujar el color del planeta
glColor3f(rgbaColor[0], rgbaColor[1], rgbaColor[2]);
glutSolidSphere(radius, 40, 32);
}
// Restaurar el entorno de la matriz antes del dibujo
glPopMatrix();
}
Este código utiliza las funciones sin() y cos(), lo que requiere incluir #include<cmath>.
Dibujo de iluminación
Para la clase Planet, que representa un cuerpo celeste no luminoso, necesitamos dibujar su efecto de iluminación. Agregue el siguiente código al archivo ~/project/stars.cpp:
void Planet::drawPlanet() {
GLfloat mat_ambient[] = {0.0f, 0.0f, 0.5f, 1.0f};
GLfloat mat_diffuse[] = {0.0f, 0.0f, 0.5f, 1.0f};
GLfloat mat_specular[] = {0.0f, 0.0f, 1.0f, 1.0f};
GLfloat mat_emission[] = {rgbaColor[0], rgbaColor[1], rgbaColor[2], rgbaColor[3]};
GLfloat mat_shininess = 90.0f;
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
glMaterialf (GL_FRONT, GL_SHININESS, mat_shininess);
}
En cuanto a la clase LightPlanet, que representa un cuerpo celeste luminoso, no solo necesitamos configurar su material de iluminación, sino también establecer la posición de su fuente de luz:
void LightPlanet::drawLight() {
GLfloat light_position[] = {0.0f, 0.0f, 0.0f, 1.0f};
GLfloat light_ambient[] = {0.0f, 0.0f, 0.0f, 1.0f};
GLfloat light_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
glLightfv(GL_LIGHT0, GL_POSITION, light_position); // Especifica la posición de la fuente de luz 0
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); // Representa la intensidad de los rayos de luz de diversas fuentes que alcanzan el material, después de múltiples reflexiones y trazado
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); // Intensidad de la luz después de la reflexión difusa
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); // Intensidad de la luz después de la reflexión especular
}
Dibujando la ventana
En la sección anterior, mencionamos las dos funciones más importantes para manejar la visualización de imágenes: glutDisplayFunc y glutIdleFunc. glutDisplayFunc ejecuta la función de devolución de llamada cuando GLUT determina que el contenido de la ventana necesita ser actualizado, mientras que glutIdleFunc maneja la función de devolución de llamada cuando el bucle de eventos está inactivo.
Para hacer que todo el sistema solar se mueva, necesitamos considerar cuándo actualizar las posiciones de los planetas y cuándo actualizar la vista.
Obviamente, glutDisplayFunc debe centrarse en actualizar la vista, y cuando el evento está inactivo, podemos comenzar a actualizar las posiciones de los planetas. Una vez actualizadas las posiciones, luego podemos llamar a la función de actualización de vista para actualizar la visualización.
Por lo tanto, primero podemos implementar la función miembro SolarSystem::onUpdate() llamada en glutDisplayFunc:
#define TIMEPAST 1 // Asumir que cada actualización toma un día
void SolarSystem::onUpdate() {
for (int i=0; i<STARS_NUM; i++)
stars[i]->update(TIMEPAST); // Actualizar las posiciones de las estrellas
this->onDisplay(); // Actualizar la visualización
}
Luego, la actualización de la vista se implementa en SolarSystem::onDisplay():
void SolarSystem::onDisplay() {
// Limpiar el búfer del viewport
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Limpiar y establecer el búfer de color
glClearColor(.7f,.7f,.7f,.1f);
// Especificar la matriz actual como la matriz de proyección
glMatrixMode(GL_PROJECTION);
// Especificar la matriz especificada como una matriz identidad
glLoadIdentity();
// Especificar el volumen de vista actual
gluPerspective(75.0f, 1.0f, 1.0f, 40000000);
// Especificar la matriz actual como la matriz de vista y modelo
glMatrixMode(GL_MODELVIEW);
// Especificar la matriz actual como una matriz identidad
glLoadIdentity();
// Definir la matriz de vista y multiplicarla por la matriz actual
gluLookAt(viewX, viewY, viewZ, centerX, centerY, centerZ, upX, upY, upZ);
// Establecer la primera fuente de luz (fuente de luz 0)
glEnable(GL_LIGHT0);
// Habilitar la iluminación
glEnable(GL_LIGHTING);
// Habilitar la prueba de profundidad, automáticamente ocultar gráficos cubiertos basados en las coordenadas
glEnable(GL_DEPTH_TEST);
// Dibujar los planetas
for (int i=0; i<STARS_NUM; i++)
stars[i]->draw();
// Usamos GLUT_DOUBLE cuando inicializamos el modo de visualización en la función principal
// Necesitamos usar glutSwapBuffers para implementar el intercambio de búferes para el doble búfer después de terminar el dibujo
glutSwapBuffers();
}
Constructores y destructores de clases
Los constructores de las clases definidas en stars.hpp deben inicializar las variables miembro de las clases. Esta parte es relativamente simple e incluso se pueden usar los destructores predeterminados, así que implemente estos constructores por su cuenta:
Star::Star(GLfloat radius, GLfloat distance,
GLfloat speed, GLfloat selfSpeed,
Star* parent);
Planet::Planet(GLfloat radius, GLfloat distance,
GLfloat speed, GLfloat selfSpeed,
Star* parent, GLfloat rgbColor[3]);
LightPlanet::LightPlanet(GLfloat radius, GLfloat distance,
GLfloat speed, GLfloat selfSpeed,
Star* parent, GLfloat rgbColor[3]);
Consejo: Tenga en cuenta que al inicializar la variable de velocidad, conviértala a velocidad angular. La fórmula de conversión es: alpha_speed = 360/speed.
Para el constructor de solarsystem.cpp, necesitamos inicializar todos los planetas. Aquí se proporcionan los parámetros entre los planetas para mayor conveniencia:
// Radio de revolución
#define SUN_RADIUS 48.74
#define MER_RADIUS 7.32
#define VEN_RADIUS 18.15
#define EAR_RADIUS 19.13
#define MOO_RADIUS 6.15
#define MAR_RADIUS 10.19
#define JUP_RADIUS 42.90
#define SAT_RADIUS 36.16
#define URA_RADIUS 25.56
#define NEP_RADIUS 24.78
// Distancia al sol
#define MER_DIS 62.06
#define VEN_DIS 115.56
#define EAR_DIS 168.00
#define MOO_DIS 26.01
#define MAR_DIS 228.00
#define JUP_DIS 333.40
#define SAT_DIS 428.10
#define URA_DIS 848.00
#define NEP_DIS 949.10
// Velocidad de movimiento
#define MER_SPEED 87.0
#define VEN_SPEED 225.0
#define EAR_SPEED 365.0
#define MOO_SPEED 30.0
#define MAR_SPEED 687.0
#define JUP_SPEED 1298.4
#define SAT_SPEED 3225.6
#define URA_SPEED 3066.4
#define NEP_SPEED 6014.8
// Velocidad de autorrotación
#define SELFROTATE 3
// Definir una macro para facilitar la configuración de una matriz multidimensional
#define SET_VALUE_3(name, value0, value1, value2) \
((name)[0])=(value0), ((name)[1])=(value1), ((name)[2])=(value2)
// En el experimento anterior, definimos el enum de los planetas
enum STARS {Sun, Mercury, Venus, Earth, Moon,
Mars, Jupiter, Saturn, Uranus, Neptune};
Consejo:
Definimos una macro SET_VALUE_3 aquí. Usted puede pensar que podemos escribir una función para lograr el mismo propósito.
De hecho, las macros completan el trabajo de sustitución general durante el proceso de compilación, mientras que la definición de funciones
Esto requiere operaciones de pila de funciones durante la llamada, lo que es mucho menos eficiente que el procesamiento de macros durante el proceso de compilación.
Por lo tanto, las macros pueden ser más eficientes.
Sin embargo, es importante tener en cuenta que aunque las macros pueden ser más eficientes, el uso excesivo puede conducir a un código feo y menos legible. Por otro lado, se alienta el uso adecuado de macros.
Por lo tanto, podemos implementar el constructor de la clase SolarSystem, donde los colores de los planetas se seleccionan al azar. Los lectores pueden cambiar los colores de los planetas por sí mismos:
SolarSystem::SolarSystem() {
// Definir la vista en perspectiva, como hemos discutido la inicialización de la vista en perspectiva antes
viewX = 0;
viewY = REST_Y;
viewZ = REST_Z;
centerX = centerY = centerZ = 0;
upX = upY = 0;
upZ = 1;
// Sol
GLfloat rgbColor[3] = {1, 0, 0};
stars[Sun] = new LightPlanet(SUN_RADIUS, 0, 0, SELFROTATE, 0, rgbColor);
// Mercurio
SET_VALUE_3(rgbColor,.2,.2,.5);
stars[Mercury] = new Planet(MER_RADIUS, MER_DIS, MER_SPEED, SELFROTATE, stars[Sun], rgbColor);
// Venus
SET_VALUE_3(rgbColor, 1,.7, 0);
stars[Venus] = new Planet(VEN_RADIUS, VEN_DIS, VEN_SPEED, SELFROTATE, stars[Sun], rgbColor);
// Tierra
SET_VALUE_3(rgbColor, 0, 1, 0);
stars[Earth] = new Planet(EAR_RADIUS, EAR_DIS, EAR_SPEED, SELFROTATE, stars[Sun], rgbColor);
// Luna
SET_VALUE_3(rgbColor, 1, 1, 0);
stars[Moon] = new Planet(MOO_RADIUS, MOO_DIS, MOO_SPEED, SELFROTATE, stars[Earth], rgbColor);
// Marte
SET_VALUE_3(rgbColor, 1,.5,.5);
stars[Mars] = new Planet(MAR_RADIUS, MAR_DIS, MAR_SPEED, SELFROTATE, stars[Sun], rgbColor);
// Júpiter
SET_VALUE_3(rgbColor, 1, 1,.5);
stars[Jupiter] = new Planet(JUP_RADIUS, JUP_DIS, JUP_SPEED, SELFROTATE, stars[Sun], rgbColor);
// Saturno
SET_VALUE_3(rgbColor,.5, 1,.5);
stars[Saturn] = new Planet(SAT_RADIUS, SAT_DIS, SAT_SPEED, SELFROTATE, stars[Sun], rgbColor);
// Urano
SET_VALUE_3(rgbColor,.4,.4,.4);
stars[Uranus] = new Planet(URA_RADIUS, URA_DIS, URA_SPEED, SELFROTATE, stars[Sun], rgbColor);
// Neptuno
SET_VALUE_3(rgbColor,.5,.5, 1);
stars[Neptune] = new Planet(NEP_RADIUS, NEP_DIS, NEP_SPEED, SELFROTATE, stars[Sun], rgbColor);
}
Además, no olvide liberar la memoria asignada en el destructor:
SolarSystem::~SolarSystem() {
for(int i = 0; i<STARS_NUM; i++)
delete stars[i];
}
Implementación del cambio de perspectiva con las teclas del teclado
Para controlar el cambio de perspectiva, podemos utilizar las cinco teclas w, a, s, d, x del teclado y la tecla r para restablecer la perspectiva. Primero, necesitamos determinar la magnitud del cambio de perspectiva después de cada pulsación de tecla. Aquí, definimos una macro OFFSET. Luego, podemos determinar el comportamiento de pulsación de tecla del usuario inspeccionando el parámetro key pasado.
#define OFFSET 20
void SolarSystem::onKeyboard(unsigned char key, int x, int y) {
switch (key) {
case 'w': viewY += OFFSET; break; // Aumenta la posición del eje Y de la cámara en OFFSET
case 's': viewZ += OFFSET; break;
case 'S': viewZ -= OFFSET; break;
case 'a': viewX -= OFFSET; break;
case 'd': viewX += OFFSET; break;
case 'x': viewY -= OFFSET; break;
case 'r':
viewX = 0; viewY = REST_Y; viewZ = REST_Z;
centerX = centerY = centerZ = 0;
upX = upY = 0; upZ = 1;
break;
case 27: exit(0); break;
default: break;
}
}
Ejecutar y probar
Principalmente implementamos el código en los archivos stars.cpp y solarsystem.cpp.
El código de stars.cpp es el siguiente:
Haga clic para ver el código completo
//
// stars.cpp
// solarsystem
//
#include "stars.hpp"
#include <cmath>
#define PI 3.1415926535
Star::Star(GLfloat radius, GLfloat distance,
GLfloat speed, GLfloat selfSpeed,
Star* parent) {
this->radius = radius;
this->selfSpeed = selfSpeed;
this->alphaSelf = this->alpha = 0;
this->distance = distance;
for (int i = 0; i < 4; i++)
this->rgbaColor[i] = 1.0f;
this->parentStar = parent;
if (speed > 0)
this->speed = 360.0f / speed;
else
this->speed = 0.0f;
}
void Star::drawStar() {
glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
int n = 1440;
glPushMatrix();
{
if (parentStar!= 0 && parentStar->distance > 0) {
glRotatef(parentStar->alpha, 0, 0, 1);
glTranslatef(parentStar->distance, 0.0, 0.0);
}
glBegin(GL_LINES);
for(int i=0; i<n; ++i)
glVertex2f(distance * cos(2 * PI * i / n),
distance * sin(2 * PI * i / n));
glEnd();
glRotatef(alpha, 0, 0, 1);
glTranslatef(distance, 0.0, 0.0);
glRotatef(alphaSelf, 0, 0, 1);
glColor3f(rgbaColor[0], rgbaColor[1], rgbaColor[2]);
glutSolidSphere(radius, 40, 32);
}
glPopMatrix();
}
void Star::update(long timeSpan) {
alpha += timeSpan * speed;
alphaSelf += selfSpeed;
}
Planet::Planet(GLfloat radius, GLfloat distance,
GLfloat speed, GLfloat selfSpeed,
Star* parent, GLfloat rgbColor[3]) :
Star(radius, distance, speed, selfSpeed, parent) {
rgbaColor[0] = rgbColor[0];
rgbaColor[1] = rgbColor[1];
rgbaColor[2] = rgbColor[2];
rgbaColor[3] = 1.0f;
}
void Planet::drawPlanet() {
GLfloat mat_ambient[] = {0.0f, 0.0f, 0.5f, 1.0f};
GLfloat mat_diffuse[] = {0.0f, 0.0f, 0.5f, 1.0f};
GLfloat mat_specular[] = {0.0f, 0.0f, 1.0f, 1.0f};
GLfloat mat_emission[] = {rgbaColor[0], rgbaColor[1], rgbaColor[2], rgbaColor[3]};
GLfloat mat_shininess = 90.0f;
glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient);
glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
glMaterialf (GL_FRONT, GL_SHININESS, mat_shininess);
}
LightPlanet::LightPlanet(GLfloat radius, GLfloat distance, GLfloat speed,
GLfloat selfSpeed, Star* parent, GLfloat rgbColor[3]) :
Planet(radius, distance, speed, selfSpeed, parent, rgbColor) {
;
}
void LightPlanet::drawLight() {
GLfloat light_position[] = {0.0f, 0.0f, 0.0f, 1.0f};
GLfloat light_ambient[] = {0.0f, 0.0f, 0.0f, 1.0f};
GLfloat light_diffuse[] = {1.0f, 1.0f, 1.0f, 1.0f};
GLfloat light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f};
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);
}
El código de solarsystem.cpp es el siguiente:
Haga clic para ver el código completo
//
// solarsystem.cpp
// solarsystem
//
#include "solarsystem.hpp"
#define REST 700
#define REST_Z (REST)
#define REST_Y (-REST)
void SolarSystem::onDisplay() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(.7f,.7f,.7f,.1f);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(75.0f, 1.0f, 1.0f, 40000000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(viewX, viewY, viewZ, centerX, centerY, centerZ, upX, upY, upZ);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
for (int i=0; i<STARS_NUM; i++)
stars[i]->draw();
glutSwapBuffers();
}
#define TIMEPAST 1
void SolarSystem::onUpdate() {
for (int i=0; i<STARS_NUM; i++)
stars[i]->update(TIMEPAST);
this->onDisplay();
}
#define OFFSET 20
void SolarSystem::onKeyboard(unsigned char key, int x, int y) {
switch (key) {
case 'w': viewY += OFFSET; break;
case 's': viewZ += OFFSET; break;
case 'S': viewZ -= OFFSET; break;
case 'a': viewX -= OFFSET; break;
case 'd': viewX += OFFSET; break;
case 'x': viewY -= OFFSET; break;
case 'r':
viewX = 0; viewY = REST_Y; viewZ = REST_Z;
centerX = centerY = centerZ = 0;
upX = upY = 0; upZ = 1;
break;
case 27: exit(0); break;
default: break;
}
}
#define SUN_RADIUS 48.74
#define MER_RADIUS 7.32
#define VEN_RADIUS 18.15
#define EAR_RADIUS 19.13
#define MOO_RADIUS 6.15
#define MAR_RADIUS 10.19
#define JUP_RADIUS 42.90
#define SAT_RADIUS 36.16
#define URA_RADIUS 25.56
#define NEP_RADIUS 24.78
#define MER_DIS 62.06
#define VEN_DIS 115.56
#define EAR_DIS 168.00
#define MOO_DIS 26.01
#define MAR_DIS 228.00
#define JUP_DIS 333.40
#define SAT_DIS 428.10
#define URA_DIS 848.00
#define NEP_DIS 949.10
#define MER_SPEED 87.0
#define VEN_SPEED 225.0
#define EAR_SPEED 365.0
#define MOO_SPEED 30.0
#define MAR_SPEED 687.0
#define JUP_SPEED 1298.4
#define SAT_SPEED 3225.6
#define URA_SPEED 3066.4
#define NEP_SPEED 6014.8
#define SELFROTATE 3
enum STARS {Sun, Mercury, Venus, Earth, Moon,
Mars, Jupiter, Saturn, Uranus, Neptune};
#define SET_VALUE_3(name, value0, value1, value2) \
((name)[0])=(value0), ((name)[1])=(value1), ((name)[2])=(value2)
SolarSystem::SolarSystem() {
viewX = 0;
viewY = REST_Y;
viewZ = REST_Z;
centerX = centerY = centerZ = 0;
upX = upY = 0;
upZ = 1;
GLfloat rgbColor[3] = {1, 0, 0};
stars[Sun] = new LightPlanet(SUN_RADIUS, 0, 0, SELFROTATE, 0, rgbColor);
SET_VALUE_3(rgbColor,.2,.2,.5);
stars[Mercury] = new Planet(MER_RADIUS, MER_DIS, MER_SPEED, SELFROTATE, stars[Sun], rgbColor);
SET_VALUE_3(rgbColor, 1,.7, 0);
stars[Venus] = new Planet(VEN_RADIUS, VEN_DIS, VEN_SPEED, SELFROTATE, stars[Sun], rgbColor);
SET_VALUE_3(rgbColor, 0, 1, 0);
stars[Earth] = new Planet(EAR_RADIUS, EAR_DIS, EAR_SPEED, SELFROTATE, stars[Sun], rgbColor);
SET_VALUE_3(rgbColor, 1, 1, 0);
stars[Moon] = new Planet(MOO_RADIUS, MOO_DIS, MOO_SPEED, SELFROTATE, stars[Earth], rgbColor);
SET_VALUE_3(rgbColor, 1,.5,.5);
stars[Mars] = new Planet(MAR_RADIUS, MAR_DIS, MAR_SPEED, SELFROTATE, stars[Sun], rgbColor);
SET_VALUE_3(rgbColor, 1, 1,.5);
stars[Jupiter] = new Planet(JUP_RADIUS, JUP_DIS, JUP_SPEED, SELFROTATE, stars[Sun], rgbColor);
SET_VALUE_3(rgbColor,.5, 1,.5);
stars[Saturn] = new Planet(SAT_RADIUS, SAT_DIS, SAT_SPEED, SELFROTATE, stars[Sun], rgbColor);
SET_VALUE_3(rgbColor,.4,.4,.4);
stars[Uranus] = new Planet(URA_RADIUS, URA_DIS, URA_SPEED, SELFROTATE, stars[Sun], rgbColor);
SET_VALUE_3(rgbColor,.5,.5, 1);
stars[Neptune] = new Planet(NEP_RADIUS, NEP_DIS, NEP_SPEED, SELFROTATE, stars[Sun], rgbColor);
}
SolarSystem::~SolarSystem() {
for(int i = 0; i<STARS_NUM; i++)
delete stars[i];
}
Ejecute en la terminal:
make && ./solarsystem
El resultado se muestra en la figura:

Dado que los colores de los planetas son sencillos, el efecto de iluminación no es muy obvio pero todavía es visible. Por ejemplo, en el lado derecho del Júpiter amarillo, hay una apariencia blanquecina.
Resumen
En este proyecto, hemos logrado un modelo simple del sistema solar. Al utilizar el sistema de iluminación de OpenGL, podemos ver los efectos de iluminación de los planetas mientras giran alrededor del sol. Además, podemos ajustar la perspectiva utilizando el teclado para observar el sistema solar desde diferentes ángulos.



