Introducción a la programación de sombreadores para diseños


WebGL ha existido durante mucho tiempo, se han escrito muchos artículos sobre sombreadores, hay una serie de lecciones. Pero en su mayor parte, son demasiado complicados para el diseñador de diseño. Es incluso mejor decir que cubren grandes cantidades de información que necesita el desarrollador del motor del juego en lugar del diseñador de diseño. Inmediatamente comienzan con la construcción de una escena compleja, una cámara, luz ... En un sitio normal, para crear un par de efectos con fotos, todo este conocimiento es redundante. Como resultado, las personas crean estructuras arquitectónicas muy complejas y escriben sombreadores largos y largos para acciones muy simples en esencia.


Todo esto provocó una introducción a los aspectos del trabajo con sombreadores que probablemente sean útiles para el diseñador de diseño para crear varios efectos 2D con imágenes en el sitio. Por supuesto, ajustado por el hecho de que ellos mismos son relativamente raramente utilizados en el diseño de interfaces. Haremos una plantilla de inicio en JS puro sin bibliotecas de terceros y consideraremos las ideas de crear algunos efectos populares basados ​​en el cambio de píxeles, que son difíciles de hacer en SVG, pero al mismo tiempo se implementan fácilmente usando sombreadores.


Se supone que el lector ya está familiarizado con el canvas , describe qué es WebGL y tiene un conocimiento mínimo de las matemáticas. Algunos puntos se describirán de manera simplista, no académica, para dar una comprensión práctica de las tecnologías para trabajar con ellos, y no una teoría completa de su cocina interior o términos para el aprendizaje. Hay libros inteligentes para esto.

Cabe señalar de inmediato que los editores integrados en el artículo de CodePen tienen la capacidad de influir en el rendimiento de lo que se hace en ellos. Entonces, antes de escribir un comentario de que algo se está desacelerando en su macbook, asegúrese de que el problema no provenga de ellos.


Ideas principales


¿Qué es un sombreador?


¿Qué es un sombreador de fragmentos? Este es esencialmente un pequeño programa. Se ejecuta para cada píxel en el anvas . Si tenemos un canvas tamaño 1000x500px, entonces este programa se ejecutará 500,000 veces, recibiendo cada vez como parámetros de entrada las coordenadas del píxel para el que se está ejecutando actualmente. Todo esto sucede en la GPU en una variedad de hilos paralelos. En el procesador central, tales cálculos tomarían mucho más tiempo.


Un sombreador de vértices también es un programa, pero no se ejecuta para cada píxel en el canvas , sino para cada vértice en las formas a partir de las cuales todo se construye en un espacio tridimensional. También paralelo a todos los vértices. En consecuencia, la entrada recibe las coordenadas del vértice, no el píxel.


Además en el contexto de nuestra tarea, ocurre lo siguiente:


  • Tomamos un conjunto de coordenadas de los vértices del rectángulo, sobre el cual la fotografía será "dibujada".
  • Un sombreador de vértices para cada vértice considera su ubicación en el espacio. Para nosotros, esto se reducirá a un caso especial: un plano paralelo a la pantalla. Fotos en 3d que no necesitamos. La proyección posterior en el plano de la pantalla no puede decir nada.
  • Además, para cada fragmento visible, y en nuestro contexto para todos los fragmentos de píxeles, se ejecuta un sombreador de fragmentos, toma una foto y las coordenadas actuales, cuenta algo y da color para este píxel en particular.
  • Si no había lógica en el sombreador de fragmentos, entonces el comportamiento de todo esto se drawImage() método drawImage() del canvas . Pero luego agregamos esta lógica y obtenemos muchas cosas interesantes.

Esta es una descripción muy simplificada, pero debe quedar claro quién hace qué.


Un poco sobre sintaxis


Los sombreadores están escritos en GLSL - OpenGL Shading Language. Este lenguaje es muy similar a C. No tiene sentido describir la sintasis completa y los métodos estándar aquí, pero siempre puede usar la hoja de trucos:


Spoiler con fotos





Cada sombreador tiene una función principal, con la que comienza su ejecución. Los parámetros de entrada estándar para sombreadores y la salida de los resultados de su trabajo se implementan a través de variables especiales con el prefijo gl_ . Se reservan por adelantado y están disponibles dentro de estos mismos sombreadores. Entonces, las coordenadas de vértice para el sombreador de vértices se encuentran en la variable gl_Position , las coordenadas de fragmento (píxel) para el sombreador de fragmentos se encuentran en gl_FragCoord , etc. Siempre puede encontrar la lista completa de variables especiales disponibles en la misma hoja de trucos.


Los principales tipos de variables en GLSL son bastante modestos: void , bool , int , float ... Si trabajó con algún lenguaje tipo C, ya los ha visto. Hay otros tipos, en particular vectores de diferentes dimensiones: vec2 , vec3 , vec4 . Los usaremos constantemente para coordenadas y colores. Las variables que podemos crear son de tres modificaciones importantes:


  • Uniforme : datos globales en todos los sentidos. Pasado desde el exterior, lo mismo para todas las llamadas de sombreadores de vértices y fragmentos.
  • Atributo : estos datos se transfieren con mayor precisión y para cada llamada de sombreador puede ser diferente.
  • Variar : necesario para transferir datos de sombreadores de vértices a sombreadores de fragmentos.

Es útil prefijar u / a / v a todas las variables en los sombreadores para facilitar la comprensión de qué datos provienen.

Creo que vale la pena pasar a un ejemplo práctico para ver de inmediato todo esto en acción y no cargar su memoria.


Plantilla de inicio de cocción


Comencemos con JS. Como suele suceder cuando se trabaja con el canvas , lo necesitamos y el contexto. Para no cargar el código de muestra, crearemos variables globales:


 const CANVAS = document.getElementById(IDs.canvas); const GL = canvas.getContext('webgl'); 

Omita el momento asociado con el tamaño del canvas y su recálculo al cambiar el tamaño de la ventana del navegador. Este código se incluye en los ejemplos y generalmente depende del resto del diseño. No tiene sentido concentrarse en él. Pasemos a las acciones con WebGL.


 function createProgram() { const shaders = getShaders(); PROGRAM = GL.createProgram(); GL.attachShader(PROGRAM, shaders.vertex); GL.attachShader(PROGRAM, shaders.fragment); GL.linkProgram(PROGRAM); GL.useProgram(PROGRAM); } 

Primero, compilamos los sombreadores (será un poco más bajo), creamos un programa, le agregamos nuestros dos sombreadores y hacemos un enlace. En este punto, se verifica la compatibilidad de los sombreadores. ¿Recuerdas las variables variables que se pasan del vértice al fragmento? - En particular, sus conjuntos se comprueban aquí para que más adelante en el proceso no resulte que algo no se ha transmitido o transmitido, pero no en absoluto. Por supuesto, esta verificación no revelará errores lógicos, creo que esto es comprensible.


Las coordenadas de los vértices se almacenarán en una matriz de búfer especial y se transmitirán en partes, un vértice, a cada llamada de sombreador. A continuación, describimos algunos detalles para trabajar con estas piezas. En primer lugar, utilizaremos las coordenadas del vértice en el sombreador a través de la a_position atributo a_position . Se le puede llamar de manera diferente, no importa. Obtenemos su ubicación (esto es algo así como un puntero en C, pero no un puntero, sino un número de entidad que existe solo dentro del programa).


 const vertexPositionAttribute = GL.getAttribLocation(PROGRAM, 'a_position'); 

A continuación, indicamos que se pasará una matriz con coordenadas a través de esta variable (en el sombreador mismo, ya la percibiremos como un vector). WebGL determinará de forma independiente qué coordenadas de qué puntos en nuestras formas deben pasarse a qué llamada de sombreador. Solo establecemos los parámetros para la matriz de vectores que se transmitirán: dimensión - 2 (transmitiremos las coordenadas (x,y) ), consta de números y no está normalizado. Los últimos parámetros no nos interesan, dejamos los ceros por defecto.


 GL.enableVertexAttribArray(vertexPositionAttribute); GL.vertexAttribPointer(vertexPositionAttribute, 2, GL.FLOAT, false, 0, 0); 

Ahora cree el búfer en sí con las coordenadas de los vértices de nuestro plano, en el que se mostrará la foto. Las coordenadas "2d" son más claras, pero para nuestras tareas esto es lo más importante.


 function createPlane() { GL.bindBuffer(GL.ARRAY_BUFFER, GL.createBuffer()); GL.bufferData( GL.ARRAY_BUFFER, new Float32Array([ -1, -1, -1, 1, 1, -1, 1, 1 ]), GL.STATIC_DRAW ); } 

Este cuadrado será suficiente para todos nuestros ejemplos. STATIC_DRAW significa que el búfer se carga una vez y luego se reutilizará. No volveremos a subir nada.


Antes de pasar a los sombreadores, veamos su compilación:


 function getShaders() { return { vertex: compileShader( GL.VERTEX_SHADER, document.getElementById(IDs.shaders.vertex).textContent ), fragment: compileShader( GL.FRAGMENT_SHADER, document.getElementById(IDs.shaders.fragment).textContent ) }; } function compileShader(type, source) { const shader = GL.createShader(type); GL.shaderSource(shader, source); GL.compileShader(shader); return shader; } 

Obtenemos el código de sombreador de los elementos de la página, creamos un sombreador y lo compilamos. En teoría, puede almacenar el código del sombreador en archivos separados y cargarlo durante el ensamblaje como una cadena en el lugar correcto, pero CodePen no brinda esa oportunidad para ejemplos. Muchas lecciones sugieren escribir código directamente en la línea en JS, pero el idioma no lo convierte en un idioma conveniente. Aunque, por supuesto, sabe y colorea ...


Si se produce un error durante la compilación, el script continuará ejecutándose mostrando un par de advertencias en la consola que no tienen mucho sentido. Es útil mirar los registros después de la compilación para no acumular cerebros sobre lo que no se compiló allí:


 console.log(GL.getShaderInfoLog(shader)); 

WebGL proporciona varias opciones diferentes para rastrear problemas al compilar sombreadores y crear un programa, pero en la práctica resulta que en tiempo real no podemos solucionar nada de todos modos. Muy a menudo nos guiará el pensamiento "se cayó - luego se cayó" y no cargaremos el código con un montón de comprobaciones adicionales.

Pasemos a los sombreadores mismos


Como solo tendremos un plano con el que no vamos a hacer nada, un simple sombreador de vértices es suficiente para nosotros, lo que haremos al principio. Los esfuerzos principales se centrarán en sombreadores de fragmentos y todos los ejemplos posteriores serán relevantes para ellos.


Intente escribir código de sombreador con nombres de variables más o menos significativos. En la red encontrará ejemplos en los que las funciones con matemáticas vigorosas para 200 líneas de texto continuo se ensamblarán a partir de variables de una letra, pero el hecho de que alguien lo haga no significa que valga la pena repetirlo. Tal enfoque no es una "especificidad de trabajar con GL", es una copia pegar banal de los códigos fuente del siglo pasado escritos por personas que en su juventud tenían restricciones en la longitud de los nombres variables.

Primero, el sombreador de vértices. Un vector 2d con coordenadas (x,y) se transferirá a la variable del atributo a_position , como dijimos. El sombreador debe devolver un vector de cuatro valores (x,y,z,w) . No moverá nada en el espacio, por lo que en el eje z, simplemente ponemos a cero todo y establecemos el valor de w en la unidad estándar. Si se pregunta por qué hay cuatro coordenadas en lugar de tres, puede usar la búsqueda de red para "coordenadas uniformes".


 <script id='vertex-shader' type='x-shader/x-vertex'> precision mediump float; attribute vec2 a_position; void main() { gl_Position = vec4(position, 0, 1); } </script> 

El resultado del trabajo se registra en una variable especial gl_Position . Los sombreadores no tienen una return en el sentido completo de la palabra, escriben todos los resultados de su trabajo en variables especialmente reservadas para estos fines.


Tenga en cuenta el trabajo de precisión para el tipo de datos flotante. Para evitar algunos de los problemas en los dispositivos móviles, la precisión debería ser peor que highp y debería ser la misma en ambos sombreadores. Esto se muestra como un ejemplo aquí, pero es una buena práctica en los teléfonos apagar por completo esa belleza con sombreadores.

Para empezar, el sombreador de fragmentos siempre devolverá el mismo color. Nuestro cuadrado ocupará todo el canvas , de modo que aquí establecemos el color para cada píxel:


 <script id='fragment-shader' type='x-shader/x-fragment'> precision mediump float; #define GOLD vec4(1.0, 0.86, 0.6, 1.0) void main() { gl_FragColor = GOLD; } </script> 

Puede prestar atención a los números que describen el color. Esto es familiar para todos los tipos de letra RGBA, solo normalizado. Los valores no son enteros de 0 a 255, sino fraccionarios de 0 a 1. El orden es el mismo.


No olvide usar el preprocesador para todas las constantes mágicas en proyectos reales; esto hace que el código sea más comprensible sin afectar el rendimiento (la sustitución, como en C, ocurre durante la compilación).

Vale la pena señalar otro punto sobre el preprocesador:


El uso de comprobaciones constantes #ifdef GL_ES en varias lecciones carece de significado práctico. En nuestro navegador actual, no existen otras opciones GL.

Pero es hora de mirar el resultado:



El cuadrado dorado indica que los sombreadores están funcionando como se esperaba. Tiene sentido jugar un poco con ellos antes de comenzar a trabajar con fotos.


Vectores gradientes y transformados


Por lo general, los tutoriales de WebGL comienzan dibujando degradados. Esto tiene poco sentido práctico, pero será útil tener en cuenta algunos puntos.


 void main() { gl_FragColor = vec4(gl_FragCoord.zxy / 500.0, 1.0); } 

En este ejemplo, usamos las coordenadas del píxel actual como el color. A menudo verá esto en ejemplos en la red. Ambos son vectores. Entonces nadie se molesta en mezclar todo en un montón. Los evangelistas de TypeScript deberían tener un ataque aquí. Un punto importante es cómo obtenemos solo una parte de las coordenadas del vector. Propiedades .x , .y , .z , .xy , .zy , .xyz , .zyx , .xyzw , etc. en diferentes secuencias le permiten extraer los elementos de un vector en un cierto orden en forma de otro vector. Muy convenientemente implementado. Además, se puede hacer un vector de mayor dimensión a partir de un vector de menor dimensión agregando los valores faltantes, como lo hicimos nosotros.


Siempre indique explícitamente la parte fraccionaria de los números. No hay conversión automática int -> flotante aquí.


Uniformes y el paso del tiempo.


El siguiente ejemplo útil es el uso de uniformes. Estos son los datos más comunes para todas las llamadas de sombreador. Obtenemos su ubicación de la misma manera que para las variables de atributo, por ejemplo:


 GL.getUniformLocation(PROGRAM, 'u_time') 

Entonces podemos establecerles valores antes de cada cuadro. Además de los vectores, hay muchos métodos similares aquí, comenzando con la palabra uniform , luego viene la dimensión de la variable (1 para números, 2, 3 o 4 para vectores) y tipo (f - float, i - int, v - vector) .


 function draw(timeStamp) { GL.uniform1f(GL.getUniformLocation(PROGRAM, 'u_time'), timeStamp / 1000.0); GL.drawArrays(GL.TRIANGLE_STRIP, 0, 4); window.requestAnimationFrame(draw); } 

De hecho, no siempre necesitamos 60 fps en las interfaces. Es bastante posible agregar una desaceleración para solicitar el marco de animación y reducir la frecuencia de los marcos de redibujado.

Por ejemplo, cambiaremos el color de relleno. En los sombreadores, todas las funciones matemáticas básicas están disponibles: sin , cos , tan , asin , acos , atan , pow , exp , log , sqrt , abs y otras. Utilizaremos dos de ellos.


 uniform float u_time; void main() { gl_FragColor = vec4( abs(sin(u_time)), abs(sin(u_time * 3.0)), abs(sin(u_time * 5.0)), 1.0); } 

El tiempo en tales animaciones es un concepto relativo. Aquí usamos los valores proporcionados por requestAnimationFrame , pero podemos hacer nuestro propio "tiempo". La idea es que si algunos parámetros se describen por una función del tiempo, entonces podemos girar el tiempo en la dirección opuesta, reducir la velocidad, acelerarlo o volver a su estado original. Esto puede ser muy útil.



Pero suficientes ejemplos abstractos, pasemos al uso de imágenes.


Cargando una imagen en una textura


Para usar la imagen, necesitamos crear una textura, que luego se renderizará en nuestro plano. Para comenzar, cargue la imagen en sí:


 function createTexture() { const image = new Image(); image.crossOrigin = 'anonymous'; image.onload = () => { // .... }; image.src = 'example.jpg'; } 

Después de que se cargue, cree una textura e indique que irá al número 0. En WebGL, puede haber muchas texturas al mismo tiempo y debemos indicar explícitamente a qué comandos posteriores se referirá. En nuestros ejemplos, solo habrá una textura, pero aún indicamos explícitamente que será cero.


 const texture = GL.createTexture(); GL.activeTexture(GL.TEXTURE0); GL.bindTexture(GL.TEXTURE_2D, texture); 

Queda por agregar una foto. También decimos de inmediato que debe voltearse a lo largo del eje Y, porque en WebGL, el eje está al revés:


 GL.pixelStorei(GL.UNPACK_FLIP_Y_WEBGL, true); GL.texImage2D(GL.TEXTURE_2D, 0, GL.RGB, GL.RGB, GL.UNSIGNED_BYTE, image); 

En teoría, la textura debería ser cuadrada. Más precisamente, incluso deberían tener un tamaño igual a la potencia de dos: 32px, 64px, 128px, etc. Pero todos entendemos que nadie procesará las fotos y estarán en diferentes proporciones cada vez. Esto provocará errores incluso si el tamaño del canvas ajusta perfectamente a la textura. Por lo tanto, llenamos todo el espacio hasta los bordes del plano con los píxeles extremos de la imagen. Esta es una práctica estándar, aunque parece una pequeña muleta.


 GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE); GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE); GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR); 

Queda por transferir la textura a los sombreadores. Estos datos son comunes a todos, por lo que utilizamos el modificador uniform .


 GL.uniform1i(GL.getUniformLocation(PROGRAM, 'u_texture'), 0); 

Ahora podemos usar los colores de la textura en el sombreador de fragmentos. Pero también queremos que la imagen ocupe todo el canvas . Si la imagen y el canvas tienen las mismas proporciones, esta tarea se vuelve trivial. Primero, transferimos el tamaño del canvas a los sombreadores (esto debe hacerse cada vez que cambie su tamaño):


 GL.uniform1f(GL.getUniformLocation(PROGRAM, 'u_canvas_size'), Math.max(CANVAS.height, CANVAS.width)); 

Y divide las coordenadas en él:


 uniform sampler2D u_texture; uniform float u_canvas_size; void main() { gl_FragColor = texture2D(u_texture, gl_FragCoord.xy / u_canvas_size); } 


En este punto, puedes pausar y preparar té. Hemos hecho todo el trabajo preparatorio y pasamos a crear varios efectos.


Efectos


Al crear varios efectos, la intuición y la experimentación juegan un papel importante. A menudo, puede reemplazar un algoritmo complejo con algo completamente simple y dar un resultado similar. El usuario final no notará la diferencia, pero aceleramos el trabajo y simplificamos el soporte. WebGL no proporciona herramientas razonables para depurar sombreadores, por lo que es beneficioso para nosotros tener pequeños fragmentos de código que puedan caber en la cabeza como un todo.


Menos código significa menos problemas. Y es más fácil de leer. Siempre revise los sombreadores encontrados en la red para acciones innecesarias. Sucede que puede eliminar la mitad del código y nada cambiará.

Juguemos un poco con el sombreador. La mayoría de nuestros efectos se basarán en el hecho de que devolvemos el color no del píxel en la textura que debería estar en este lugar, sino algunos de los vecinos. Es útil intentar agregar a las coordenadas el resultado de una función estándar de las coordenadas. El tiempo también será útil para usar, por lo que el resultado de la ejecución será más fácil de rastrear y, al final, aún crearemos efectos animados. Intentemos usar el seno:


 gl_FragColor = texture2D(u_texture, gl_FragCoord.xy / u_canvas_size + sin(u_time + gl_FragCoord.y)); 

El resultado es extraño. Obviamente, todo se mueve con demasiada amplitud. Divide todo por algún número:


 gl_FragColor = texture2D(u_texture, gl_FragCoord.xy / u_canvas_size + sin(u_time + gl_FragCoord.y) / 250.0); 

Ya mejor. Ahora está claro que tenemos un poco de emoción. En teoría, para aumentar cada onda, necesitamos dividir el argumento seno: la coordenada. Hagámoslo:


 gl_FragColor = texture2D(u_texture, gl_FragCoord.xy / u_canvas_size + sin(u_time + gl_FragCoord.y / 30.0) / 250.0); 


Efectos similares a menudo van acompañados de la selección de coeficientes. Esto se hace a simple vista. Al igual que con la cocina, al principio será difícil de adivinar, pero luego sucederá por sí solo. Lo principal es comprender al menos aproximadamente a qué afecta este o aquel coeficiente en la fórmula resultante. Después de seleccionar los coeficientes, tiene sentido ponerlos en macros (como fue el primer ejemplo) y dar nombres significativos.


Espejo torcido, bicicletas y experimentos.


Pensar es bueno. Sí, hay algoritmos listos para resolver algunos problemas que podemos tomar y usar. , .


, " ", . Que hacer


, , ? . , rand() - . , , , , . . . , . . . -, . . , , , . , "":


 float rand(vec2 seed) { return fract(sin(dot(seed, vec2(12.9898,78.233))) * 43758.5453123); } 

, , , NVIDIA ATI . , .


, , :


 gl_FragColor = texture2D(u_texture, gl_FragCoord.xy / u_canvas_size + rand(gl_FragCoord.xy) / 100.0); 

:


 gl_FragColor = texture2D(u_texture, gl_FragCoord.xy / u_canvas_size + rand(gl_FragCoord.xy + vec2(sin(u_time))) / 250.0); 

, , :



, . , , . — . Como hacerlo . .


0 1, - . 5 — . , .


 vec2 texture_coord = gl_FragCoord.xy / u_canvas_size; gl_FragColor = texture2D(u_texture, texture_coord + rand(floor(texture_coord * 5.0) + vec2(sin(u_time))) / 100.0); 

, - . - . , , . ?


, , , - . , . , .. -. , . . , , . .


sin cos , . . .


 gl_FragColor = texture2D(u_texture, texture_coord + vec2( noise(texture_coord * 10.0 + sin(u_time + texture_coord.x * 5.0)) / 10.0, noise(texture_coord * 10.0 + cos(u_time + texture_coord.y * 5.0)) / 10.0)); 

. fract . 1 1 — :


 float noise(vec2 position) { vec2 block_position = floor(position); float top_left_value = rand(block_position); float top_right_value = rand(block_position + vec2(1.0, 0.0)); float bottom_left_value = rand(block_position + vec2(0.0, 1.0)); float bottom_right_value = rand(block_position + vec2(1.0, 1.0)); vec2 computed_value = fract(position); // ... } 

. WebGL smoothstep , :


 vec2 computed_value = smoothstep(0.0, 1.0, fract(position)); 

, . , X :


 return computed_value.x; 


… , , ...


- , , ... .

y — , . ?


 return length(computed_value); 


.


. 0.5 — .


 return mix(top_left_value, top_right_value, computed_value.x) + (bottom_left_value - top_left_value) * computed_value.y * (1.0 - computed_value.x) + (bottom_right_value - top_right_value) * computed_value.x * computed_value.y - 0.5; 

:



, , , .



, , . - .


uniform-, . 0 1, 0 — , 1 — .


 uniform float u_intensity; 

:


 gl_FragColor = texture2D(u_texture, texture_coord + vec2(noise(texture_coord * 10.0 + sin(u_time + texture_coord.x * 5.0)) / 10.0, noise(texture_coord * 10.0 + cos(u_time + texture_coord.y * 5.0)) / 10.0) * u_intensity); 

, .



( 0 1), .


, , , . — requestAnimationFrame. , FPS.


, . uniform-.


 document.addEventListener('mousemove', (e) => { let rect = CANVAS.getBoundingClientRect(); MOUSE_POSITION = [ e.clientX - rect.left, rect.height - (e.clientY - rect.top) ]; GL.uniform2fv(GL.getUniformLocation(PROGRAM, 'u_mouse_position'), MOUSE_POSITION); }); 

, . — , .


 void main() { vec2 texture_coord = gl_FragCoord.xy / u_canvas_size; vec2 direction = u_mouse_position / u_canvas_size - texture_coord; float dist = distance(gl_FragCoord.xy, u_mouse_position) / u_canvas_size; if (dist < 0.4) { gl_FragColor = texture2D(u_texture, texture_coord + u_intensity * direction * dist * 1.2 ); } else { gl_FragColor = texture2D(u_texture, texture_coord); } } 

- . .


. , .



. Glitch- , SVG. . — . ? — , , , .


 float random_value = rand(vec2(texture_coord.y, u_time)); if (random_value < 0.05) { gl_FragColor = texture2D(u_texture, vec2(texture_coord.x + random_value / 5.0, texture_coord.y)); } else { gl_FragColor = texture2D(u_texture, texture_coord); } 

" ?" — , . .

. — , .


 float random_value = rand(vec2(floor(texture_coord.y * 20.0), u_time)); 

. , :


 gl_FragColor = texture2D(u_texture, vec2(texture_coord.x + random_value / 4.0, texture_coord.y)) + vec4(vec3(random_value), 1.0); 

. — . , — .r , .g , .b , .rg , .rb , .rgb , .bgr , ... .


:


 float random_value = u_intensity * rand(vec2(floor(texture_coord.y * 20.0), u_time)); 


Cual es el resultado?


, , . , , — .

Source: https://habr.com/ru/post/es420847/


All Articles