hellOGL: Olá mundo OpenGL

Hoje vou mostrar como abrir uma janela e criar um contexto OpenGL. Essa é uma tarefa surpreendentemente difícil, pois o OpenGL ainda não possui ferramentas oficiais de plataforma cruzada para a criação de contexto; portanto, contaremos com bibliotecas de terceiros (neste caso, GLFW e feliz). Já existem muitos mundos olá semelhantes na Internet, mas não gosto de tudo o que vi: ou é muito sofisticado ou as imagens nos exemplos são muito primitivas ( ou ambas !). Muito obrigado a todos os autores, mas vou baixar outro tutorial :)

Hoje vamos desenhar isso:



Este modelo foi desenhado pelo artista Samuel Sharit (arshlevon) , muito obrigado por me permitir usá-lo como parte do meu curso de computação gráfica!

Etapa 0: leitura do tinyrenderer


De um modo geral, é melhor (embora não seja necessário) dar essa palestra depois de ler todo o meu curso de renderizador . Para quem não fala inglês, este curso de palestras está disponível no hub , embora eu não seja mais compatível com a versão em russo. Como parte deste curso de palestras, mostrei como você pode desenhar apenas essa figura em apenas quinhentas linhas de código e mesmo com a proibição completa de bibliotecas de terceiros:



Surpreendentemente, muitos de meus alunos não entendem que esse rasterizador de software não é apenas um brinquedo, mas uma introdução real de como o OpenGL funciona. Portanto, hoje mostrarei como processar diabetes com aceleração de hardware e, em muitos aspectos, usarei o código do repositório do rasterizador de software.

Atenção, não me proponho a tarefa de explicar todas as linhas de código, pois confio no fato de que ler a documentação é a melhor maneira de entender tudo. Meu código é necessário apenas para saber exatamente o que a documentação deve ler e em que ordem. Além disso, não explicarei o que são shaders e não explicarei como ler mapas normais . Passei muito tempo no tinyrenderer, onde está tudo resolvido.

Etapa um, a mais difícil: criar uma janela


O repositório inteiro mora aqui ; criou um commit para cada etapa do tutorial, já que o github oferece um visualizador muito conveniente de todas as alterações feitas. Começamos aqui , nosso objetivo é obter esta janela:



O código é compilado usando o CMake; Eu verifiquei no Linux (g ++) e Windows (Visual Studio 2017). No Linux, a versão mais recente do código é compilada assim:

git clone --recurse-submodules https://github.com/ssloy/hellOGL.git cd hellOGL mkdir build cd build cmake .. make 

Use `git checkout` se desejar compilar um commit separado, não a versão mais recente. Esse código carrega glad e GLFW, cria uma janela com o retorno de chamada necessário do teclado e carrega shaders de vértice e pixel vazios do disco.

Etapa 2: carregando um modelo 3D


Mudanças no projeto feito nesta fase, veja aqui . Nesse estágio, nosso objetivo é analisar o arquivo do modelo 3D e desenhar os primeiros triângulos sem se preocupar com a iluminação no momento:



Observe que, tanto o modelo em si quanto a biblioteca para trabalhar com vetores, peguei o analisador de modelo inteiramente no tinyrenderer. Talvez o renderizador de software não seja tão inútil?

A idéia básica no OpenGL moderno é muito simples. Primeiro, carregamos um modelo 3D e, em seguida, crio uma matriz de vértices de tamanho 3 * 3 * (o número de triângulos). Cada triângulo tem três vértices, certo? Cada vértice é descrito por três números (x, y, z). No total, 3 * 3 * model.nfaces () é suficiente para descrevermos o modelo inteiro:

  std::vector<GLfloat> vertices(3*3*model.nfaces(), 0); for (int i=0; i<model.nfaces(); i++) { for (int j=0; j<3; j++) { for (int k=0; k<3; k++) vertices[(i*3+j)*3 + k] = model.point(model.vert(i, j))[k]; } } 

E então dizemos ao OpenGL que aqui está um array, draw, nativo!

  while (!glfwWindowShouldClose(window)) { [...]  glDrawArrays(GL_TRIANGLES, 0, vertices.size()); [...] } 

O shader de vértice não faz nada de interessante, simplesmente passa os dados para o shader de fragmento como ele é:

 #version 330 core // Input vertex data, different for all executions of this shader layout(location = 0) in vec3 vertexPosition_modelspace; void main() { gl_Position = vec4(vertexPosition_modelspace, 1); // Output position of the vertex, in clip space } 

Bem, o shader de fragmento também é despretensioso. Simplesmente desenha o picles atual em vermelho:

 #version 330 core // Output data out vec3 color; void main() { color = vec3(1,0,0); } 

A coisa mais difícil é feita, agora é uma questão de tecnologia!

Estágio três: iluminação difusa


Mudanças no projeto feito nesta fase, veja aqui . Devemos obter esta imagem:



A iluminação difusa no modelo Phong, como você sabe, é um produto escalar simples entre
vetor normal e vetor de iluminação. Portanto, além da matriz de vértices, adicionei outra matriz de normais. Sem olhar para o código, me diga qual é o tamanho?

O mais interessante acontece no shader de fragmento, no arquivo .cpp principal, apenas os dados são carregados:

 #version 330 core // Output data out vec3 color; // Interpolated values from the vertex shaders in vec3 Normal_cameraspace; in vec3 LightDirection_cameraspace; void main() { vec3 n = normalize(Normal_cameraspace); // Normal of the computed fragment, in camera space vec3 l = normalize(LightDirection_cameraspace); // Direction of the light (from the fragment to the light) float cosTheta = clamp(dot(n,l), 0, 1); // Cosine of the angle between the normal and the light direction, color = vec3(1,0,0)*(0.1 + // ambient lighting 1.3*cosTheta); // diffuse lighting } 

Estágio Quatro: Matrizes de Transformação


Mudanças no projeto feito nesta fase, veja aqui . Nesse ponto, eu codifiquei as matrizes Modelo, Visualização e Projeção. No início, eles são únicos, mas se você pressionar a barra de espaço, o modelo começará a girar: cada vez que faço um desenho, giro a matriz do modelo em torno do eixo z em 0,01 radianos:

  { // rotate the model around the z axis with each frame Matrix R = rot_z(0.01); if (animate) M = R*M; } 

Aqui, a função rot_z () retorna a matriz de rotação em torno do eixo z por um determinado ângulo. Como o OpenGL não sabe nada sobre minha classe de matriz, tive que adicionar a exportação de matriz void export_row_major () a um ponteiro simples a um ponto flutuante.



Etapa 5: mapas normais


Mudanças no projeto feito nesta fase, veja aqui . Neste ponto, aprenderemos como sobrepor texturas. Como a textura difusa usual é chata, aplicarei imediatamente o mapa normal, e mesmo no espaço tangente. Os mapas normais são mais ou menos assim:



Os cálculos correspondentes, para dizer o mínimo, não são óbvios; leia novamente as explicações no tinyrenderer . Em termos de dados, você precisa adicionar vários buffers: coordenadas uv e matrizes de vetores tangentes e bi-tangentes.



Estágio Cinco: Textura Difusa


Bem, se já sabemos contar mapas normais, a aplicação de uma textura difusa normal é simplesmente trivial. Mudanças no projeto feito nesta fase, veja aqui .



Estágio Seis: Brilho


Mudanças no projeto feito nesta fase, veja aqui . Na etapa final, adicione outra textura que permita simular o brilho da iluminação de superfícies brilhantes:



Conclusão


Há muito que pode ser aprimorado nesse código e os efeitos visuais podem ser distorcidos infinitamente. Mas esse não é meu objetivo, meu objetivo é mostrar que absolutamente todas as técnicas que abordamos na renderização de software são aplicáveis ​​no contexto atual do OpenGL. E, pessoalmente, ainda acho que você precisa familiarizar-se com gráficos 3D desenhando sem usar a magia das bibliotecas gráficas.

Como uma extensão, tente, por exemplo, adicionar sombras ou contar a iluminação global ou, finalmente, fazer um mapa de brilho: afinal, os olhos e o cristal na testa de Diablo devem brilhar!

Source: https://habr.com/ru/post/pt443174/


All Articles