En OpenTK

Anuncio
Introducción: Laboratorio 0
OpenGL (+3.2)
OpenTK
Shaders
Buffers
CG 2015
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.
OpenGL: Definición e Historia
Ver transparencias SIGGRAPH 2013.
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)
Introducción
● 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:
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
App.
Framebuffer
GPU Data flow
vertices
vertices
Vertex
processing
fragments
Rasterizer
Vertex
shader
pixels
Fragment
processing
Fragment
shader
¡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,
● Vector types:
○
○
○
int, bool
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;
vec4 a, b, c;
c = m * a;
//Multip. de vec y mat.
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
Directivas al compilador
in vec3 vPos;
uniform mat4 projMat;
Parámetros de entrada, salida y
uniformes
uniform mat4 mvMat;
void main(){
gl_Position = projMat * mvMat * vec4(vPos, 1.0);
}
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(..) y
glGetShaderInfoLog(...).
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
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
Shader
2..*
- ID : int
- type : ShaderType
- fileName : String
- source :String
- Shader(fileName, type) : void
- Compile() : void
- Delete() : void
- GetID() : int
ProgramLinkageException
ShaderCompilationException
Estudiar el ejemplo adjunto para ver
cómo se utilizan estas clases!!!
ProgramShaderException
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.
CPU
GPU
● Para renderizar un modelo, la GPU espera un flujo de
información sobre los vértices, ó vertex stream.
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)
glVertexAttribPointer
glVertexAttribPointer(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)
Ejemplo - Laboratorio 0
● Una vez que tenemos claros estos dos conceptos
principales (Shaders y Buffers), examinemos el
proyecto de Visual Studio del Laboratorio 0.
Descargar