Nous utilisons la mosaïque, la pixellisation et les masques géométriques de Voronoi dans les shaders pour décorer le site

image

Cet article est une suite logique de l' introduction des shaders de programmation pour les concepteurs de mise en page . Dans ce document, nous avons créé un modèle pour créer divers effets bidimensionnels avec des photos à l'aide de shaders et regardé quelques exemples. Dans cet article, nous ajouterons quelques textures supplémentaires, appliquerons le fractionnement de Voronoi en pratique pour créer des mosaïques à partir de celles-ci, parlerons de la création de divers masques dans les shaders, de la pixellisation et aborderons également certains problèmes de l'ancienne syntaxe GLSL qui existe toujours dans nos navigateurs.


Tout comme la dernière fois, il y aura un minimum de théorie et un maximum de pratique et de raisonnement dans un langage banal de tous les jours. Les débutants trouveront ici une séquence d'actions avec des conseils et des notes utiles, et les vendeurs expérimentés peuvent trouver quelques idées d'inspiration.


Une enquête dans un article précédent a montré que le sujet des effets WebGL pour les sites peut intéresser non seulement les typographes, mais aussi nos collègues d'autres spécialisations. Afin de ne pas les dérouter avec les dernières fonctionnalités ES, nous nous limitons délibérément à des constructions de syntaxe plus traditionnelles que tout le monde comprend. Et encore une fois, j'attire l'attention des lecteurs sur le fait que les éditeurs intégrés de CodePen affectent les performances de ce qui est fait en eux.


Mais commençons ...


Modèle pour travailler avec des shaders


Pour ceux qui n'ont pas lu l'article précédent, nous avons créé ce modèle pour travailler avec des shaders:



Un plan y est créé (dans notre cas, un carré) sur lequel la texture de l'image est "dessinée". Pas de dépendances inutiles et un vertex shader très simple. Ensuite, nous avons développé ce modèle, mais maintenant nous allons commencer à partir du moment où il n'y a pas encore de logique dans le shader de fragment.


Mosaïque


La mosaïque est un plan divisé en petites zones, où chacune des zones est remplie d'une certaine couleur ou, comme dans notre cas, d'une texture. Comment pouvons-nous même casser notre avion en morceaux? Évidemment, vous pouvez le diviser en rectangles. Mais c'est déjà si facile à faire avec l'aide de SVG, de faire glisser WebGL vers cette tâche et de tout mettre à l'improviste sur absolument rien.


Pour que la mosaïque soit intéressante, elle doit avoir différents fragments, à la fois en forme et en taille. Il existe une approche très simple, mais en même temps très divertissante pour construire une telle partition. Il est connu comme la mosaïque de Voronoï ou la partition de Dirichlet, et sur Wikipédia, il est écrit que Descartes a utilisé quelque chose de similaire au XVIIe siècle lointain. L'idée est quelque chose comme ça:


  • Prenez un ensemble de points dans l'avion.
  • Pour chaque point de l'avion, recherchez le point le plus proche de cet ensemble.
  • C'est tout. Le plan est divisé en zones polygonales, chacune étant déterminée par l'un des points de l'ensemble.

Il est probablement préférable de montrer ce processus avec un exemple pratique. Il existe différents algorithmes pour générer cette partition, mais nous agirons sur le front, car calculer quelque chose pour chaque point sur le plan est juste la tâche du shader. Nous devons d'abord créer un ensemble de points aléatoires. Afin de ne pas charger le code des exemples, nous allons leur faire une variable globale.


function createPoints() { for (let i = 0; i < NUMBER_OF_POINTS; i++) { POINTS.push([Math.random(), Math.random()]); } } 

Maintenant, nous devons les transmettre aux shaders. Les données sont globales, nous allons donc utiliser le modificateur uniform . Mais il y a un point subtil: nous ne pouvons pas simplement passer un tableau. Il semblerait que le XXIe siècle soit dans la cour, mais néanmoins rien n'en sortira. Par conséquent, vous devez transférer un tableau de points un par un.


 for (let i = 0; i < NUMBER_OF_POINTS; i++) { GL.uniform2fv(GL.getUniformLocation(PROGRAM, 'u_points[' + i + ']'), POINTS[i]); } 

Aujourd'hui, nous rencontrerons souvent des problèmes similaires d'incohérence entre ce qui est attendu et ce qui se trouve dans les vrais navigateurs. Habituellement, les leçons WebGL utilisent THREE.js et cette bibliothèque cache une partie de la saleté en elle-même, comme jQuery l'a fait dans ses tâches, mais si vous la supprimez, cela fait vraiment mal à votre cerveau.


Dans le shader de fragment, nous avons une variable de tableau pour les points. Nous ne pouvons créer que des tableaux d'une longueur fixe. Commençons par 10 points:


 #define NUMBER_OF_POINTS 10 uniform vec2 u_points[NUMBER_OF_POINTS]; 

Assurez-vous que tout cela fonctionne en dessinant des cercles à la place des points. Un tel dessin de diverses primitives géométriques est souvent utilisé pendant le débogage - elles sont clairement visibles et vous pouvez immédiatement comprendre ce qui se trouve et où il se déplace.


Utilisez le "dessin" de cercles, de lignes et d'autres points de repère pour les objets invisibles sur lesquels les animations sont construites. Cela donnera des indices évidents sur la façon dont ils fonctionnent, surtout si les algorithmes sont complexes à comprendre rapidement sans préparation préalable. Ensuite, tout cela peut être commenté et laissé à des collègues - ils diront merci.

 for (int i = 0; i < NUMBER_OF_POINTS; i++) { if (distance(texture_coord, u_points[i]) < 0.02) { gl_FragColor = WHITE; break; } } 


Bon. Ajoutons également du mouvement aux points. Laissez-les se déplacer en cercle pour commencer, puis nous reviendrons sur cette question plus tard. Les coefficients sont également mis sur l'œil, juste pour ralentir légèrement leur mouvement et réduire l'amplitude des oscillations.


 function movePoints(timeStamp) { if (timeStamp) { for (let i = 0; i < NUMBER_OF_POINTS; i++) { POINTS[i][0] += Math.sin(i * timeStamp / 5000.0) / 500.0; POINTS[i][1] += Math.cos(i * timeStamp / 5000.0) / 500.0; } } } 

Retournez au shader. Pour de futures expériences, nous trouverons un nombre utile de zones dans lesquelles tout sera divisé. Nous trouvons donc le point le plus proche du pixel actuel de l'ensemble et enregistrons le numéro de ce point - c'est le numéro de la zone.


 float min_distance = 1.0; int area_index = 0; for (int i = 0; i < NUMBER_OF_POINTS; i++) { float current_distance = distance(texture_coord, u_points[i]); if (current_distance < min_distance) { min_distance = current_distance; area_index = i; } } 

Pour tester les performances, nous peignons à nouveau tout dans des couleurs vives:


 gl_FragColor = texture2D(u_texture, texture_coord); gl_FragColor.g = abs(sin(float(area_index))); gl_FragColor.b = abs(sin(float(area_index))); 

La combinaison de module (abs) et de fonctions limitées (en particulier sin et cos) est souvent utilisée lorsque l'on travaille avec des effets similaires. D'une part, cela ajoute un peu de hasard, et d'autre part, cela donne immédiatement un résultat normalisé de 0 à 1, ce qui est très pratique - nous avons de très nombreuses valeurs qui se situent précisément dans ces limites.

Nous trouverons également des points plus ou moins équidistants de plusieurs points de l'ensemble, et les colorierons. Cette action ne porte pas de charge utile spéciale, mais regarder le résultat est toujours intéressant.


 int number_of_near_points = 0; for (int i = 0; i < NUMBER_OF_POINTS; i++) { if (distance(texture_coord, u_points[i]) < min_distance + EPSILON) { number_of_near_points++; } } if (number_of_near_points > 1) { gl_FragColor.rgb = vec3(1.0); } 

Vous devriez obtenir quelque chose comme ceci:



Ceci est encore un projet, nous allons encore le finaliser. Mais maintenant, le concept général d'une telle séparation de l'avion est clair.


Mosaïque à partir de photos


Il est clair que dans sa forme pure, il n'y a pas beaucoup d'avantages d'une telle partition. Pour élargir vos horizons et juste pour le plaisir, vous pouvez jouer avec lui, mais sur un vrai site, il vaudrait la peine d'ajouter quelques photos de plus et d'en faire une mosaïque. Refaisons un peu la fonction de création de textures, pour qu'il y en ait plus d'une.


 function createTextures() { for (let i = 0; i < URLS.textures.length; i++) { createTexture(i); } } function createTexture(index) { const image = new Image(); image.crossOrigin = 'anonymous'; image.onload = () => { const texture = GL.createTexture(); GL.activeTexture(GL['TEXTURE' + index]); GL.bindTexture(GL.TEXTURE_2D, texture); GL.pixelStorei(GL.UNPACK_FLIP_Y_WEBGL, true); GL.texImage2D(GL.TEXTURE_2D, 0, GL.RGB, GL.RGB, GL.UNSIGNED_BYTE, image); 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); GL.uniform1i(GL.getUniformLocation(PROGRAM, 'u_textures[' + index + ']'), index); }; image.src = URLS.textures[index]; } 

Rien d'inhabituel ne s'est produit, nous venons de remplacer les zéros par le paramètre index et de réutiliser le code existant pour charger les trois textures. Dans le shader, nous avons maintenant un tableau de textures:


 #define NUMBER_OF_TEXTURES 3 uniform sampler2D u_textures[NUMBER_OF_TEXTURES]; 

Maintenant, nous pouvons utiliser le numéro de zone enregistré précédemment afin de sélectionner l'une des trois textures. Mais ...


Mais avant cela, je voudrais faire une petite digression. A propos de la plaie. À propos de la syntaxe. Le Javascript moderne (conditionnellement ES6 +) est une langue agréable. Il vous permet d'exprimer vos pensées au fur et à mesure qu'elles surviennent, ne limite le cadre à aucun paradigme de programmation spécifique, complète certains points pour nous et vous permet de vous concentrer davantage sur l'idée que sur sa mise en œuvre. Pour le créateur - c'est tout. Certaines personnes pensent qu'il donne trop de liberté et passent par exemple à TypeScript. Pure C est un langage plus rigoureux. Il permet également beaucoup, vous pouvez attirer n'importe quoi dessus, mais après JS, il est perçu comme un peu maladroit, démodé ou quelque chose. Néanmoins, il est toujours bon. GLSL tel qu'il existe dans les navigateurs est juste quelque chose. Non seulement c'est un ordre de grandeur plus strict que C, mais il manque encore de nombreux opérateurs et constructions de syntaxe familiers. C'est probablement le plus gros problème lors de l'écriture de shaders plus ou moins complexes pour WebGL. Derrière l'horreur que le code se transforme, il peut être très difficile de jeter un œil à l'algorithme d'origine. Certains codeurs pensent que jusqu'à ce qu'ils apprennent le C, le chemin vers les shaders est fermé pour eux. Donc: la connaissance de C ne sera pas particulièrement utile ici. Voici une sorte de monde qui lui est propre. Le monde de la folie, des dinosaures et des béquilles.


Comment puis-je choisir l'une des trois textures ayant un numéro - le numéro de zone. Le reste me vient à l'esprit en divisant le nombre par le nombre de textures. Excellente idée. Seul l'opérateur % , que les mains écrivent déjà, n'est pas là. L'impression de comprendre ce fait est bien décrite par l'image:


image


Bien sûr, vous dites: "Oui, pas de problème, il y a une fonction mod - prenons-la!". Mais il s'avère qu'elle n'accepte pas deux entiers, seulement des entiers. D'accord, faites-en un float . Nous obtenons également un float , mais nous avons besoin d'un int . Vous devez tout reconvertir, sinon il y a une chance non fausse d'obtenir une erreur de compilation.


 int texture_index = int(mod(float(area_index), float(NUMBER_OF_TEXTURES))); 

Et voici une question rhétorique: peut-être sera-t-il plus facile de réaliser sa fonction du reste de la division entière que d'essayer de l'assembler à partir de méthodes standard? Et ceci est encore une fonction simple, et il arrive que des séquences très profondément intégrées de telles transformations soient obtenues dans lesquelles il n'est plus clair de ce qui se passe.


Bon, laissons tel quel pour l'instant. Prenez simplement la couleur du pixel souhaité dans la texture sélectionnée et affectez-la à la variable gl_FragColor . Alors? Avons-nous déjà fait cela? Et puis ce chat réapparaît. Vous ne pouvez pas utiliser une non constante lors de l'accès à un tableau. Et tout ce que nous avons calculé n'est plus une constante. Ba-dum-tsss !!!


Vous devez faire quelque chose comme ça:


 if (texture_index == 0) { gl_FragColor = texture2D(u_textures[0], texture_coord); } else if (texture_index == 1) { gl_FragColor = texture2D(u_textures[1], texture_coord); } else if (texture_index == 2) { gl_FragColor = texture2D(u_textures[2], texture_coord); } 

D'accord, un tel code est un chemin direct vers govnokod.ru , mais néanmoins, il est différent de quelque façon que ce soit. Même l' switch-case n'est pas là pour au moins en quelque sorte ennoblir cette honte. Il existe vraiment une autre béquille moins évidente qui résout le même problème:


 for (int i = 0; i < 3; i++) { if (texture_index == i) { gl_FragColor = texture2D(u_textures[i], texture_coord); } } 

Compteurs de cycles, qui augmentent d'une unité, le compilateur peut compter comme une constante. Mais cela n'a pas fonctionné avec un tableau de textures - dans le dernier Chrome, une erreur est apparue disant qu'il était impossible de le faire avec un tableau de textures. Avec un tableau de nombres, cela a fonctionné. Devinez pourquoi cela fonctionne avec un tableau, mais pas avec un autre? Si vous pensiez que le système de fonte de type dans JS était plein de magie - triez le système "constant - pas constant" dans GLSL. Le plus drôle est que les résultats dépendent également de la carte vidéo utilisée, donc les béquilles délicates qui fonctionnaient sur la carte graphique NVIDIA peuvent très bien tomber en panne sur AMD.


Il vaut mieux éviter de telles décisions basées sur des hypothèses sur le compilateur. Ils ont tendance à se casser et sont difficiles à tester.

La tristesse est la tristesse. Mais, si nous voulons faire des choses intéressantes, nous devons nous abstenir de tout cela et continuer.


Pour le moment, nous avons obtenu une mosaïque de photos. Mais il y a un détail: si les points sont très proches les uns des autres, alors il y a une transition rapide de deux domaines. Ce n'est pas très joli. Vous devez ajouter un algorithme qui ne permet pas aux points de se rapprocher. Vous pouvez faire une option simple, dans laquelle les distances entre les points sont vérifiées et, si elle est inférieure à une certaine valeur, nous les écartons. Cette option n'est pas sans inconvénients, en particulier, elle conduit parfois à un peu de contraction des points, mais dans de nombreux cas, elle peut être suffisante, d'autant plus qu'il n'y a pas beaucoup de calculs ici. Des options plus avancées seraient un système de charges mobiles et une "toile d'araignée" dans laquelle des paires de points sont reliées par des ressorts invisibles. Si vous êtes intéressé à les mettre en œuvre, vous pouvez facilement trouver toutes les formules dans le livre de référence sur la physique pour le lycée.


 for (let i = 0; i < NUMBER_OF_POINTS; i++) { for (let j = i; j < NUMBER_OF_POINTS; j++) { let deltaX = POINTS[i][0] - POINTS[j][0]; let deltaY = POINTS[i][1] - POINTS[j][1]; let distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (distance < 0.1) { POINTS[i][0] += 0.001 * Math.sign(deltaX); POINTS[i][1] += 0.001 * Math.sign(deltaY); POINTS[j][0] -= 0.001 * Math.sign(deltaX); POINTS[j][1] -= 0.001 * Math.sign(deltaY); } } } 

Le principal problème avec cette approche, ainsi que celle que nous avons utilisée dans le shader, est de comparer tous les points avec tous. Vous n'avez pas besoin d'être un grand mathématicien pour comprendre que le nombre de calculs de distance sera incroyable si nous ne faisons pas 10 points, mais 1000. Oui, même 100 suffisent pour que tout ralentisse. Par conséquent, il est logique de l'appliquer uniquement pour un petit nombre de points.


Si nous voulons faire une telle mosaïque pour un grand nombre de points, alors nous pouvons utiliser la division familière du plan en carrés identiques. L'idée est de mettre un point dans chaque carré, puis d'effectuer toutes les comparaisons uniquement avec les points des carrés voisins. Une bonne idée, mais des expériences ont montré qu'avec un grand nombre de points, les ordinateurs portables bon marché avec des cartes vidéo intégrées ne peuvent toujours pas faire face. Par conséquent, il vaut la peine de réfléchir dix fois avant de décider de faire une telle mosaïque sur votre site à partir d'un grand nombre de fragments.


Ne soyez pas radis, vérifiez les performances de votre artisanat non seulement sur votre ferme minière, mais aussi sur des ordinateurs portables ordinaires. Ce sont essentiellement les utilisateurs.


Partitionnement d'un plan selon un graphe de fonction


Voyons une autre option pour diviser un plan en parties. Il n'aura plus besoin d'une grande puissance de calcul. L'idée principale est de prendre une fonction mathématique et de construire son graphique. La ligne résultante divisera simplement l'avion en deux parties. Si nous utilisons une fonction de la forme y = f(x) , nous obtenons la division sous la forme d'une coupe. En remplaçant X par Y, nous pouvons changer la section horizontale en verticale. Si vous prenez la fonction en coordonnées polaires, vous devez tout traduire en cartésien et vice versa, mais l'essence des calculs ne changera pas. Dans ce cas, le résultat n'est pas une coupe en deux parties, mais plutôt une coupe de trou. Mais nous verrons la première option.


Pour chaque Y, nous calculerons la valeur de X pour faire une coupe verticale. Nous pourrions prendre une onde sinusoïdale à ces fins, par exemple, mais c'est trop ennuyeux. Il est préférable de prendre quelques morceaux à la fois et de les plier.


Nous prenons plusieurs sinusoïdes, dont chacune est liée à une coordonnée le long de Y et au temps, et les ajoutons. Les physiciens appellent cette superposition d'addition. Évidemment, en multipliant le résultat entier par un certain nombre, nous changeons l'amplitude. Retirez-le dans une macro distincte. Si vous multipliez les coordonnées - le paramètre sinus, la fréquence changera. Nous l'avons déjà vu dans un article précédent. Nous supprimons également le modificateur de fréquence commun à toutes les sinusoïdes de la formule. Il ne sera pas superflu de jouer avec le temps, un signe négatif donnera l'effet de déplacer la ligne dans la direction opposée.


 float time = u_time * SPEED; float x = (sin(texture_coord.y * FREQUENCY) + sin(texture_coord.y * FREQUENCY * 2.1 + time) + sin(texture_coord.y * FREQUENCY * 1.72 + time * 1.121) + sin(texture_coord.y * FREQUENCY * 2.221 + time * 0.437) + sin(texture_coord.y * FREQUENCY * 3.1122 + time * 4.269)) * AMPLITUDE; 

Après avoir fait de tels réglages globaux pour notre fonction, nous serons confrontés au problème de répéter le même mouvement à des intervalles assez courts. Pour résoudre ce problème, nous devons tout multiplier par des coefficients pour lesquels le plus petit multiple commun est très grand. Quelque chose de similaire est également utilisé dans le générateur de nombres aléatoires, rappelez-vous? Dans ce cas, nous n'avons pas réfléchi et avons pris des chiffres prêts à l'emploi à partir d'un exemple provenant d'Internet, mais personne ne prend la peine d'expérimenter nos valeurs.


Il ne reste plus qu'à choisir l'une des deux textures pour les points au-dessus de notre graphique de fonction et la seconde pour les points en dessous. Plus précisément à gauche et à droite, nous avons tous tourné:


 if (texture_coord.x - 0.5 > x) { gl_FragColor = texture2D(u_textures[0], texture_coord); } else { gl_FragColor = texture2D(u_textures[1], texture_coord); } 

Ce que nous avons reçu ressemble à des ondes sonores. Plus précisément, leur image sur l'oscilloscope. En effet, nous pourrions au lieu de nos sinusoïdes transmettre des données à partir d'une sorte de fichier son. Mais travailler avec le son est un sujet pour un article séparé.



Masques


Les exemples précédents devraient conduire à une remarque assez logique: tout cela ressemble au travail des masques en SVG (si vous n'avez pas travaillé avec eux, voir les exemples de l'article Masques SVG et effets wow ). C'est juste qu'ici nous les faisons un peu différemment. Et le résultat est le même: certaines zones sont peintes avec une texture, d'autres avec une autre. Seules les transitions en douceur ne l'ont pas encore été. Alors faisons-en un.


Nous supprimons tous les inutiles et retournons les coordonnées de la souris. Faites un dégradé radial avec le centre à l'emplacement du curseur et utilisez-le comme masque. Dans cet exemple, le comportement du shader ressemblera plus à la logique des masques en SVG que dans les exemples précédents. Nous avons besoin d'une fonction de mix et d'une fonction de distance. Le premier mélangera les valeurs de couleur des pixels des deux textures, en prenant comme troisième paramètre un coefficient (de 0 à 1) qui détermine laquelle des valeurs prévaudra en conséquence. Nous prenons le module sinus en fonction de la distance - cela donnera juste un changement en douceur de la valeur entre 0 et 1.


 gl_FragColor = mix( texture2D(u_textures[0], texture_coord), texture2D(u_textures[1], texture_coord), abs(sin(length(texture_coord - u_mouse_position / u_canvas_size)))); 

C'est tout. Regardons le résultat:



Le principal avantage sur SVG est évident:


Contrairement à SVG, ici, nous pouvons facilement créer des gradients lisses pour diverses fonctions mathématiques, et non les collecter à partir de nombreux gradients linéaires.

Si vous avez une tâche plus simple qui ne nécessite pas de transitions fluides ou de formes complexes calculées dans le processus, il sera probablement plus facile à mettre en œuvre sans utiliser de shaders. Oui, et les performances sur un matériel faible devraient être meilleures. Choisissez un outil en fonction de vos tâches.


À des fins éducatives, voyons un autre exemple. Tout d'abord, faites un cercle dans lequel la texture restera telle qu'elle est:


 gl_FragColor = texture2D(u_textures[0], texture_coord); float dist = distance(texture_coord, u_mouse_position / u_canvas_size); if (dist < 0.3) { return; } 

Et remplissez le reste de rayures diagonales:


 float value = sin((texture_coord.y - texture_coord.x) * 200.0); if (value > 0.0) { gl_FragColor.rgb *= dist; } else { gl_FragColor.rgb *= dist / 10.0; } 

Les acceptations sont les mêmes - on multiplie le paramètre du sinus pour augmenter la fréquence des rayures; divisez les valeurs obtenues en deux parties; pour chacune des moitiés, nous transformons la couleur des pixels à notre manière. Il est utile de se rappeler que dessiner des lignes diagonales est généralement associé à l'ajout de coordonnées en X et Y. Notez que nous utilisons également la distance au curseur de la souris lors du changement de couleurs, créant ainsi une sorte d'ombre. De la même manière, vous pouvez l'utiliser avec des transformations géométriques, nous le verrons bientôt sur l'exemple de la pixellisation. En attendant, jetez un œil au résultat de ce shader:



Simple et joli.


Et oui, si vous êtes un peu confus, vous pouvez créer des textures non pas à partir d'images, mais à partir d'images de vidéos (il existe de nombreux exemples sur le réseau, vous pouvez facilement les comprendre) et leur appliquer tous nos effets. De nombreux sites d'annuaire comme Awwwards utilisent ces effets conjointement avec la vidéo.

Il convient de rappeler une dernière pensée:


Personne ne prend la peine d'utiliser l'une des textures comme masque. Nous pouvons prendre une photo et utiliser les valeurs de couleur de ses pixels dans nos transformations, que ce soit des changements dans d'autres couleurs, des décalages sur les côtés ou autre chose qui vous vient à l'esprit.

Mais revenons à diviser l'avion en plusieurs parties.


Pixélisation


Cet effet est quelque peu évident, mais en même temps, il est si courant qu'il serait faux de passer. Divisez notre avion en carrés, de la même manière que dans l'exemple avec le générateur de bruit, puis pour tous les pixels à l'intérieur de chaque carré, nous définissons la même couleur. Il est obtenu en mélangeant les valeurs des coins d'un carré, nous avons déjà fait quelque chose de similaire. Pour cet effet, nous n'avons pas besoin de formules complexes, alors additionnez simplement toutes les valeurs et divisez par 4 - le nombre d'angles du carré.


 float block_size = abs(sin(u_time)) / 20.0; vec2 block_position = floor(texture_coord / block_size) * block_size; gl_FragColor = ( texture2D(u_textures[0], block_position) + texture2D(u_textures[0], block_position + vec2(1.0, 0.0) * block_size) + texture2D(u_textures[0], block_position + vec2(0.0, 1.0) * block_size) + texture2D(u_textures[0], block_position + vec2(1.0, 1.0) * block_size) ) / 4.0; 

Nous avons à nouveau lié l'un des paramètres au temps dans le module sinus pour voir visuellement ce qui se passe quand il change.



Ondes de pixels


, .


 float block_size = abs(sin( length(texture_coord - u_mouse_position / u_canvas_size) * 2.0 - u_time)) / 100.0 + 0.001; 

, 0 1; , , , . , .



"" , , -. . " ", , . . — . .


Résumé


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




PS: , WebGL ( ) ? , , . ?

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


All Articles