opengl (+3.2) opentk shaders introducción: laboratorio 0 ...cs.uns.edu.ar/cg/practicos/labo0...
TRANSCRIPT
Introducción
● En la materia, trabajaremos con la librería gráfica OpenGL○ En particular, con una versión “moderna”.
● Herramientas que utilizaremos:○ OpenGL +3.2.○ OpenTK○ Visual Studio para C#○ Herramientas de Modelado: Blender, sketchup, etc.○ Herramientas para probar shaders: glMan, etc.○ Herramientas específicas para algún tema.
Intro: OpenTK
● Open toolkit (OpenTK) es una librería de bajo nivel que encapsula (es un wrapper) las librerías OpenGL, OpenCL y OpenAL.
● Ventajas:○ Multiplataforma (Windows / Linux)○ Multilenguaje (Mono/.Net)○ Código abierto.○ Fuertemente tipado (reemplaza constantes por enumerados).○ Uso de genericidad.○ Código completamente manejado (.NET) (No hay que manejar
punteros)○ Documentación en línea.○ Provee APIs para manejar Vectores, Matrices, Quaterniones, Bezier,
Audio, Input.
● Link: www.opentk.com
Intro: OpenTK
● Se descarga e instala desde el sitio oficial.● Archivos que vamos a utilizar están en [InstallFolder]
/Binaries/OpenTK/Release:○ OpenTK.dll: dll de .Net que debemos incluir.○ OpenTK.xml: Documentación de las operaciones.○ OpenTK.GLControl.dll: Componente gráfico para visual studio.○ OpenTK.GLControl.xml: documentación del GLControl.
● Además del Código fuente, incluye un navegador de ejemplos. (Aunque los ejemplos estan desactualizados.)○ [InstallFolder]/Binaries/OpenTK/Release/Examples.exe
Intro: OpenTK
● Los espacios de nombres (namespaces) que utilizaremos son:○ OpenTK: Se encuentran las clases y structs auxiliares matemáticas,
como Vector, Matrix, etc.○ OpenTK.Graphics.OpenGL: Se encuentran todas las clases, enums y
structs que reemplazan a OpenGL.
● La clase estática principal es: OpenTK.Graphics.OpenGL.GL
En OpenGL: En OpenTK:
glClear(...); GL.Clear(...);
glViewport(...); GL.Viewport(...);
● El componente GLControl nos permitirá crear interfaces gráficas de manera fácil. Lo podemos manejar como cualquier otro componente (botón, slider, label, etc)
● Durante el transcurso de la materia utilizaremos la API que nos brinda OpenGL, en su versión 3.2 o superior.
● Para trabajar con este OpenGL moderno, es necesario comprender dos conceptos fundamentales que utilizaremos a lo largo del cuatrimestre:
Introducción
Shaders Buffers
Programas en OpenGL
● Los programas OpenGL actuales esencialmente deben realizar los siguientes pasos:
1. Crear y cargar los shaders y programas de shaders.2. Crear los buffers y cargar los datos en ellos.3. “Conectar” la ubicación de esos datos con las variables
en los shaders.4. Renderizar (Dibujar).
Shaders
En OpenGL 3.x estamos obligados a trabajar con el pipeline programable.
● Vamos a tener que programar (sólamente algunas etapas) de este pipe.
● Esto nos brinda mucha más flexibilidad, a costa de un mayor esfuerzo en la programación.
Para programarlo, vamos a utilizar Shaders.
Shaders
● Los shaders son pequeños programas que se ejecutan dentro de la GPU.○ Su propósito es ejecutar algunas de las etapas del pipeline de
rendering.
● Las GPUs actuales nos permiten especificar varias etapas de su pipeline. Sin embargo, nosotros sólo trabajaremos con:○ Shaders de vértices.○ Shaders de fragmentos.
● Cada shader tiene determinadas entradas y determinadas salidas.
Esquema simplificado de la GPU
Vertexprocessing Rasterizer Fragment
processing
GPU Data flow FramebufferApp.
vertices vertices fragments pixels
Vertexshader
Fragmentshader
¡Debemos programarlos!
Shaders en OpenGL
En OpenGL, vamos a utilizar el lenguage GLSL (GL Shading Language), que es muy similar a C.Agrega muchas facilidades, pero también tiene restricciones.● Por ejemplo, tiene soporte para vectores y matrices,
pero no soporta recursión.
Más sobre GLSL en el Orange Book: OpenGL Shading Language, third ed.
GLSL: Tipos de datos y operaciones
● Scalar types: float, int, bool● Vector types:
○ vec2, vec3, vec4○ ivec2, ivec3, ivec4○ bvec2, bvec3, bvec4
● Matrix types: mat2, mat3, mat4, matNxM● Texture sampling: sampler1D, sampler2D, sampler3D,
samplerCube.
● C++ style constructor: vec3 a = vec3(1.0, 2.0, 3.0);● Operadores aritméticos y lógicos estandar de C/C++● Sobrecarga de operadores para vectores y matrices: mat4 m; b = a * m; //Multip. de vec y mat.
vec4 a, b, c; c = m * a;
GLSL: Componentes y “swizzling”
● Diversos modos de acceso a las componentes del vector:○ [] (usando corchetes, al estilo C)○ xyzw, rgba ó strq (usando nombres de componentes)
● Ejemplo:○ vec3 v; v[1], v.y, v.g, v.t - se refieren todos al mismo
elemento (segundo elemento del vector v)● Swizzling:
○ vec3 a, b;○ a.xy = b.yx
GLSL: Calificadores y funciones
● in, out: Copia atributos de vértices y otras variables hacia o desde los shaders○ in vec2 textCoord;○ out vec4 color;
● uniform: Valor constante durante una operación de dibujado.○ uniform float time;○ uniform vec4 rotation;
● Funciones built-in:○ Aritméticas: sqrt, power, abs○ Trigonométricas: sin, asin○ Gráficas: length, reflect
● Funciones definidas por el usuario
GLSL: Built-in variables
● gl_Position:○ (requerida) posición del vertice (en espacio de clipping). Salida del
shader de vértices.● gl_FragCoord:
○ posición del fragmento. Entrada al shader de Fragmentos.● gl_FragDepth:
○ profundidad del fragmento. Entrada al shader de Fragmentos.
Obligatorio:● que el shader de vertices retorne la posición en gl_Position.● que el shader de fragmentos retorne un color.
Ejemplo (código fuente shader vért.)
// VERTEX SHADER. Simple. Transforma la posicion.
#version 150
in vec3 vPos;
uniform mat4 projMat;
uniform mat4 mvMat;
void main(){
gl_Position = projMat * mvMat * vec4(vPos, 1.0);
}
Directivas al compilador
Parámetros de entrada, salida y uniformes
Ejemplo (código fuente shader frag.)
// FRAGMENT SHADER.
#version 150
uniform vec4 figureColor;
out vec4 fColor;
void main(){
fColor = figureColor;
}
Terminología
En GLSL, se utiliza una terminología distinta a la de otros lenguajes de shaders.● Un shader es el código compilado para controlar una
determinada etapa (programable) del pipeline.● Un programa es un conjunto de shaders que controlan
las distintas etapas del pipe.Nosotros vamos a construir programas, utilizando shaders, para ser ejecutados en la GPU.
Modelo de Compilación
GLSL utiliza un modelo de compilación parecido al de C.● Se compilan por separado los códigos fuente de cada shader, para
generar códigos objeto.● Se linkean varios códigos objetos, para generar el programa ejecutable.
El programa ejecutable es el que se envía a la GPU para que controle las etapas del rendering.
Estas operaciones de compilación/linkeo se realizan en tiempo de ejecución de nuestra aplicación!● Esto quiere decir que cada vez que ejecutamos nuestra aplicación, se
compila(n) y linkea(n) nuestros shaders/programas.
En OpenGL
● En OpenGL, los shaders y programas se manejan (al igual que otros recursos) con objetos.○ NO los objetos de POO!!.
● Cada objeto tiene asignado un identificador (handle) con el cual podemos manipularlo.
● OpenGL crea, asigna y destruye los objetos (e identificadores) a pedido nuestro.
En OpenGL
int glCreateShader(tipo);● Crea un objeto shader vacío y devuelve un identificador
para poder referirnos a éste.● El tipo especifica que shader vamos a crear: de
vértices, de fragmentos, geométrico, etc.void glShaderSource(int shader, string source);● Asigna el código fuente de un determinado shader.
En OpenGL
void glCompileShader(int shader);○ Compila el código fuente de un shader.
El estado de la compilación e información sobre la misma, puede obtenerse mediante:glGetShader(..) yglGetShaderInfoLog(...).
Consulte la documentación de estas funciones.
En OpenGL
int glCreateProgram();
○ Crea un objeto programa vacío, y retorna un identificador.
void glAttachShader(prog, shader);○ Adosa un objeto shader a un objeto programa.
void glGetDettachShader(prog, shader);○ Quita el shader del programa.
Consulte la documentación de estas funciones.
En OpenGL
void glLinkProgram(program);
○ Linkea un programa con todos los shaders que tenga adosados.
El estado del proceso puede consultarse mediante:glGetProgram(...);glGetProgramInfoLog(...);
Consulte la documentación de estas funciones.
En OpenGL
void glUseProgram(program);
○ Setea un programa linkeado para ser utilizado en el rendering.
○ A partir de ese momento, se usará el programa hasta que no se especifique otro. (Recordar que OpenGL es una máquina de estados)
glUseProgram(0);Setea un programa nulo.
○ Estado indeterminado!○ Para renderizar, hay que volver a setear otro
programa.
En OpenGL
void glDeleteShader(shader);○ Elimina un objeto Shader.○ Si el shader está adosado a un programa, no se
elimina hasta que se elimine el programa, o bien se desacople del mismo (dettach).
void glDeleteProgram(program);○ Elimina un objeto Programa.○ Para renderizar, hay que volver a setear otro
programa.
Secuencia
glCreateProgram(...);
glCreateShader(...);
glShaderSource(...);
glCompileShader(...);
glAttachShader(...);
glLinkProgram(...);
glUseProgram(...);
Estos pasos se repiten para cada tipo de shader en el programa.
Estos pasos se realizan una única vez (Inicialización)
Cada vez que se quiera dibujar algo utilizando este programa de shader.
Shaders - Parámetros
Cada shader tiene entradas y salidas. Estas pueden provenir desde la aplicación o desde otra etapa del pipe.Las entradas del shader de vértices se denominan vertex attributes.Cuando se “linkea” el programa, a cada parámetro de entrada se le asigna una ubicación.
○ Un índice a una tabla interna que nos permitirá especificarle los datos de entrada.
Shaders - Parámetros
Se dividen en dos:● Los indices para los atributos de entrada al shader de
vertices.○ Las variables declaradas como in.
● Los indices para los atributos uniformes.○ Las variables declaradas como uniform.
Para obtener los primeros:glGetProgram(...); (Con el flag ActiveAttributes)glGetActiveAttrib(...);
Para obtener los segundos:glGetProgram(...); (Con el flag ActiveUniforms)glGetActiveUniformName(...);
Modelando Shaders con Clases
Como vamos a utilizar frecuentemente los shaders, ya sea para:
○ Crearlos, leerlos de un archivo de texto.○ Compilarlos y detectar errores.○ Crear programas para agrupar los shaders.○ Linkear el programa y detectar errores.○ Establecer los datos de entrada
Podemos tratar de modelarlos con clases, para favorecer su reutilización.Un posible modelo puede ser:
Modelando Shaders con Clases
Shader- ID : int- type : ShaderType- fileName : String- source :String
- Shader(fileName, type) : void- Compile() : void- Delete() : void- GetID() : int
ShaderProgram- ID : int- shaders : List<Shader>- uniformLocations : Dictionary<String, int>- attribLocations : Dictionary<String, int>
- ShaderProgram() : void- AddShader(shader) : void- Build() : void- Activate() : void- Deactivate() : void- GetAttribLocation(attribName) : int- GetUniformLocation(uniformName) : int- SetUniformValue(uniformName, value) : void
2..*
ProgramLinkageException
ShaderCompilationException
ProgramShaderExceptionEstudiar el ejemplo adjunto para ver cómo se utilizan estas clases!!!
Buffers - Introducción
● Veremos el concepto y la forma de trabajar de OpenGL a la hora de almacenar la geometría y atributos de los objetos que deseamos mostrar en pantalla.
● Esto ha ido variando en las distintas versiones de OpenGL, principalmente por cuestiones de performance.○ Modo inmediato [OBSOLETO]: Cada vez que se dibujaba la escena,
se enviaba toda la geometría a la GPU.○ Buffers: La mayor parte de la información reside en la GPU, se envía
una sola vez.
Esquema ilustrativo
● Los objetos que dibujemos, son enviados a la GPU, donde son procesados y terminan componiendo la imagen vista en la pantalla.
● Para renderizar un modelo, la GPU espera un flujo de información sobre los vértices, ó vertex stream.
CPU GPU
Vértices
● Los objetos que queremos dibujar se representan mediante vértices.
● Un vértice tiene una colección de atributos:○ Posición (el más importante)○ Color○ Normal○ Coordenadas de textura○ Cualquier otro dato asociado a ese punto en el
espacio.● La posición se procesa en coordenadas homogéneas
(4D)
Buffers
● Los Buffers son objetos de OpenGL.○ Son administrados por el contexto de OpenGL.
● Son bloques de memoria asignados por el contexto de OpenGL (GPU) que almacenan información sin formato.○ Similar al espacio que se reserva al utilizar malloc()
en el lenguaje C.● Se pueden utilizar para almacenar distinto tipo de
información:○ Atributos de vértices, información de pixels,
texturas, etc.
VBOs, EBOs y VAOs
● Vertex Buffer Objects (VBOs): Son buffers diseñados para almacenar información (atributos) sobre los vértices:○ Posiciones○ Colores○ Normales○ Coordenadas de Texturas, etc.○ Indices. A veces llamados Element Buffer Object (EBO).
● Vertex Array Objects (VAOs): Son objetos de OpenGL que contienen uno o más VBOs y (opcional) un EBO, junto con su configuración.○ Es decir, contiene toda la información para que un objeto pueda ser
renderizado
Pasos para crear un VBO
● Generar un nombre (handler) para el buffer.○ glGenBuffers(cant, &ids)
● Seleccionarlo para configurarlo/utilizarlo.○ glBindBuffer(GL_ARRAY_BUFFER, id)○ GL_ARRAY_BUFFER: Buffer de datos.○ GL_ELEMENT_ARRAY_BUFFER: Buffer de índices.
● Inicializarlo con datos.○ glBufferData(...) / glBufferSubData(...)
● Cuando ya no se necesita más.glDeleteBuffers(cant, ids)
glBufferData
glBufferData(target, size, data, hint)
Reserva espacio para el buffer y llena el mismo con datos.
● target: GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, etc.
● size: Tamaño (en BYTES!) del buffer.
● data: La información a almacenar.
● hint: Flag para que la implementación de OpenGL decida dónde almacenar el contenido (entre otras cosas) con relación a:○ Frecuencia de acceso al buffer: STATIC, DYNAMIC, STREAM.○ Modo de acceso: DRAW, READ, COPY.
Por ahora utilizaremos GL_STATIC_DRAW
Pasos para crear un VAO
● Generar un nombre (handler) para el objeto.○ glGenVertexArrays(cant, &ids)
● Seleccionarlo para configurarlo/utilizarlo.○ glBindVertexArray(id)
● Configuramos cada VBO.○ glEnableVertexAttribArray(attribLocation)○ glBindBuffer(bufferType, bufferId)○ glVertexAttribPointer(...)
● Seleccionamos el EBO a utilizar.○ glBindBuffer(bufferType, bufferId)
● Cuando ya no se necesita más el VAO.glDeleteVertexArrays(cant, ids)
glVertexAttribPointerglVertexAttribPointer(index, size, type, normalized, stride, offset)
Especifica la ubicación y formato de un arreglo de atributos de vértices.
● index: Número de atributo (ubicación en el programa de shaders)● size: Cantidad de componentes de cada dato (1, 2, 3 ó 4)
○ size = 2: (u, v); (s, t) coordenadas de textura.○ size = 3: (x, y, z); (r, g ,b) posiciones, colores.○ size = 4: (x, y, z, w); (r, g, b, a) posiciones coord homogeneas, colores
con opacidad/transparencias.● type: Tipo de cada componente (BYTE, SHORT, INT, HALF_FLOAT, FLOAT,
DOUBLE… ) Los tipos enteros pueden ser con o sin signo.(UNSIGNED)● normalized: Si type es entero, se pasan a punto flotante. Este flag indica si
se pasa directamente ó si lo tiene que normalizar primero.○ Rango [-1.0, 1.0] para enteros con signo○ Rango [0.0, 1.0] para enteros sin signo
Stride y Offset
En los VBOs almacenamos los atributos de los vértices:● Cada atributo en un VBO distinto (VBO de posiciones,
VBO de colores, VBO de normales, etc.)● Varios atributos en un mismo VBO.
○ Agrupados: Primero las posiciones, luego colores…○ Intercalados: posicion, color, normal, posicion,....
● Esto último lo configuramos con los parámetros stride y offset:○ Offset: desplazamiento (en bytes) de la primer componente del
atributo que estamos configurando.○ Stride: cantidad de bytes que hay que “saltar” para encontrar la
próxima componente del atributo que estamos configurando. Si los datos estan uno a continuación del otro, podemos usar Stride = 0.
Dibujar el contenido del VAO
En el VAO tenemos toda la configuración para dibujar un objeto● Seleccionamos el VAO a utilizar:
○ glBindVertexArray(VAO_id)
● Si utilizamos índices, dibujamos con:○ glDrawElements(primitive, count, idxType, offset)
● Si no usamos índices, dibujamos con:○ glDrawArrays(primitive, start, count)
● Finalmente, desactivamos el VAO:○ glBindVertexArray(0)
Primitivas Gráficas
● Para formar los objetos geométricos en 3D, se descomponen en primitivas geométricas que OpenGL puede dibujar:○ Puntos, Líneas, Triángulos○ Puede utilizar colecciones del mismo tipo de primitiva para optimizar
el rendering.
Recordemos: Programas en OpenGL
● Los programas OpenGL actuales esencialmente deben realizar los siguientes pasos:
1. Crear y cargar los shaders y programas de shaders.2. Crear los buffers y cargar los datos en ellos.3. “Conectar” la ubicación de esos datos con las variables
en los shaders.4. Renderizar (Dibujar).
Vinculación buffers - shaders
Como vimos, al configurar el VAO, especificamos la ubicación del atributo. Esta ubicación es la que se le asignó a cada variable de tipo IN que hay en el shader de vertices.La ubicación de los atributos puede obtenerse de 3 formas:● Dejar que OpenGL asigne una ubicación, y utilizar glGetAttribLocation
(...), DESPUES del linkeo.● Especificar la ubicación nosotros con glBindAttribLocation(...),
ANTES del linkeo.● Especificar la ubicación en el código fuente con una directiva layout
(location = x) (sólo para #version 330 o superior)