The Book of Shaders by Patricio Gonzalez Vivo & Jen Lowe

Bahasa Indonesia - Tiếng Việt - 日本語 - 中文版 - 한국어 - Español - Portugues - Français - Italiano - Deutsch - Русский - Polski - English


Uniforms

Hasta ahora hemos visto como la GPU maneja grandes números de threads en paralelo, cada uno responsable de asignar un color a una fracción de la pantalla. A pesar de que cada thread no conoce a los otros, necesitamos poder enviarle valores de entrada desde la CPU a todos los threads. Debido a la arquitectura de la GPU todos esos valores van a ser iguales (uniform) para todos los threads y de sólo lectura. En otras palabras, cada thread recibe las misma información y puede leerla pero no modificarla.

Estas entradas se llaman uniform y vienen en diferentes tipos: float, vec2, vec3, vec4, mat2, mat3, mat4, sampler2D y samplerCube. Los uniforms son definidos con sus correspondientes tipos, al principio del código, luego de definir la precisión del punto flotante.

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution; // Canvas size (width,height)
uniform vec2 u_mouse;      // mouse position in screen pixels
uniform float u_time;     // Time in seconds since load

Podemos imaginar que los uniforms son como pequeños puentes entre la CPU y la GPU. Los nombres varían dependiendo de cada implementación, en esta serie de ejemplos estoy usando u_time (tiempo en segundos desde que shaders comenzó a correr), u_resolution (el tamaño de la ventana donde se está dibujando el shader) y u_mouse (la posición del mouse dentro de la ventana en pixeles). Estoy siguiendo la convención de utilizar u_ antes del nombre del uniform, para ser explícito respecto a la naturaleza de la variable, pero encontrarás diferentes nombre de uniforms. Por ejemplo ShaderToy.com utiliza las mismas uniforms pero con los siguientes nombres:

uniform vec3 iResolution;   // viewport resolution (in pixels)
uniform vec4 iMouse;        // mouse pixel coords. xy: current, zw: click
uniform float iTime;        // shader playback time (in seconds)

Ya hemos hablado mucho, vamos a ver los uniforms en acción. En el código siguiente usamos u_time - el número de segundos desde que el shader comenzó a ejecutarse - junto con una función del seno para animar en transición la cantidad de rojo en la pantalla.

Como puedes ver, GLSL tiene más sorpresas. La GPU tiene funciones de ángulo, de trigonometría y exponenciales, que son aceleradas por hardware: sin(), cos(), tan(), asin(), acos(), atan(), pow(), exp(), log(), sqrt(), abs(), sign(), floor(), ceil(), fract(), mod(), min(), max() y clamp().

Es hora de jugar con el código de arriba:

gl_FragCoord

De la misma forma que GLSL nos da por default la variable reservada vec4 gl_FragColor, también nos da vec4 gl_FragCoord que guarda la coordenada del pixel o screen fragment del thread actual. Con vec4 gl_FragCoord podemos saber el lugar en la pantalla en el que el thread está actualmente trabajando. En este caso, esta variable no es un uniform porque será diferente en cada uno de los threads, las variables que cambian en cada thread, como gl_FragCoord, son varying.

En el código de arriba normalizamos la coordenada del fragment, dividiéndolo por la resolución total de la ventana. Una vez que hicimos este proceso, la posición va de 0.0 a 1.0, lo que vuelve mucho más fácil de usar estos valores en los canales RED (rojo) y GREEN (verde).

En el mundo de los shaders no tenemos muchas herramientas para hacer debug más allá de asignar colores e intentar encontrarles el sentido. Muchas veces verás que programar en GLSL es como poner barcos dentro de botellas, cuanto más complicado, más hermoso y gratificante es.

Es hora de poner en práctica los conocimientos aprendidos.

Luego de hacer estos ejercicios seguramente te preguntarás que más puedes hacer con los superpoderes que los shaders te dan. En el próximo capítulo veremos como crear tus propios shaders en three.js, Processing y openFrameworks.