Conceptos básicos de entrada de OpenGL: conocimientos básicos

A través de los tutoriales anteriores, ya tenemos un entorno de desarrollo, pero antes de desarrollar el programa, primero entendemos los conceptos básicos de Opengl.

¿Qué es OpenGL?

Generalmente se dice en Internet que Opengl es una especificación y una interfaz, pero esta declaración es un poco abstracta. Echemos un vistazo primero al siguiente proceso simple de GL.

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
using namespace std;
int main()
{
    //glfw的初始化和设置
    // -----------------------------------------------------
    //调用glfwInit函数来初始化GLFW
    glfwInit();
    //配置GLFW,第一个参数代表选项的名称,第二个参数接受一个整型,用来设置这个选项的值
    //此处设置表示使用的OpenGL版本号3.3
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    //glfwWindowHint作为窗口创建的一种提示,可以设置窗口的多种属性,包括透明度等等,感兴趣的可以在glfw文档中查询
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    //使用流水线配置模式
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    //glfw窗口创建
    // -----------------------------------------------------
    //glfwCreateWindow函数需要窗口的宽和高作为它的前两个参数,第三个参数表示这个窗口的名称,最后两个参数我们暂时忽略,它返还一个GLFWwindow对象
    GLFWwindow* window = glfwCreateWindow(800, 800, "OpenGL", NULL, NULL);
    //创建完窗口,通知GLFW将我们窗口的上下文设置为当前线程的主上下文了
    glfwMakeContextCurrent(window);
    while (!glfwWindowShouldClose(window))
    {
        //OpenGL采用双缓冲来渲染窗口
        glfwSwapBuffers(window);
        //处理按键事件
        glfwPollEvents();
    }
    return 0;
}

Algunas personas en el código pueden estar confundidas acerca de parámetros como GLFW_OPENGL_PROFILE, o interesadas en más configuraciones de glfwWindowHint, puede obtener más información al respecto en la documentación oficial de glfw window

Ejecute el código, obtendrá un resultado como se muestra en la figura

En el proceso anterior, creamos un objeto ventana a través de glfwInit(), glfwWindowHint(), glfwCreateWindow(), etc., sin escribir una lógica específica, es decir, realizamos esta ventana llamando a una serie de objetos de interfaz, que es la interpretación de OpenGL como interfaz.

Entonces, ¿quién implementó la lógica específica? Primero debemos entender un trasfondo. La organización Khronos ha estado manteniendo la interfaz opengl. Si Khronos define una interfaz glDrawLine, entonces los fabricantes de dispositivos de GPU que admiten opengl permitirán que sus programadores usen la lógica de imagen para implementar esta interfaz en productos de hardware, y la corriente principal actual Todos los fabricantes de dispositivos GPU son compatibles con opengl.

Entonces, los fabricantes de equipos Khronos y GPU nos ayudan a darnos cuenta de la lógica específica, y lo que tenemos que hacer es seguir las especificaciones de esta serie de interfaces para obtener el contenido deseado.

proceso abierto

Después de comprender el concepto de opengl, es posible que tenga alguna comprensión de la relación entre GPU y opengl. A continuación, también podría echar un vistazo a la imagen a continuación.

tubería de renderizado opengl

En pocas palabras, el proceso de opengl primero llama a la interfaz de opengl e ingresa datos como atributos, datos de textura y uniformes en sombreadores como vetexshader Si se trata de datos de atributos, el sombreador de vértices los procesará y transferirá el flujo de resultados al siguiente ensamblaje primitivo Realice la rasterización en , y luego coopere con los datos de textura para procesar a través del sombreador de fragmentos, y finalmente renderice el resultado, puede consultar el diagrama de canalización de representación de opengl (el sombreador de geometría no se presenta en este artículo, porque no es necesario, quizás más adelante lo expliquemos en detalle).

Sustantivos comunes de OpenGL

Aquí mencionamos sombreado, rasterización y otras palabras, estos son sustantivos comunes de opengl, también podríamos entender su significado primero:


 

Atributos Un tipo de variable que se usa para recibir datos que cambian con frecuencia, como datos de color, datos de vértices, datos de textura, iluminación normal, etc.; los atributos solo se pueden pasar al sombreador de vértices, no directamente al sombreador de fragmentos, y es necesario pasar GLSL el código se pasa indirectamente al fragment shader.
sombreadores Un programa separado que se ejecuta en la GPU para procesar vértices y realizar tareas de rasterización.
GLSL

nombre completo, (

Lenguaje de sombreado openGL, lenguaje de sombreado OpenGL, una especificación de lenguaje, al igual que C++, pertenece a un lenguaje, el sombreador se ejecuta en la GPU para procesar datos como atributos, uniformes, luego el sombreador se escribe a través de GLSL.

uniformes Un tipo de variable, que se utiliza para recibir datos relativamente uniformes y que cambian con poca frecuencia, como matriz de rotación, datos YUV del espacio de color de video; los uniformes se pueden pasar tanto al sombreador de vértices como al sombreador de fragmentos.
Vértice Los vértices de OpenGL son 4 componentes (x, y, z, w). Cuando w es 0, representa un punto en el espacio, cuando w es 1, representa una dirección, y x, y, z son datos tridimensionales de coordenadas espaciales. .
elemento de textura Todo el elemento de textura, el texel se puede definir por el rango de la imagen, es la unidad básica en el espacio de textura de los gráficos por computadora. Así como las imágenes se componen de píxeles, las texturas se representan mediante téxeles.
Datos de textura Una comprensión simple se puede considerar como una pieza de datos de imagen, y el modelo a menudo tiene textura, por lo que TextureData puede cooperar con el sombreador de fragmentos para determinar qué texel en la imagen de textura se asigna a qué vértice.
Sombreador de vértices (Sombreador de vértices) Puede recibir datos como Uniformes, Atributos, Datos de Textura, etc. Se utiliza para procesar coordenadas de vértices, como el escalado y movimiento de las coordenadas del espacio tridimensional del modelo. (Los datos de textura generalmente se ensamblan directamente en el sombreador de fragmentos para su procesamiento)
Primitivos Sólidos o superficies 1D o 2D (puntos, líneas, polígonos).
Rasterización El proceso de convertir una primitiva en una imagen bidimensional es simplemente el proceso de convertir objetos del mundo tridimensional en píxeles en la pantalla.
Ensamblaje primitivo El ensamblaje primitivo tiene dos partes. El primer paso es ensamblar. El ensamblaje consiste en tomar todos los vértices generados por el sombreador de vértices como entrada y ensamblar todos los puntos en la forma del primitivo especificado. El segundo paso es lograr la rasterización y convierte la primitiva en fragmentos 2D de Groups.
Fragmento Cada punto de una imagen 2D contiene datos de color, profundidad y textura. Este punto y la información relacionada se denomina fragmento y, en el proceso de opengl, un fragmento representa un píxel que se puede dibujar en la pantalla.
Sombreador de fragmentos Los datos de cada fragmento y los datos de textura generados por la etapa de rasterización se pueden recibir para calcular el color final de cada píxel.

Después de comprender el significado de algunos términos de opengl y el proceso de opengl, la mayoría de las personas pueden estar un poco confundidas acerca de conceptos como GLSL y shaders.Puedes echar un vistazo al siguiente ejemplo práctico.

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
using namespace std;

//Shader创建
// -----------------------------------------------------
//GLSL编写的用于顶点着色器的shader
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
"   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";

//用于片段着色器的shader,rgba格式
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
"   FragColor = vec4(0.41f, 0.35f, 0.80f, 1.0f);\n"
"}\n\0";

//顶点数据
float vertices[] = {
     0.5f,  0.5f, 0.0f,  
     0.5f, -0.5f, 0.0f,  
    -0.5f, -0.5f, 0.0f,  
    -0.5f,  0.5f, 0.0f,
     0.5f,  0.4f, 0.0f,
    -0.4f, -0.5f, 0.0f
};

//索引数据
unsigned int indices[] = {    
   0, 2, 3,
   1, 4, 5
};

int main()
{
    //glfw创建
    // -----------------------------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(800, 800, "OpenGL", NULL, NULL);
    glfwMakeContextCurrent(window);

    //glad初始化
    // -----------------------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    //着色器创建和链接
    // -----------------------------------------------------
    //顶点着色器创建
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    //片段着色器创建
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);

    //链接着色器
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    //创建数据对象和绑定数据对象
    // -----------------------------------------------------
    //顶点数组对象:Vertex Array Object,VAO
    //顶点缓冲对象:Vertex Buffer Object,VBO
    //索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO

    unsigned int VBO, VAO, EBO;
    //unsigned int VBO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);


    //绑定VAO数据对象
    glBindVertexArray(VAO);
    //复制顶点数组到缓冲中供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    //复制索引数组到缓冲中供OpenGL使用
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    //解释顶点数据
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);
    //启用顶点属性
    glEnableVertexAttribArray(0);
    //解绑VAO
    glBindVertexArray(0);

    while (!glfwWindowShouldClose(window))
    {
        //按照rgba格式设置窗口背景颜色
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        //将当前设置的color写入启用的缓冲区
        glClear(GL_COLOR_BUFFER_BIT);
        //加载shdaer
        glUseProgram(shaderProgram);
        //绑定VAO数据对象
        glBindVertexArray(VAO); 
        //获取索引,根据GL_TRIANGLES类型绘制图像
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    return 0;
}

 Resultados de la:

El flujo de la implementación del código anterior simplemente presenta el proceso de renderizado opengl, la creación de ventanas, la escritura de sombreadores, la creación y vinculación de sombreadores, la creación y el dibujo de VAO, VBO, EBO.

Análisis de la función OpenGL

La creación de la ventana se ha mencionado al principio de este artículo.

Escritura de sombreado

El shader está escrito en el lenguaje GLSL. Es posible que encuentre que esta sintaxis es muy similar al lenguaje C, y está marcado en un formato de cadena. Se puede decir que la comprensión simple es así. Sobre esta base, hay algunos más tales como: declaración de versión opengl, declaración de ubicación de datos y atributos especiales, entrada, salida, vec3, etc., y el sombreador realmente se ejecuta en la GPU, lo que significa que no podemos escribir el registro del sombreador, pero podemos realizar una verificación de errores cuando el sombreador está compilado, el método específico se agregará más adelante.

Creación y vinculación de shaders

Creación y compilación de shaders

El shader mencionado arriba, escrito como código, es relativamente fácil de entender. A través de la sintaxis especial glCreateShader y los parámetros, podemos crear el shader y obtener su id correspondiente. Al operar glShaderSource en el id, podemos usar el shader escrito arriba y el coloración correspondiente El controlador está vinculado, solo vincular no es suficiente, también se necesita glCompileShader para compilar.

En opengl, tenemos y debemos tener dos shaders: vertex shader y fragment shader (también conocido como: fragment shader), los cuales se crean a través de los mismos tres pasos: glCreateShader, glShaderSource, glCompileShader.

Enlaces a sombreadores

Hemos creado y compilado el sombreador de vértices y el sombreador de fragmentos, y luego necesitamos fusionarlos y vincularlos al objeto del programa de sombreado. El procesamiento del sombreador que realizamos en opengl usa el objeto del programa de sombreado (tenga en cuenta que sí, hay un orden estricto entre el enlace del sombreador y el objeto del programa de sombreado. La salida del sombreador anterior corresponde a la entrada del siguiente sombreador. Si los datos no coinciden, ¡se producirá un error!), la creación del objeto del programa de sombreado es similar a el del sombreador, Create y obtenga la identificación a través de glCreateProgram, de acuerdo con la identificación, usamos glAttachShader para adjuntar cada objeto de sombreado y, finalmente, completamos el enlace general a través de glLinkProgram.

En resumen, a través de tres pasos: glCreateProgram, glAttachShader, glLinkProgram

Creación y dibujo VAO, VBO, EBO/IBO

VAO, nombre completo Objeto de matriz de vértices, objeto de matriz de vértices, VBO nombre completo Objeto de búfer de vértices, objeto de búfer de vértices, nombre completo de EBO Objeto de búfer de elementos, nombre completo de IBO Objeto de búfer de índice, todos se refieren al objeto de matriz de índices

En opengl, creamos un objeto de programa de sombreado, y luego necesitamos ingresar datos.¿Cómo definir el tipo de datos?

Datos de coordenadas de vértice

Las coordenadas de vértice en opengl usan coordenadas 3D. En pocas palabras, es un sistema de coordenadas rectangulares en el espacio, correspondiente a las tres direcciones de x, y y z. Puede encontrar que nuestros datos de vértice reales en el código son (x, y, z, w), el vector w se usa para el procesamiento de perspectiva. Como conocimiento básico, no entraremos en detalles, pero lo presentaremos en detalle más adelante. Por ejemplo, en el código, los vértices realmente cargan los datos de coordenadas de cada punto que queremos dibujar, y z se establece en 0. Son las coordenadas del plano 2D, correspondientes al sistema de coordenadas cartesianas del plano (debe tenerse en cuenta que el rango de coordenadas en opengl es 0-1, y todas las coordenadas de entrada deben ser normalizado)

índice de datos de matriz

La matriz de índices en el código, algunas personas pueden tener dudas sobre esto, este es el punto de índice, cada columna de datos como (0, 1, 2), corresponde a la primera fila, segunda fila y tercera fila de datos en los datos de coordenadas del vértice, de modo que cuando opengl dibuje, encontrará los vértices correspondientes a las tres líneas de datos y los vinculará de acuerdo con la imagen dibujada, y así sucesivamente, podemos dibujar la siguiente imagen y usarla repetidamente, por ejemplo ( 0, 1, 3), los sorteos de openlg para encontrar la primera fila, la segunda fila, la cuarta fila de datos y las matrices de índice se usan a menudo en proyectos reales.

El significado y el proceso de creación de VBO

La matriz que definimos debe estar vinculada al VBO para ingresar al sombreador

Proceso de creación: Vinculamos el tipo de objeto y la dirección a través de glBindBuffer y luego usamos glBufferData para ingresar el valor en VBO

El significado y proceso de creación de VAO

En proyectos reales, es imposible para nosotros dibujar solo dos imágenes. Cuando el número es demasiado grande, necesitamos implementar llamadas VBO cada vez que dibujamos una imagen. El dibujo será muy complicado y VAO puede guardar objetos VBO, como como VAO1 corresponde a VBO1, VAO2 corresponde a VBO2.Al dibujar, llamamos VAO1, VAO2... y podemos dibujar todos los datos VBO del objeto

Proceso de creación: Cree VAO1 a través de glGenVertexArrays, y luego use glBindVertexArray para vincular VAO1 antes del VBO1 que debe vincularse, de modo que VAO1 corresponda al VBO1 descrito anteriormente. Al dibujar, llame directamente a glBindVertexArray para llamar al contenido de VBO1

función glVertexAttribPointer

La función glVertexAttribPointer le dice a OpenGL cómo analizar los datos de los vértices. Esta función está deshabilitada de forma predeterminada y glEnableVertexAttribArray debe habilitarla. Los parámetros correspondientes son los siguientes:

glEnableVertexAttribArray (0, 3, GL_FLOAT, GL_FALSE, 3 * tamaño de (flotante), (vacío*)0)

primer parámetro

0

Especifique los atributos de vértice que queremos configurar. ¿Recuerda que usamos el diseño (ubicación = 0) en el sombreador de vértices para definir el valor de posición (Ubicación) del atributo de vértice de posición? Puede establecer el valor de posición del atributo de vértice en 0. Porque queremos pasar datos a este atributo de vértice, aquí pasamos 0

segundo parámetro

3

Especifica el tamaño del atributo de vértice. El atributo de vértice es un vec3 que consta de 3 valores, por lo que el tamaño es 3.
El tercer parámetro GL_FLOAT Especifique el tipo de datos, aquí está GL_FLOAT (vec* en GLSL se compone de valores de coma flotante).
El cuarto parámetro GL_FALSE Define si queremos que los datos se normalicen (Normalize). Si lo configuramos en GL_TRUE, todos los datos se asignarán entre 0 (-1 para datos firmados) y 1. Lo configuramos en GL_FALSE.

quinto parámetro

3 * tamaño de (flotante)

Stride, que nos indica el intervalo entre grupos sucesivos de atributos de vértice. Dado que los datos de posición del siguiente grupo están después de 3 flotantes, establecemos el tamaño de paso en 3 * tamaño de (flotante). Tenga en cuenta que, dado que sabemos que la matriz está muy empaquetada (sin espacios entre dos atributos de vértice), también podemos establecerla en 0 para permitir que OpenGL decida cuál es el paso exacto (solo disponible si los valores están muy empaquetados).
El sexto parámetro (void*)0 Representa el desplazamiento (Offset) de la posición inicial de los datos de posición en el búfer. Dado que los datos de posición están al comienzo de la matriz, aquí es 0.

El significado y el proceso de creación de EBO/IBO

El objeto de matriz de índice es un objeto que usamos a menudo en el desarrollo real. Los datos de coordenadas de vértice pueden tener decenas de miles o cientos de miles. Esto solo se refiere a la cantidad de puntos independientes. Al dibujar, es posible que necesitemos conectar el primero, 2 y 3, y luego conectamos los tres puntos 1, 2 y 4. En el código, usamos glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0) para decirle a opengl Nuestros datos de vértice es un grupo de tres, y los GL_TRIANGLES en glDrawElements se dibujarán en triángulos más adelante. Hay un problema aquí. Opengl solo sabe usar tres puntos para dibujar en triángulos, por lo que al dibujar un triángulo con puntos repetidos, será necesario. para ingresar repetidamente estos datos de vértice. Por ejemplo, nuestros datos de vértice: (0.1f, 0.2f, 0.3f), cada entrada de vértice adicional requiere un conjunto adicional de datos de vértice. En un modelo con cientos de miles de puntos, puede causar Más de 100,000 puntos de datos conducirán a la expansión del espacio de memoria y un proceso de cálculo complicado, por lo que debemos introducir un objeto de matriz de índice, que puede decirle a opengl cómo reutilizar los datos en el objeto de matriz de vértices.

Su proceso de creación es similar a VBO: cree EBO y obtenga la identificación a través de glGenBuffers, copie la matriz de índice en el búfer a través de glBindBuffer y glBufferData para que OpenGL la use, y use glDrawElements para decirle a opengl que dibuje la imagen con el índice.


Resumen: este artículo es una breve introducción al proceso general de opengl, descrito a través del código, y presenta específicamente el conocimiento de gráficos relacionados con opengl, para que los principiantes puedan tener una impresión general de opengl en su conjunto, y el conocimiento posterior de opengl será desarrollado en profundidad y amplitud sobre esta base extensión.

Supongo que te gusta

Origin blog.csdn.net/qq_40779012/article/details/123533931
Recomendado
Clasificación