Introdução
Neste projeto, criaremos uma simulação do sistema solar usando OpenGL. A simulação incluirá o sol, os planetas e seus movimentos e rotações. Usaremos GLUT (OpenGL Utility Toolkit) para lidar com as funções de janela e entrada, e OpenGL para renderização.
Ao concluir este projeto, você aprenderá:
- Conceitos básicos de programação gráfica usando OpenGL
- Como criar modelos 3D e renderizá-los em um ambiente simulado
- Como lidar com a entrada do usuário e atualizar a simulação de acordo
- Como implementar um sistema de iluminação básico para aprimorar a qualidade visual da simulação
- Como organizar o código usando princípios de programação orientada a objetos
Este projeto pressupõe uma compreensão básica da programação C++ e alguma familiaridade com conceitos de programação gráfica. Ele fornecerá uma experiência prática na construção de uma aplicação gráfica simples usando OpenGL.
👀 Pré-visualização

🎯 Tarefas
Neste projeto, você aprenderá:
- Como instalar as bibliotecas necessárias e configurar o ambiente de desenvolvimento.
- Como criar as classes necessárias e implementar a funcionalidade básica para rotação e revolução dos planetas.
- Como configurar a perspectiva e a projeção para a cena 3D.
- Como implementar o sistema de iluminação para aprimorar a qualidade visual da simulação.
- Como lidar com a entrada do usuário para permitir que o usuário controle a perspectiva da simulação.
- Como testar e refinar a simulação para garantir que ela funcione conforme o esperado.
🏆 Conquistas
Após concluir este projeto, você será capaz de:
- Aplicar conceitos básicos de programação gráfica usando OpenGL.
- Criar modelos 3D e renderizá-los em um ambiente simulado.
- Implementar um sistema de iluminação básico para aprimorar a qualidade visual da simulação.
- Organizar o código usando princípios de programação orientada a objetos.
- Demonstrar habilidades de resolução de problemas e depuração.
Entendendo OpenGL e GLUT
OpenGL contém muitas funções de renderização, mas seu propósito de design é independente de qualquer sistema de janelas ou sistema operacional. Portanto, não inclui funções para criar janelas abertas, ler eventos do teclado ou mouse, ou mesmo a funcionalidade mais básica de exibição de janelas. Assim, é completamente impossível criar um programa gráfico completo usando apenas OpenGL. Além disso, a maioria dos programas precisa interagir com o usuário (responder às operações do teclado e do mouse). GLUT fornece essa conveniência.
GLUT significa OpenGL Utility Toolkit. É uma biblioteca de ferramentas para lidar com programas OpenGL, principalmente responsável por lidar com chamadas para o sistema operacional subjacente e operações de E/S. Usar GLUT pode proteger alguns dos detalhes da implementação da GUI do sistema operacional subjacente, e apenas a API GLUT é necessária para criar janelas de aplicativos, lidar com eventos de mouse e teclado, e assim por diante, alcançando assim compatibilidade multiplataforma.
Vamos primeiro instalar GLUT no ambiente do Experimento:
sudo apt-get update && sudo apt-get install freeglut3 freeglut3-dev
A estrutura de um programa GLUT padrão é mostrada no código abaixo:
// Basic header file for using GLUT
#include <GL/glut.h>
// Basic macros for creating a graphical window
#define WINDOW_X_POS 50
#define WINDOW_Y_POS 50
#define WIDTH 700
#define HEIGHT 700
// Callback functions registered with GLUT
void onDisplay(void);
void onUpdate(void);
void onKeyboard(unsigned char key, int x, int y);
int main(int argc, char* argv[]) {
// Initialize GLUT and process all command-line arguments
glutInit(&argc, argv);
// This function specifies whether to use the RGBA mode or the color index mode for display. It can also specify whether to use a single-buffered or double-buffered window. Here, we use the RGBA and double-buffered window.
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
// Set the position of the top-left corner of the window when created on the screen
glutInitWindowPosition(WINDOW_X_POS, WINDOW_Y_POS);
// Set the width and height of the window when created, for simplicity
glutInitWindowSize(WIDTH, HEIGHT);
// Create a window, and the input string is the title of the window
glutCreateWindow("SolarSystem at LabEx");
// The prototype of glutDisplayFunc is glutDisplayFunc(void (*func)(void))
// This is a callback function, and it will be executed whenever GLUT determines that the contents of a window need to be updated and displayed.
//
// glutIdleFunc(void (*func)(void)) specifies a function to be executed when the event loop is idle. This callback function takes a function pointer as its only parameter.
//
// glutKeyboardFunc(void (*func)(unsigned char key, int x, int y)) associates a key on the keyboard with a function. This function is called when the key is pressed or released.
//
// Therefore, the three lines below are actually registering the three key callback functions with GLUT
glutDisplayFunc(onDisplay);
glutIdleFunc(onUpdate);
glutKeyboardFunc(onKeyboard);
glutMainLoop();
return 0;
}
Crie um arquivo main.cpp no diretório ~/project/ e escreva o seguinte 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;
}
Dicas
- O buffer único envolve a execução de todos os comandos de desenho diretamente na janela, o que é lento. Se o poder de processamento do computador não for suficiente e o buffer único for usado, a tela pode piscar.
- O buffer duplo envolve a execução dos comandos de desenho em um buffer na memória, o que é muito mais rápido. Após a conclusão dos comandos de desenho, os resultados são copiados para a tela por meio de uma troca de buffer. A vantagem dessa abordagem é que, se deixarmos as operações de desenho serem executadas em tempo real com a placa gráfica, quando as tarefas de desenho forem complexas, as operações de E/S se tornarão complexas, resultando em menor desempenho. Em contraste, com o buffer duplo, os resultados de desenho concluídos são enviados diretamente para a placa gráfica para renderização quando o buffer é trocado, o que reduz significativamente a E/S.
Em OpenGL, é recomendado usar GLfloat para representar números de ponto flutuante.
Design da Classe
Na programação orientada a objetos (POO), é essencial esclarecer com quais objetos estamos lidando primeiro. Obviamente, em todo o sistema celeste, todos são planetas (Estrelas), e a distinção entre planetas e estrelas só requer se eles têm um nó pai. Em segundo lugar, para diferentes planetas, eles geralmente têm seus próprios materiais, e diferentes materiais demonstrarão se eles emitem luz. Portanto, temos um modelo de objeto preliminar. Portanto, dividimos os planetas em: planetas comuns que podem girar e orbitar em torno de um ponto (Estrela), planetas com materiais especiais (Planeta) e planetas que podem emitir luz (PlanetaLuz).
Além disso, para a conveniência da implementação da programação, precisamos fazer algumas suposições sobre o modelo de programação real no mundo real:
- A órbita do planeta é circular;
- A velocidade de rotação permanece a mesma;
- Cada vez que a tela é atualizada, presume-se que um dia se passou.
Primeiramente, podemos considerar as seguintes etapas para implementar a lógica:
- Inicializar os objetos planetários;
- Inicializar o mecanismo OpenGL, implementar onDraw e onUpdate;
- Cada planeta deve ser responsável por lidar com suas próprias propriedades, relação de revolução e desenho relacionado à transformação. Portanto, ao projetar a classe planeta, um método de desenho draw() deve ser fornecido;
- O planeta também deve lidar com suas próprias atualizações relacionadas à rotação e revolução para exibição. Portanto, ao projetar a classe planeta, um método de atualização update() também deve ser fornecido;
- Chamar o método draw() do planeta em onDraw();
- Chamar o método update() do planeta em onUpdate();
- Ajustar a exibição de todo o sistema solar com o teclado em onKeyboard().
Além disso, para cada planeta, eles têm os seguintes atributos:
- Cor color
- Raio de revolução radius
- Velocidade de rotação selfSpeed
- Velocidade de revolução speed
- Distância ao centro do sol distance
- Planeta pai em revolução parentStar
- Ângulo de rotação atual alphaSelf
- Ângulo de revolução atual alpha
No diretório ~/project/, crie um arquivo stars.hpp. Com base na análise acima, podemos projetar o seguinte código de classe:
class Star {
public:
// Planet's revolution radius
GLfloat radius;
// Planet's revolution and rotation speed
GLfloat speed, selfSpeed;
// Distance from the center of the planet to the parent planet's center
GLfloat distance;
// Planet's color
GLfloat rgbaColor[4];
// Parent planet
Star* parentStar;
// Constructor, when constructing a planet, the rotation radius, rotation speed, rotation speed, and revolving (parent planet) should be provided
Star(GLfloat radius, GLfloat distance,
GLfloat speed, GLfloat selfSpeed,
Star* parentStar);
// Draw common planet's movement, rotation, and other activities
void drawStar();
// Provide default implementation to call drawStar()
virtual void draw() { drawStar(); }
// The parameter is the time span for each screen refresh
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]);
// Add materials to planets with their own materials
void drawPlanet();
// Continue to open the rewrite function to its subclasses
virtual void draw() { drawPlanet(); drawStar(); }
};
class LightPlanet : public Planet {
public:
LightPlanet(GLfloat Radius, GLfloat Distance,
GLfloat Speed, GLfloat SelfSpeed,
Star* ParentStar, GLfloat rgbColor[]);
// Add lighting to stars that provide light sources
void drawLight();
virtual void draw() { drawLight(); drawPlanet(); drawStar(); }
};
Além disso, também precisamos considerar o projeto da classe SolarSystem. No sistema solar, é óbvio que o sistema solar consiste em vários planetas. Para o sistema solar, a atualização da visualização após o movimento do planeta deve ser tratada pelo sistema solar. Portanto, as variáveis membro de SolarSystem devem incluir variáveis contendo os planetas, e as funções membro devem lidar com a atualização da visualização e os eventos de resposta do teclado dentro do sistema solar. Portanto, no diretório ~/project/, crie um arquivo solarsystem.hpp e, nele, podemos projetar a classe SolarSystem:
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;
};
Dicas
- Aqui, usamos a forma tradicional de array para gerenciar todos os planetas em vez de usar o vector em C++, porque a forma tradicional de array é suficiente.
- Definir o ângulo de visão em OpenGL é um conceito complexo que requer um certo tempo para explicar. Mencionaremos brevemente que definir o ângulo de visão requer pelo menos nove parâmetros aqui. Explicaremos suas funções em detalhes quando os implementarmos mais tarde na próxima seção.
Finalmente, também precisamos considerar as configurações básicas de parâmetros e variáveis.
Em SolarSystem, incluindo o sol, há um total de nove planetas (excluindo Plutão), mas na classe Star que projetamos, cada objeto Star tem os atributos de uma Star, então podemos implementar adicionalmente os satélites desses planetas, como a lua orbitando a Terra. Portanto, consideramos implementar um total de dez planetas. Portanto, podemos definir o seguinte enum para indexar os planetas em um array:
#define STARS_NUM 10
enum STARS {
Sun, // Sun
Mercury, // Mercury
Venus, // Venus
Earth, // Earth
Moon, // Moon
Mars, // Mars
Jupiter, // Jupiter
Saturn, // Saturn
Uranus, // Uranus
Neptune // Neptune
};
Star * stars[STARS_NUM];
Também presumimos que as velocidades de rotação são as mesmas, então definimos sua velocidade usando uma macro:
#define TIMEPAST 1
#define SELFROTATE 3
Neste ponto, mova as funções membro não implementadas para o arquivo .cpp correspondente e concluímos o experimento nesta seção.
Resumo do Código
Vamos resumir o código que precisa ser concluído nos experimentos acima:
Primeiro, em main.cpp, criamos um objeto SolarSystem e, em seguida, delegamos a atualização da exibição, a atualização ociosa e o tratamento de eventos do teclado para glut:
Clique para ver o 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;
}
Em segundo lugar, em stars.hpp, criamos as classes Star, Planet e LightPlanet:
Clique para ver o 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 */
No diretório ~/project/, crie um arquivo stars.cpp e preencha as implementações das funções membro correspondentes de stars.hpp:
Clique para ver o 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:
}
Em solarsystem.hpp, a classe SolarSystem é projetada:
//
// 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;
};
No diretório ~/project/, crie um arquivo solarsystem.cpp e implemente as funções membro correspondentes de solarsystem.hpp:
Clique para ver o 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:
}
No diretório ~/project/, crie um arquivo Makefile e adicione o seguinte código a ele:
Por favor, insira-o manualmente, não copie e cole diretamente, e lembre-se de usar a tecla <tab> em vez da tecla de espaço.
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
Ao escrever o comando de compilação, preste atenção à colocação de -lglut -lGLU -lGL. Isso ocorre porque o uso da opção -l no compilador g++ é ligeiramente especial.
Por exemplo: foo1.cpp -lz foo2.cpp, se o arquivo de destino foo2.cpp usa funções da biblioteca z, essas funções não serão carregadas diretamente. No entanto, se foo1.o usa funções da biblioteca z, nenhum erro de compilação ocorrerá.
Em outras palavras, todo o processo de vinculação vai da esquerda para a direita. Quando um símbolo de função não resolvido é encontrado em foo1.cpp, ele procura a biblioteca de link do lado direito. Quando encontra a opção z, ele pesquisa em z e encontra a função, completando assim o link sem problemas. Portanto, a biblioteca com a opção -l deve ser colocada à direita de todos os arquivos compilados.
Finalmente, execute no terminal:
make && ./solarsystem
Você pode ver que a janela foi criada, mas não há nada nela (ela exibe o que está atrás da janela). Isso ocorre porque ainda não implementamos o mecanismo de atualização para os gráficos na janela. Continuaremos a concluir o código restante no próximo experimento para fazer com que toda a simulação do sistema solar seja executada.
O Conceito de Matrizes em OpenGL
Em álgebra linear, estamos familiarizados com o conceito de matrizes, mas nossa compreensão de seu propósito e função específicos pode ser limitada. Então, o que exatamente é uma matriz?
Vamos começar analisando a seguinte equação:
x = Ab
Onde A é uma matriz, e x, b são vetores.
Perspectiva Um:
Se x e b são ambos vetores em nosso espaço tridimensional, o que A faz? Ele transforma b em (ou para) x. Desta perspectiva, uma matriz A pode ser entendida como uma transformação.
Agora, vamos considerar outra equação:
Ax = By
Onde A, B são matrizes, e x, y são vetores.
Perspectiva Dois:
Para dois vetores diferentes x e y, eles são essencialmente os mesmos porque podem ser tornados iguais multiplicando-os com as matrizes A e B. Desta perspectiva, uma matriz A pode ser entendida como um sistema de coordenadas. Em outras palavras, os próprios vetores são únicos, mas definimos um sistema de coordenadas para descrevê-los. Como diferentes sistemas de coordenadas são usados, as coordenadas dos vetores variam. Nesse caso, para o mesmo vetor, ele tem coordenadas diferentes em diferentes sistemas de coordenadas.
A matriz A descreve precisamente um sistema de coordenadas, e a matriz B descreve outro sistema de coordenadas. Quando esses dois sistemas de coordenadas são aplicados a x e y, eles produzem o mesmo resultado, significando que x e y são essencialmente o mesmo vetor, mas com diferentes sistemas de coordenadas.
Combinando essas duas perspectivas, podemos concluir que a essência de uma matriz é descrever o movimento.
No contexto do OpenGL, existe uma matriz responsável pelas transformações de renderização, conhecida como o modo de matriz em OpenGL.
Como mencionado anteriormente, uma matriz pode descrever tanto a transformação de um objeto quanto o sistema de coordenadas em que ele existe. Portanto, ao lidar com diferentes operações, precisamos definir diferentes modos de matriz em OpenGL. Isso é feito usando a função glMatrixMode().
Esta função aceita três modos diferentes: GL_PROJECTION para operações de projeção, GL_MODELVIEW para operações de modelo-visualização e GL_TEXTURE para operações de textura.
GL_PROJECTION informa ao OpenGL que as operações de projeção serão executadas, projetando o objeto em um plano. Quando este modo está ativado, a matriz precisa ser definida como a matriz identidade usando glLoadIdentity(), seguido por operações como definir a perspectiva usando gluPerspective (explicaremos esta função em mais detalhes mais tarde, quando discutirmos o conceito de visualização em OpenGL).
GL_MODELVIEW informa ao OpenGL que as instruções subsequentes serão usadas para representar uma operação baseada em modelo, como definir o ponto de vista da câmera. Da mesma forma, após ativar este modo, precisamos definir o modo de matriz OpenGL como a matriz identidade.
GL_TEXTURE é usado para operações relacionadas à textura, nas quais não nos aprofundaremos neste momento.
Se você não está familiarizado com o conceito de matrizes, pode simplesmente entender
glMatrixMode()como declarações para o OpenGL sobre as próximas operações. Antes de renderizar ou rotacionar um objeto, devemos usarglPushMatrixpara salvar o ambiente de matriz atual; caso contrário, erros de desenho misteriosos podem ocorrer.
APIs Comuns de Desenho de Imagens em OpenGL
OpenGL fornece muitas APIs comumente usadas relacionadas ao desenho de gráficos. Aqui, apresentaremos brevemente algumas delas e as usaremos no código a seguir:
- glEnable(GLenum cap): Esta função é usada para ativar várias funcionalidades fornecidas pelo OpenGL. O parâmetro cap é uma macro interna ao OpenGL, que fornece efeitos como iluminação, névoa e dithering.
- glPushMatrix() e glPopMatrix(): Essas funções salvam a matriz atual no topo da pilha (salvando a matriz atual).
- glRotatef(alpha, x, y, z): Representa a rotação da forma atual no sentido anti-horário por alpha graus ao longo do eixo
(x, y, z). - glTranslatef(distance, x, y): Representa a translação da forma atual por distance ao longo da direção
(x, y). - glutSolidSphere(GLdouble radius, GLint slices, GLint stacks): Desenha uma esfera, onde radius é o raio, slices é o número de linhas longitudinais e stacks é o número de linhas latitudinais.
- glBegin() e glEnd(): Quando queremos desenhar uma forma, precisamos chamar essas duas funções antes e depois do desenho.
glBegin()especifica o tipo da forma a ser desenhada. Por exemplo,GL_POINTSrepresenta o desenho de pontos,GL_LINESrepresenta o desenho de pontos conectados por linhas,GL_TRIANGLEScompleta um triângulo com cada três pontos eGL_POLYGONdesenha um polígono do primeiro ponto ao n-ésimo ponto, e assim por diante. Por exemplo, quando precisamos desenhar um círculo, podemos usar um polígono com muitos lados para simulá-lo:
// r is the radius, n is the number of sides
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 em OpenGL
Na seção anterior, definimos nove variáveis membro na classe SolarSystem em OpenGL:
GLdouble viewX, viewY, viewZ;
GLdouble centerX, centerY, centerZ;
GLdouble upX, upY, upZ;
Para entender essas nove variáveis, primeiro precisamos estabelecer o conceito de perspectiva da câmera na programação 3D com OpenGL.
Imagine que as cenas que geralmente assistimos em filmes são, na verdade, filmadas da perspectiva da câmera. Portanto, OpenGL também tem um conceito semelhante. Se imaginarmos a câmera como nossa própria cabeça, então:
viewX,viewY,viewZcorrespondem às coordenadas da cabeça (câmera) no sistema de coordenadas do mundo OpenGL;centerX,centerY,centerZcorrespondem às coordenadas do objeto sendo visualizado (visto pela câmera);upX,upY,upZcorrespondem ao vetor de direção apontando para cima a partir do topo da cabeça (topo da câmera) (como podemos inclinar a cabeça para observar um objeto).
Com essa compreensão, você agora tem um conceito do sistema de coordenadas em OpenGL.
Para este experimento, vamos supor que a perspectiva inicial está nas coordenadas (x, -x, x). Portanto, temos:
#define REST 700
#define REST_Y (-REST)
#define REST_Z (REST)
A posição do objeto observado (sol) está em (0,0,0), então no construtor da classe SolarSystem, inicializamos a perspectiva da seguinte forma:
viewX = 0;
viewY = REST_Y;
viewZ = REST_Z;
centerX = centerY = centerZ = 0;
upX = upY = 0;
upZ = 1;
Então, podemos definir os nove parâmetros da perspectiva usando a função gluLookAt:
gluLookAt(viewX, viewY, viewZ, centerX, centerY, centerZ, upX, upY, upZ);
Em seguida, vamos dar uma olhada em gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear,GLdouble zFar).
Esta função criará um volume de visualização de perspectiva simétrico, e antes de usar esta função, o modo de matriz do OpenGL precisa ser definido como GL_PROJECTION.
Como mostrado na figura a seguir:

A imagem na janela é capturada pela câmera, e o conteúdo real sendo capturado está no plano distante, enquanto o conteúdo exibido está no plano próximo. Portanto, esta função requer quatro parâmetros:
- O primeiro parâmetro é o tamanho do ângulo de perspectiva.
- O segundo parâmetro é a proporção da janela real, como mostrado na figura
aspect=w/h. - O terceiro parâmetro é a distância para o plano próximo.
- O quarto parâmetro é a distância para o plano distante.
Efeitos de Iluminação em OpenGL
OpenGL divide o sistema de iluminação em três partes: fontes de luz, materiais e ambiente de iluminação.
Como o nome sugere, uma fonte de luz é a origem da luz, como o sol; Materiais referem-se às superfícies de vários objetos que recebem luz, como planetas e satélites no sistema solar, exceto o sol; O ambiente de iluminação inclui parâmetros adicionais que determinam o efeito de iluminação final, como a reflexão dos raios de luz, que pode ser controlada definindo um parâmetro chamado "brilho ambiente" para tornar a imagem final mais próxima da realidade.
Na física, quando a luz paralela atinge uma superfície lisa, a luz refletida permanece paralela. Esse tipo de reflexão é chamado de "reflexão especular". Por outro lado, a reflexão causada por uma superfície irregular é chamada de "reflexão difusa".

Fontes de Luz
Para implementar o sistema de iluminação em OpenGL, a primeira coisa que precisamos fazer é configurar as fontes de luz. Vale a pena mencionar que OpenGL suporta um número limitado de fontes de luz (oito no total), representadas pelas macros GL_LIGHT0 a GL_LIGHT7. Elas podem ser habilitadas com a função glEnable e desabilitadas com a função glDisable. Por exemplo: glEnable(GL_LIGHT0);
A posição da fonte de luz é definida usando a função glLightfv, por exemplo:
GLfloat light_position[] = {0.0f, 0.0f, 0.0f, 1.0f};
glLightfv(GL_LIGHT0, GL_POSITION, light_position); // Especifica a posição da fonte de luz 0
A posição é representada por quatro valores, (x, y, z, w). Quando w é 0, significa que a fonte de luz está infinitamente distante. Os valores de x, y e z especificam a direção dessa fonte de luz infinitamente distante. Quando w não é 0, representa uma fonte de luz posicional, e sua posição é (x/w, y/w, z/w).
Materiais
Para definir o material de um objeto, geralmente são necessários cinco atributos:
- A intensidade da luz restante no ambiente após múltiplas reflexões.
- A intensidade da luz após a reflexão difusa.
- A intensidade da luz após a reflexão especular.
- A intensidade da luz emitida por objetos não emissores de luz em OpenGL, que é fraca e não afeta outros objetos.
- O expoente especular, que representa a rugosidade do material. Um valor menor significa um material mais áspero, e quando a luz emitida por uma fonte pontual de luz incide sobre ele, são produzidos pontos brilhantes maiores. Por outro lado, um valor maior significa que o material é mais parecido com uma superfície especular, produzindo pontos brilhantes menores.
OpenGL fornece duas versões de funções para definir materiais:
void glMaterialf(GLenum face, GLenum pname, TYPE param);
void glMaterialfv(GLenum face, GLenum pname, TYPE *param);
A diferença entre elas é que apenas um valor precisa ser definido para o expoente especular, então glMaterialf é usado. Para outras configurações de material que exigem vários valores, uma matriz é usada, e a versão com parâmetros de vetor de ponteiro, glMaterialfv, é usada. Por exemplo:
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);
Ambiente de Iluminação
Por padrão, OpenGL não lida com iluminação. Para habilitar a funcionalidade de iluminação, você precisa usar a macro GL_LIGHTING, ou seja, glEnable(GL_LIGHTING);
Desenhando Planetas
Ao desenhar um planeta, primeiro precisamos considerar seu ângulo de revolução e ângulo de rotação. Portanto, podemos primeiro implementar a função membro Star::update(long timeSpan) da classe Star no arquivo ~/project/stars.cpp:
void Star::update(long timeSpan) {
alpha += timeSpan * speed; // update revolution angle
alphaSelf += selfSpeed; // update rotation angle
}
Após atualizar os ângulos de revolução e rotação, podemos desenhar o planeta específico com base nos parâmetros:
void Star::drawStar() {
glEnable(GL_LINE_SMOOTH);
glEnable(GL_BLEND);
int n = 1440;
// Save the current OpenGL matrix environment
glPushMatrix();
{
// Revolution
// If it is a planet and the distance is not 0, then translate it to the origin by a radius
// This part is used for satellites
if (parentStar != 0 && parentStar->distance > 0) {
// Rotate the drawing graphics around the z-axis by alpha
glRotatef(parentStar->alpha, 0, 0, 1);
// Translate in the x-axis direction by distance, while y and z directions remain unchanged
glTranslatef(parentStar->distance, 0.0, 0.0);
}
// Draw the orbit
glBegin(GL_LINES);
for(int i=0; i<n; ++i)
glVertex2f(distance * cos(2 * PI * i / n),
distance * sin(2 * PI * i / n));
glEnd();
// Rotate around the z-axis by alpha
glRotatef(alpha, 0, 0, 1);
// Translate in the x-axis direction by distance, while y and z directions remain unchanged
glTranslatef(distance, 0.0, 0.0);
// Rotation
glRotatef(alphaSelf, 0, 0, 1);
// Draw the planet color
glColor3f(rgbaColor[0], rgbaColor[1], rgbaColor[2]);
glutSolidSphere(radius, 40, 32);
}
// Restore the matrix environment before drawing
glPopMatrix();
}
Este código usa as funções sin() e cos(), que exigem a inclusão de #include<cmath>.
Desenho da Iluminação
Para a classe Planet, que representa um corpo celeste não luminoso, precisamos desenhar seu efeito de iluminação. Adicione o seguinte código ao arquivo ~/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);
}
Quanto à classe LightPlanet, que representa um corpo celeste luminoso, não apenas precisamos definir seu material de iluminação, mas também precisamos definir a posição de sua fonte 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 a posição da fonte de luz 0
glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); // Representa a intensidade dos raios de luz de várias fontes que atingem o material, após múltiplas reflexões e rastreamento
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); // Intensidade da luz após a reflexão difusa
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); // Intensidade da luz após a reflexão especular
}
Desenhando a Janela
Na seção anterior, mencionamos as duas funções mais importantes para lidar com a exibição de imagens: glutDisplayFunc e glutIdleFunc. glutDisplayFunc executa a função de retorno de chamada quando GLUT determina que o conteúdo da janela precisa ser atualizado, enquanto glutIdleFunc lida com o retorno de chamada quando o loop de eventos está ocioso.
Para fazer todo o sistema solar se mover, precisamos considerar quando atualizar as posições dos planetas e quando atualizar a visualização.
Obviamente, glutDisplayFunc deve se concentrar na atualização da visualização, e quando o evento estiver ocioso, podemos começar a atualizar as posições dos planetas. Depois que as posições são atualizadas, podemos então chamar a função de atualização da visualização para atualizar a exibição.
Portanto, podemos primeiro implementar a função membro SolarSystem::onUpdate() chamada em glutDisplayFunc:
#define TIMEPAST 1 // Assume that each update takes one day
void SolarSystem::onUpdate() {
for (int i=0; i<STARS_NUM; i++)
stars[i]->update(TIMEPAST); // Update the positions of the stars
this->onDisplay(); // Refresh the display
}
Em seguida, a atualização da visualização da exibição é implementada em SolarSystem::onDisplay():
void SolarSystem::onDisplay() {
// Clear the viewport buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Clear and set the color buffer
glClearColor(.7f, .7f, .7f, .1f);
// Specify the current matrix as the projection matrix
glMatrixMode(GL_PROJECTION);
// Specify the specified matrix as an identity matrix
glLoadIdentity();
// Specify the current view volume
gluPerspective(75.0f, 1.0f, 1.0f, 40000000);
// Specify the current matrix as the model view matrix
glMatrixMode(GL_MODELVIEW);
// Specify the current matrix as an identity matrix
glLoadIdentity();
// Define the view matrix and multiply it with the current matrix
gluLookAt(viewX, viewY, viewZ, centerX, centerY, centerZ, upX, upY, upZ);
// Set the first light source (light source 0)
glEnable(GL_LIGHT0);
// Enable lighting
glEnable(GL_LIGHTING);
// Enable depth testing, automatically hide covered graphics based on the coordinates
glEnable(GL_DEPTH_TEST);
// Draw the planets
for (int i=0; i<STARS_NUM; i++)
stars[i]->draw();
// We used GLUT_DOUBLE when initializing the display mode in the main function
// We need to use glutSwapBuffers to implement buffer swapping for double buffering after drawing is finished
glutSwapBuffers();
}
Construtores e Destrutores de Classe
Os construtores das classes definidas em stars.hpp precisam inicializar as variáveis membro das classes. Esta parte é relativamente simples e até mesmo destrutores padrão podem ser usados, então implemente esses construtores por conta própria:
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]);
Dica: Observe que, ao inicializar a variável de velocidade, converta-a para velocidade angular. A fórmula de conversão é: alpha_speed = 360/speed.
Para o construtor de solarsystem.cpp, precisamos inicializar todos os planetas. Aqui fornecemos os parâmetros entre os planetas para conveniência:
// Revolution radius
#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
// Distance from the sun
#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
// Movement speed
#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
// Self-rotation speed
#define SELFROTATE 3
// Define a macro to facilitate the setting of a multi-dimensional array
#define SET_VALUE_3(name, value0, value1, value2) \
((name)[0])=(value0), ((name)[1])=(value1), ((name)[2])=(value2)
// In the previous experiment, we defined the enum of the planets
enum STARS {Sun, Mercury, Venus, Earth, Moon,
Mars, Jupiter, Saturn, Uranus, Neptune};
Dica:
Definimos uma macro SET_VALUE_3 aqui. Você pode pensar que podemos escrever uma função para atingir o mesmo propósito.
Na verdade, as macros completam o trabalho geral de substituição no processo de compilação, enquanto definir funções
Isso requer operações de pilha de funções durante a chamada, o que é muito menos eficiente do que o processamento de macro durante o processo de compilação.
Portanto, as macros podem ser mais eficientes.
No entanto, vale a pena notar que, embora as macros possam ser mais eficientes, o uso excessivo pode levar a um código feio e menos legível. O uso apropriado de macros, por outro lado, é incentivado.
Portanto, podemos implementar o construtor da classe SolarSystem, onde as cores dos planetas são selecionadas aleatoriamente. Os leitores podem alterar as cores dos planetas por conta própria:
SolarSystem::SolarSystem() {
// Define the perspective view, as we have discussed the initialization of the perspective view before
viewX = 0;
viewY = REST_Y;
viewZ = REST_Z;
centerX = centerY = centerZ = 0;
upX = upY = 0;
upZ = 1;
// Sun
GLfloat rgbColor[3] = {1, 0, 0};
stars[Sun] = new LightPlanet(SUN_RADIUS, 0, 0, SELFROTATE, 0, rgbColor);
// Mercury
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);
// Earth
SET_VALUE_3(rgbColor, 0, 1, 0);
stars[Earth] = new Planet(EAR_RADIUS, EAR_DIS, EAR_SPEED, SELFROTATE, stars[Sun], rgbColor);
// Moon
SET_VALUE_3(rgbColor, 1, 1, 0);
stars[Moon] = new Planet(MOO_RADIUS, MOO_DIS, MOO_SPEED, SELFROTATE, stars[Earth], rgbColor);
// Mars
SET_VALUE_3(rgbColor, 1, .5, .5);
stars[Mars] = new Planet(MAR_RADIUS, MAR_DIS, MAR_SPEED, SELFROTATE, stars[Sun], rgbColor);
// Jupiter
SET_VALUE_3(rgbColor, 1, 1, .5);
stars[Jupiter] = new Planet(JUP_RADIUS, JUP_DIS, JUP_SPEED, SELFROTATE, stars[Sun], rgbColor);
// Saturn
SET_VALUE_3(rgbColor, .5, 1, .5);
stars[Saturn] = new Planet(SAT_RADIUS, SAT_DIS, SAT_SPEED, SELFROTATE, stars[Sun], rgbColor);
// Uranus
SET_VALUE_3(rgbColor, .4, .4, .4);
stars[Uranus] = new Planet(URA_RADIUS, URA_DIS, URA_SPEED, SELFROTATE, stars[Sun], rgbColor);
// Neptune
SET_VALUE_3(rgbColor, .5, .5, 1);
stars[Neptune] = new Planet(NEP_RADIUS, NEP_DIS, NEP_SPEED, SELFROTATE, stars[Sun], rgbColor);
}
Além disso, não se esqueça de liberar a memória alocada no destrutor:
SolarSystem::~SolarSystem() {
for(int i = 0; i<STARS_NUM; i++)
delete stars[i];
}
Implementação da Mudança de Perspectiva com Teclas do Teclado
Para controlar a mudança de perspectiva, podemos usar as cinco teclas w, a, s, d, x no teclado e usar a tecla r para redefinir a perspectiva. Primeiro, precisamos determinar a magnitude da mudança de perspectiva após cada pressionamento de tecla. Aqui, definimos uma macro OFFSET. Em seguida, podemos determinar o comportamento de pressionamento de tecla do usuário inspecionando o parâmetro key passado.
#define OFFSET 20
void SolarSystem::onKeyboard(unsigned char key, int x, int y) {
switch (key) {
case 'w': viewY += OFFSET; break; // Increase the camera's Y-axis position by 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;
}
}
Executar e Testar
Implementamos principalmente o código nos arquivos stars.cpp e solarsystem.cpp.
O código para stars.cpp é o seguinte:
Clique para ver o 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);
}
O código para solarsystem.cpp é o seguinte:
Clique para ver o 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];
}
Execute no terminal:
make && ./solarsystem
O resultado é mostrado na figura:

Como as cores dos planetas são únicas, o efeito de iluminação não é muito óbvio, mas ainda é visível. Por exemplo, no lado direito de Júpiter, que é amarelo, há uma aparência esbranquiçada.
Resumo
Neste projeto, alcançamos um modelo simples do sistema solar. Ao usar o sistema de iluminação em OpenGL, somos capazes de ver os efeitos de iluminação dos planetas enquanto eles giram em torno do sol. Além disso, podemos ajustar a perspectiva usando o teclado para observar o sistema solar de diferentes ângulos.



