hellOGL: OpenGL hola mundo

Hoy mostraré cómo abrir una ventana y crear un contexto OpenGL. Esta es una tarea sorprendentemente difícil, OpenGL todavía no tiene herramientas oficiales multiplataforma para crear contexto, por lo que confiaremos en bibliotecas de terceros (en este caso GLFW y contento). Ya hay muchos hola mundos similares en Internet, pero no me gusta todo lo que vi: o es muy sofisticado o las imágenes de los ejemplos son muy primitivas (¡ o ambas !) Muchas gracias a todos los autores, pero descargaré otro tutorial :)

Hoy dibujaremos esto:



Este modelo fue dibujado por el artista Samuel Sharit (arshlevon) , ¡muchas gracias por permitirme usarlo como parte de mi curso de conferencias sobre gráficos por computadora!

Etapa 0: lectura de pequeños compradores


En términos generales, es mejor (aunque no necesario) dar esta conferencia después de leer todo mi curso para pequeños compradores . Para aquellos que no hablan inglés, este curso de conferencias está disponible en el centro , aunque ya no soporto la versión rusa. Como parte de este curso de conferencias, mostré cómo puedes dibujar solo esta imagen en solo quinientas líneas de código, e incluso con una prohibición completa de bibliotecas de terceros:



Sorprendentemente, muchos de mis estudiantes no entienden que este rasterizador de software no es solo un juguete, sino una introducción real a cómo funciona OpenGL. Por lo tanto, hoy mostraré cómo representar la diabetes con aceleración de hardware, y utilizaré el código del repositorio de rasterizadores de software en muchos aspectos.

Atención, no me propongo explicar cada línea de código, ya que confío en que leer la documentación es la mejor manera de entenderlo todo. Mi código es necesario solo para saber exactamente qué leer en la documentación y en qué orden. Además, no explicaré qué son los sombreadores y no explicaré cómo leer mapas normales . Pasé mucho tiempo en tinyrenderer, donde todo está resuelto.

Etapa uno, la más difícil: crear una ventana


Todo el repositorio vive aquí ; creó una confirmación para cada paso del tutorial, ya que el github ofrece un visor muy conveniente de todos los cambios realizados. Comenzamos aquí , nuestro objetivo es obtener esta ventana:



El código se compila usando CMake; Lo comprobé en Linux (g ++) y Windows (Visual Studio 2017). En Linux, la última versión del código se compila así:

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

Use `git checkout` si desea compilar una confirmación por separado, no la última versión. Este código carga contento y GLFW, crea una ventana con la devolución de llamada de teclado necesaria y carga vértices vacíos y sombreadores de píxeles desde el disco.

Etapa dos: cargar un modelo 3D


Cambios en el proyecto realizado en esta etapa, ver aquí . En esta etapa, nuestro objetivo es analizar el archivo del modelo 3D y dibujar los primeros triángulos sin preocuparse por la iluminación en este momento:



Tenga en cuenta que tanto el modelo en sí como la biblioteca para trabajar con vectores, y tomé el analizador de modelos completamente de tinyrenderer. ¿Quizás el procesador de software no es tan inútil?

La idea básica en OpenGL moderno es muy simple. Primero cargamos un modelo 3D y luego creo una matriz de vértices de tamaño 3 * 3 * (el número de triángulos). Cada triángulo tiene tres vértices, ¿verdad? Cada vértice se describe con tres números (x, y, z). En total, 3 * 3 * model.nfaces () es suficiente para que describamos el modelo completo:

  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]; } } 

Y luego le decimos a OpenGL que aquí hay una matriz, dibujo, ¡nativo!

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

El sombreador de vértices no hace nada interesante, simplemente pasa los datos al sombreador de fragmentos tal como está:

 #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 } 

Bueno, el sombreador de fragmentos tampoco es pretencioso. Simplemente dibuja el encurtido actual en rojo:

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

¡Lo más difícil está hecho, ahora es cuestión de tecnología!

Etapa tres: iluminación difusa


Cambios en el proyecto realizado en esta etapa, ver aquí . Deberíamos obtener esta imagen:



La iluminación difusa en el modelo Phong, como saben, es un producto escalar simple entre
vector normal y vector de iluminación. Por lo tanto, además de la matriz de vértices, agregué otra matriz de normales. Sin mirar el código, dime de qué tamaño es?

Lo más interesante sucede en el fragment shader, en el archivo principal .cpp, solo se cargan los datos:

 #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 } 

Etapa cuatro: matrices de transformación


Cambios en el proyecto realizado en esta etapa, ver aquí . En este punto, codifiqué las matrices Modelo, Vista y Proyección. Al principio, son simples, pero si presiona la barra espaciadora, el modelo comenzará a rotar: cada vez que dibuje una imagen, rotaré la matriz del Modelo alrededor del eje z en 0.01 radianes:

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

Aquí la función rot_z () devuelve la matriz de rotación alrededor del eje z en un ángulo dado. Como OpenGL no sabe nada sobre mi clase de matriz, tuve que agregar la matriz export void export_row_major () a un puntero simple a un flotante.



Paso cinco: mapas normales


Cambios en el proyecto realizado en esta etapa, ver aquí . En este punto, aprenderemos cómo superponer texturas. Como la textura difusa habitual es aburrida, aplicaré inmediatamente el mapa normal, e incluso en el espacio tangente. Los mapas normales se parecen a esto:



Los cálculos correspondientes, por decirlo suavemente, no son obvios, así que de nuevo, lea las explicaciones en tinyrenderer . En términos de datos, debe agregar varios búferes: coordenadas uv y matrices de vectores tangentes y bi-tangentes.



Etapa cinco: textura difusa


Bueno, si ya sabemos cómo contar mapas normales, entonces aplicar una textura difusa normal es simplemente trivial. Cambios en el proyecto realizado en esta etapa, ver aquí .



Etapa seis: resplandor


Cambios en el proyecto realizado en esta etapa, ver aquí . La etapa final, agrega otra textura que nos permitirá simular el resplandor de la iluminación desde superficies brillantes:



Conclusión


Hay mucho que se puede mejorar en este código, y los efectos visuales se pueden torcer sin fin. Pero este no es mi objetivo, mi objetivo es mostrar que absolutamente todas las técnicas que mencioné en la representación de software son aplicables en el contexto actual de OpenGL. Y personalmente, sigo pensando que debes comenzar a familiarizarte con los gráficos 3D dibujando imágenes sin usar la magia de las bibliotecas gráficas.

Como extensión, intente, por ejemplo, agregar sombras , contar la iluminación global o, finalmente, hacer un mapa de brillo: después de todo, ¡los ojos y el cristal en la frente de Diablo deberían brillar!

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


All Articles