Shaders

Anuncio
Shaders
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
En OpenGL antes de la versión 2.0 la línea de
producción gráfica implementada por OpenGL
era fija: se podían cambiar estados, pero los
resultados eran muy restringidos, por ejemplo,
no se podían aplicar efectos como normal
mapping (bump mapping).
■  Por ello, a partir de OpenGL 2.0 se
implemento la posibilidad de programar partes
de la línea de producción gráfica.
■ 
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
Primitivas Geométricas 3D
Espacio del Modelo
Transformaciones
del Modelo
Espacio del Ojo
Iluminación
Transformaciones
de Vista
Espacio de Restricción
Restricciones
(Clipping)
Espacio de la Pantalla
Transformaciones
de Proyecciones
Conversión de
Barrido
Imagen
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
Primitivas Geométricas 3D
con atributos
Transformaciones
del Modelo
Conversión de
Barrido
Aplicación de
Texturas
Iluminación
Suma de Colores
Coordenadas y Transformaciones
de Textura
Restricciones
(Clipping)
Transformaciones
de Proyecciones
Édgar Garduño Ángeles!
Niebla
AntiAliasing
Operaciones sobre Píxeles
Imagen
C. Computación, I.I.M.A.S!
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
•  geometry shaders desde OpenGL 3.2
•  tessellation shaders desde OpenGL 4.0
•  compute shaders desde OpenGL 4.3.
Primitivas Geométricas 3D
con atributos
Transformaciones de Vértices
(Vertex Shaders)
Transformaciones de Geometria
(Geometry Shaders)
Ensamble de Primitivas
Ensamble de Primitivas
Control de Teselación
(Tesselation Control Shaders)
Generador de Primitivas
de Teselación
Evaluación de Teselación
(Tesselation Evaluation Shaders)
Édgar Garduño Ángeles!
Operaciones sobre Píxeles
(Fragment Shaders)
AntiAliasing
Restricciones
(Clipping)
Operaciones sobre Píxeles
Rasterizado
Imagen
C. Computación, I.I.M.A.S!
GLSL Version
Versión OpenGL
Fecha
Shader
Preprocessor
1.10.59
2.0
Abril 2004
#versión 110
1.20.8
2.1
Septiembre 2006
#versión 120
1.30.10
3.0
Agosto 2008
#versión 130
1.40.08
3.1
Marzo 2009
#versión 140
1.50.11
3.2
Agosto 2009
#versión 150
3.30.6
3.3
Febrero 2010
#versión 330
4.00.9
4.0
Marzo 2010
#versión 400
4.10.6
4.1
Julio 2010
#versión 410
4.20.11
4.2
Agosto 2011
#versión 420
4.30.8
4.3
Agosto 2012
#versión 430
4.40
4.4
Julio 2013
#versión 440
4.50
4.5
Agosto 2014
#versión 450
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
■ 
■ 
■ 
La programación de vértices, geometría y fragmentos
en OpenGL se conoce como Shading y el lenguaje se
conoce OpenGL Shading Language o GLSL.
GLSL es un lenguaje de alto nivel que está basado en
C/C++ y es el que se utiliza para escribir shaders.
Referencias:
●  M. Bailey and S. Cunningham, Graphics Shaders Theory
an Practice. A K Peters, 2009.
●  R. J. Rost, OpenGL(R) Shading Language, (2nd Edition).
Addison-Wesley Professional, January 2006.
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
■ 
■ 
■ 
Cuando se utilizan programas de GLSL es
necesario crear por lo menos un Vertex Shader y
un Fragment Shader.
El vertex shader únicamente procesa primitivas de
vértices en un esquema entrada/salida estricto: un
vértice entra y sale un vértice.
En esta etapa el procesamiento se lleva a cabo en
forma completamente independiente; a través de
la pipeline un vértice no tiene información acerca
de los otros lo cual permite procesar varios
vértices en paralelo.
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
■ 
■ 
■ 
Cuando se puede utilizar un geometry shader, este
procesa primitivas formadas por vértices conectados,
tales como líneas o polígonos. En esta etapa toda la
información perteneciente a la primitiva se encuentra
disponible.
En este caso, el shader puede producir primitivas de
salida que sean diferentes a las de entrada; sin embargo,
los tipos de primitivas de entrada y/o de salida y el
número máximo de vértices de salida deben estar
predefinidos por el usuario.
Cada primitiva de entrada puede generar como salida
desde cero primitivas hasta el número máximo,
predefinido, de primitivas.
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
■ 
■ 
■ 
En el caso de un fragment shader, este se ejecuta
una vez por cada fragmento generado por cada
primitiva.
Cada ejecución no posee información sobre a cual
primitiva pertenece ni acerca de otros fragmentos.
Sin embargo, recibe los atributos interpolados de
los vértices de la primitiva a la que el fragmento
pertenece.
La tarea más importante de un fragment shader es
producir o descartar un color por cada fragmento.
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
varying vec3 C;
void main(void)
{
// Transformación Proyección ModelView
vec4 clipCoord = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_Position = clipCoord;
// Copia el color primario
gl_FrontColor = gl_Color;
// Calcula las Coordenadas Normalizadas del Dispositivo
vec3 ndc = clipCoord.xyz / clipCoord.w;
// Mapea del rango [-1,1] a [0,1] antes de producir una salida
C = (ndc * 0.5) + 0.5;
}
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
varying vec3 C;
void main(void)
{
// Mezcla los colores primarios y secundarios usando una proporción 50:50
gl_FragColor = mix(gl_Color, vec4(vec3(C), 1.0), 0.5);
}
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
■ 
Estos son los tipos básicos en GLSL:
●  float,
float vec2, vec3, vec4
●  int,
int ivec2, ivec3, ivec4
●  bool,
bool bvec2, bvec3, bvec4
●  void
●  matcxr, donde c y r =2,3 o 4 (float)
●  sampler1D, sampler2D, sampler3D
●  samplerCube
●  sampler1DShadow, sampler2DShadow
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
■ 
A partir de GLSL 1.30
●  uint
●  double
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
struct surface{
float indexOfRefraction;
float reflectivity;
vec3 color;
float turbulence;
} myStruct;
…
surface anotherSurf;
Para estructuras del mismo tipo se pueden usar comparaciones =, == y !=.
Como en C/C++, se puede acceder a un miembro de la estructura usando el
operador ., por ejemplo:
float reflection = anotherSurf.reflectivity * 0.6;
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
Se pueden declarar arreglos unidimensionales de
cualquier tipo, excepto arreglos de arreglos:
surface mySurfaces[];
vec4 lightPositions[8];
vec4 moreLightPositions[] = lightPositions;
const int numSurfaces = 5;
surface myFiveSurfaces[numSurfaces];
float[5] values;ç
Todos los arreglos unidimensionales pueden utilizar el
método length para saber sus tamaño: values.length();
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
const valor constante asignado durante la declaración.
attribute datos de vértices de solo lectura. Solo disponibles para vertex shaders. Se declara como
global (fuera de todas las funciones). Se puede asignar a float, vectores de reales o
matrices de reales pero no a estructuras ni a arreglos.
uniform se utiliza para dejar valores fijos durante la ejecución de un shader. A diferencia de un
tipo const, un valor uniform no se conoce el valor al momento de compilación y se
inicializa fuera del shader.
varying es la salida de un vertex shader que corresponde a la entrada interpolada (de solo lectura) a
un fragment shader.
centroid varying es equivalente a varying cuando no se multi-muestrea, cuando se multi-muestrea,
el tipo se evalúa dentro de una posición que cae en el interior de la primitiva
que se está rasterizando en lugar de una posición fija, tal como el centro.
invariant se utiliza para las salidas de un vertex shader (y entrada del correspondiente fragment
shader) para indicar que los valores deben ser consistentes entre varios shaders. NO se
debe usar a menos que sea indispensable.
in califica un argumento como de solo entrada. Este es el comportamiento por defecto.
out califica un argumento como de solo salida. No necesita tener un valor a la entrada.
inout califica un argumento como de entrada/salida. El argumento puede tener valores de entrada y
regresar valores de salida.
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
Nombre
Tipo
Descripción
gl_Color
vec4
Input attribute corresponding to per-vertex primary color.
gl_SecondaryColor
vec4
Input attribute corresponding to per-vertex secondary color.
gl_Normal
vec3
Input attribute corresponding to per-vertex normal.
gl_Vertex
vec4
Input attribute corresponding to object-space vertex position.
gl_MultiTexCoordn
vec4
Input attribute corresponding to per-vertex texture coordinate n.
gl_FogCoord
float
Input attribute corresponding to per-vertex fog coordinate.
gl_Position
vec4
Output for transformed vertex position that will be used by fixed functionality
primitive assembly, clipping, and culling; all vertex shaders must write to this
variable.
gl_ClipVertex
vec4
Output for the coordinate to use for user clip plane clipping.
gl_PointSize
float
Output for the size of the point to be rasterized, measured in pixels.
gl_FrontColor
vec4
Varying output for front primary color.
gl_BackColor
vec4
Varying output for back primary color.
gl_FrontSecondaryColor
vec4
Varying output for front secondary color.
gl_BackSecondaryColor
vec4
Varying output for back secondary color.
gl_TexCoord[]
vec4
Array of varying outputs for texture coordinates.
gl_FogFragCoord
float
Varying output for the fog coordinate.
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
Nombre
Tipo
Descripción
gl_Color
vec4
Interpolated read-only input containing the primary color.
gl_SecondaryColor
vec4
Interpolated read-only input containing the secondary color.
gl_TexCoord[]
vec3
Array of interpolated read-only inputs containing texture
coordinates.
gl_FogFragCoord
float
Interpolated read-only input containing the fog coordinate.
gl_FragCoord
vec4
Read-only input containing the window-space x, y, z, and 1/w.
gl_FrontFacing
bool
Read-only input whose value is true if part of a front-facing
primitive.
gl_PointCoord
vec2
Two-dimensional coordinates ranging from (0.0, 0.0) to (1.0,
1.0) across a point sprite, defined only for point primitives and
when GL_POINT_SPRITE is enabled.
gl_FragColor
vec4
Output for the color to use for subsequent per-pixel
operations.
gl_FragData[]
vec4
Array of arbitrary data output to be used with glDrawBuffers
and cannot to be used in combination with gl_FragColor.
gl_FragDepth
float
Output for the depth to use for subsequent per-pixel operations;
if unwritten, the fixed functionality depth is used instead.
C. Computación, I.I.M.A.S!
Édgar Garduño Ángeles!
()
[]
.
++ -+-!
*/
< > <= >= == !=
&& || ^^
?:
= += -= *= /=
,
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
vec3 myNormal = vec3(0.0, 1.0, 0.0);
greenTint = myColor + vec3(0.0, 1.0, 0.0);
ivec4 myColor = ivec4(255);
vec4 myVector1 = vec4(x, vec2(y, z), w);
vec2 myVector2 = vec2(myVector1); // z y w se eliminan
float myFloat = float(myVector2); // y se elimina
// Todos estos son lo mismo que la matriz identidad 2x2
mat2 myMatrix1 = mat2(1.0, 0.0, 0.0, 1.0);
mat2 myMatrix2 = mat2(vec2(1.0, 0.0), vec2(0.0, 1.0));
mat2 myMatrix3 = mat2(1.0);
mat2 myMatrix4 = mat2(mat4(1.0)); // takes upper 2x2 of the 4x4
float myFloat = 4.7;
int myInt = int(myFloat); // myInt = 4
ivec2 cursorPositions[3] = ivec2[3]((0, 0), (10, 20), (15, 40));
ivec2 morePositions[3] = ivec2[]((0, 0), (10, 20), (15, 40));
struct surface {
float indexOfRefraction;
float reflectivity;
vec3 color;
float turbulence;
};
surface mySurf = surface(ior, refl, vec3(red, green, blue), turb);
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
■ 
■ 
■ 
■ 
■ 
■ 
Se puede acceder a los componentes individuales de un vector usando la notación de punto junto con
{x,y,z,w}, {r,g,b,a}, or {s,t,p,q}.
vec3 myVector = {0.25, 0.5, 0.75};
float myR = myVector.r; // 0.25
vec2 myYZ = myVector.yz; // 0.5, 0.75
float myQ = myVector.q; // ILEGAL!! Accede a componentes mas allá de vec3
float myRY = myVector.ry; // ILEGAL!! Combina dos notaciones
vec3 myZYX = myVector.zyx; // orden inverse
vec4 mySSTT = myVector.sstt; // replica s y t dos veces cada uno
vec4 myColor = vec4(0.0, 1.0, 2.0, 3.0);
myColor.x = -1.0; // -1.0, 1.0, 2.0, 3.0
myColor.yz = vec2(-2.0, -3.0); // -1.0, -2.0, -3.0, 3.0
myColor.wx = vec2(0.0, 1.0); // 1.0, -2.0, -3.0, 0.0
myColor.zz = vec2(2.0, 3.0); // ILEGAL!!
float myY = myVector[1]; // Comportamiento indefinido
mat3 myMatrix = mat3(1.0);
vec3 myFirstColumn = myMatrix[0]; // primera columna: 1.0, 0.0, 0.0
float element21 = myMatrix[2][1]; // última columna, hilera de enmedio: 0.0
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
■ 
■ 
■ 
for (l = 0; l < numLights; l++){
if (!lightExists[l])
continue;
color += light[l];
}
while (lightNum >= 0){
color += light[lightNum];
lightNum—;
}
do
{
color += light[lightNum];
lightNum—;
} while (lightNum > 0);
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
■ 
■ 
■ 
if (numLights > 0){
color = litColor;
}
if (numLights > 0){
color = litColor;
}
else{
color = unlitColor;
}
Para fragment shaders existe una forma de terminar la ejecución sin
procesar el resto de las instrucciones y sin escribir en el framebuffer:
if (color.a < 0.9)
discard;
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
// declaración
bool isAnyComponentNegative(const vec4 v);
// Utilización
void main()
{
…
bool someNeg = isAnyComponentNegative(gl_MultiTexCoord0);
…
}
// definición
bool isAnyComponentNegative(const vec4 v)
{
…
return true;
…
return false;
}
Igual que en C++, se permite sobrecarga (overload) de funciones.
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
■ 
■ 
El procesador de vértices es responsable de ejecutar los
programas vertex shaders. Un vertex shader recibe como
entrada los datos de vértices (por ejemplo, su posición,
color, o normales asociadas) dependiendo de lo que la
aplicación de OpenGL envíe.
Un vertex shader puede realizar tareas como las siguientes:
●  Transformaciones de las posiciones de los vértices usando las
● 
● 
● 
● 
matrices de modelview y proyección.
Transformaciones de normales y su normalización, en caso de
ser necesario.
Generación y transformación de coordenadas de textura.
Iluminación por vértice o cálculo de valores para iluminación
por píxel.
Cálculo de color.
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
■ 
■ 
■ 
■ 
No es necesario realizar todas las operaciones mencionadas en un solo
vertex shader, pero hay que tener en cuenta que cuando se usa un vertex
shader se remplaza toda la funcionalidad del procesador de vértices; por lo
tanto, no se puede esperar que la funcionalidad fija de OpenGL realice las
operaciones que no se programaron en el vertex shader: Cuando se usa un
vertex shader, éste se hace responsable por realizar toda la funcionalidad en
este punto de la línea de producción gráfica.
El procesador de vértices no posee información acerca de la conectividad y
por ello no se pueden realizar operaciones que requieren conocimiento
topológico. Por ejemplo, no se pueden realizar operaciones relacionadas a
caras/polígonos. El procesador de vértices procesa vértices individualmente
y no tiene idea sobre los otros vértices.
Un vertex shader necesita escribir por lo menos una variable: gl_Position,
la cual normalmente con el resultado de la transformación del vértice con
las matrices de modelview y proyección.
Un procesador e vértices tiene acceso al estado de OpenGL, por lo que
puede realizar operaciones que involucran iluminación y materiales.
También puede acceder a texturas. No se tiene acceso al framebuffer.
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
■ 
Los fragment shaders se ejecutan en el procesador de fragmentos, una unidad
responsable por operaciones como:
● 
● 
● 
● 
■ 
■ 
■ 
■ 
Cálculo de colores y coordenadas por píxel.
Aplicación de texturas.
Cálculo de neblina.
Cálculo de normales para iluminación por píxel.
Un fragment shader recibe como entrada los valores interpolados que han sido
calculados en una etapa anterior de la línea de producción grafica tales como las
posiciones de los vértices, colores o normales.
En un vertex shader estos valores se calculan por vértice, en un fragment shader se
trabaja con fragmentos dentro de las primitivas, por lo que se necesitan los valores
interpolados.
Al igual que en el caso de un vertex shader, un fragment shader substituye la
funcionalidad fija de OpenGL y en caso de utilizar un shader no se puede esperar
que OpenGL realice las operaciones que no se llevaron a cabo en el shader.
Un procesador de fragmentos opera sobre fragmentos individualmente y no tiene
idea sobre el resto de fragmentos. El fragment shader también tiene acceso al
estado de OpenGL y por lo tanto puede acceder a colores y materiales.
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
■ 
■ 
Un fragment shader NO puede cambiar la coordenada del píxel que
ya fue calculado previamente en la línea de producción. Un
fragment shader tiene acceso a las posiciones de los píxeles sobre la
pantalla pero no puede cambiarlas.
Un fragment shader puede producir una de dos opciones:
●  Descartar el fragmento, por lo que no se produce algo/nada.
●  Calcular gl_FragColor (el color final del fragmento) o gl_FragData
(cuando se hace rendering a varios objetivos).
■ 
■ 
La profundidad tambien puede ser modificada aunque típicamente
no es necesario ya que ha sido calculada “adecuadamente” en etapas
previas.
Un fragment shader no tiene acceso al framebuffer. Por lo tanto,
operaciones de mezcla de colores ocurren después de la ejecución
del fragment shader.
Édgar Garduño Ángeles!
C. Computación, I.I.M.A.S!
Descargar