Lorsque vous travaillez avec des actifs polygonaux, vous ne pouvez dessiner qu'un seul objet à la fois (si vous ne prenez pas en compte des techniques telles que le traitement par lots et l'instanciation), mais si vous utilisez des champs de distance avec un signe (champs de distance signés, SDF), nous ne sommes pas limités à cela. Si deux positions ont la même coordonnée, les fonctions de distance signées renverront la même valeur, et dans un calcul, nous pouvons obtenir plusieurs chiffres. Pour comprendre comment transformer l'espace utilisé pour générer des champs de distance signée, je vous recommande de
comprendre comment
créer des formes à l'aide des fonctions de distance signée et
combiner des formes sdf .
La configuration
Pour ce tutoriel, je modifie l'association entre le carré et le cercle, mais vous pouvez l'utiliser pour n'importe quelle autre forme. Ceci est similaire à la configuration du
didacticiel précédent .
Il est important ici que la partie modifiable soit avant d'utiliser des positions pour générer des figures.
Shader "Tutorial/036_SDF_Space_Manpulation/Type"{ Properties{ _InsideColor("Inside Color", Color) = (.5, 0, 0, 1) _OutsideColor("Outside Color", Color) = (0, .5, 0, 1) _LineDistance("Mayor Line Distance", Range(0, 2)) = 1 _LineThickness("Mayor Line Thickness", Range(0, 0.1)) = 0.05 [IntRange]_SubLines("Lines between major lines", Range(1, 10)) = 4 _SubLineThickness("Thickness of inbetween lines", Range(0, 0.05)) = 0.01 } SubShader{
Et la fonction 2D_SDF.cginc située dans le même dossier avec le shader, que nous allons développer, ressemble d'abord à ceci:
#ifndef SDF_2D #define SDF_2D
Répétition de l'espace
Réflexion miroir
L'une des opérations les plus simples consiste à mettre en miroir le monde autour d'un axe. Pour le refléter autour de l'axe y, nous prenons la valeur absolue de la composante x de notre position. Ainsi, les coordonnées à droite et à gauche de l'axe seront les mêmes.
(-1, 1)
transforme en
(1, 1)
et se trouve à l'intérieur d'un cercle en utilisant
(1, 1)
comme origine de coordonnées et avec un rayon supérieur à 0.
Le plus souvent, le code utilisant cette fonction ressemblera à quelque chose comme
position = mirror(position);
afin que nous puissions simplifier un peu. Nous allons simplement déclarer l'argument position comme inout. Ainsi, lors de l'écriture de l'argument, cela changera également la variable que nous transmettons à la fonction. La valeur de retour peut alors être de type void, car nous n'utilisons toujours pas la valeur de retour.

Cela s'est déjà bien passé, mais de cette façon, nous n'avons qu'un seul axe pour la mise en miroir. Nous pouvons étendre la fonction en faisant pivoter l'espace comme nous l'avons fait lors de la rotation des figures. Vous devez d'abord faire pivoter l'espace, puis le mettre en miroir, puis le retourner. De cette façon, nous pouvons effectuer une mise en miroir par rapport à n'importe quel angle. La même chose est possible lors du transfert d'espace et de l'exécution d'un transfert inverse après la mise en miroir. (Si vous effectuez les deux opérations, puis avant la mise en miroir, n'oubliez pas d'effectuer d'abord le transfert, puis de tourner, après quoi le tour commence en premier.)
Cellules
Si vous savez comment
fonctionne la génération de bruit , vous comprenez que pour la génération procédurale, nous répétons souvent la position et obtenons de petites cellules qui sont essentiellement les mêmes, ne différant que par des paramètres insignifiants. Nous pouvons faire de même pour les champs de distance.
Étant donné que la fonction
fmod
(en plus d'utiliser% pour diviser avec le reste) nous donne le reste, pas la définition du reste, nous devrons utiliser une astuce. Tout d'abord, nous prenons le reste de la division entière par la fonction fmod. Pour les nombres positifs, c'est exactement ce dont nous avons besoin, et pour les nombres négatifs, c'est le résultat dont nous avons besoin moins la période. Vous pouvez résoudre ce problème en ajoutant un point et en prenant à nouveau le reste de la division. L'ajout d'une période donnera le résultat souhaité pour les valeurs d'entrée négatives et pour les valeurs d'entrée positives, la valeur est supérieure d'une période. Le deuxième reste de la division ne fera rien avec les valeurs des valeurs d'entrée négatives, car elles sont déjà comprises entre 0 et la période, et pour les valeurs d'entrée positives, nous soustraireons essentiellement une période.

Le problème avec les cellules est que nous perdons la continuité pour laquelle nous aimons les champs de distance. Ce n'est pas mauvais si les formes ne sont qu'au milieu des cellules, mais dans l'exemple illustré ci-dessus, cela peut conduire à des artefacts importants qui devraient être évités lorsque les champs de distance sont utilisés pour une variété de tâches dans lesquelles des champs de distance peuvent généralement être appliqués.
Il y a une solution qui ne fonctionne pas dans tous les cas, mais quand cela fonctionne, c'est merveilleux: refléter toutes les autres cellules. Pour ce faire, nous avons besoin d'un index de cellule de pixel, mais nous n'avons toujours pas de valeur de retour dans la fonction, nous pouvons donc simplement l'utiliser pour renvoyer l'index de cellule.
Pour calculer l'indice de cellule, nous divisons la position par la période. Ainsi, 0-1 est la première cellule, 1-2 est la seconde, et ainsi de suite ... et nous pouvons facilement le discrétiser. Pour obtenir l'index de la cellule, nous arrondissons simplement la valeur vers le bas et retournons le résultat. L'important est que l'on calcule l'indice de la cellule avant de diviser par le reste pour répéter les cellules; sinon, nous obtiendrions l'indice 0 partout, car la position ne peut pas dépasser la période.
Avec ces informations, nous pouvons retourner les cellules. Pour comprendre s'il faut retourner ou non, on divise l'index de cellule modulo 2. Le résultat de cette opération est alternativement 0 et 1 ou -1 toutes les deux cellules. Pour rendre le changement plus permanent, nous prenons la valeur absolue et obtenons une valeur qui bascule entre 0 et 1.
Pour utiliser cette valeur pour basculer entre une position normale et inversée, nous avons besoin d'une fonction qui ne fait rien pour la valeur 0, et soustrait la position de la période pendant laquelle le retournement est 1. Autrement dit, nous effectuons une interpolation linéaire de la position normale à la position inversée à l'aide de la variable flip . Comme la variable flip est un vecteur 2D, ses composants sont inversés individuellement.
Cellules radiales
Une autre grande caractéristique est la répétition de l'espace dans un motif radial.
Pour obtenir cet effet, nous calculons d'abord la position radiale. Pour ce faire, nous encodons l'angle par rapport au centre de l'axe x et la distance du centre le long de l'axe y.
float2 radialPosition = float2(atan2(position.x, position.y), length(position));
Ensuite, nous répétons le coin. Étant donné que la transmission du nombre de répétitions est beaucoup plus facile que l'angle de chaque pièce, nous calculons d'abord la taille de chaque pièce. Le cercle entier fait 2 * pi, donc pour obtenir la bonne partie, nous divisons 2 * pi par la taille des cellules.
const float PI = 3.14159; float cellSize = PI * 2 / cells;
Avec ces informations, nous pouvons répéter la composante x de la position radiale toutes les unités cellSize. Nous effectuons la répétition par division avec le reste, donc, comme précédemment, nous avons des problèmes avec les nombres négatifs, qui peuvent être éliminés en utilisant les deux fonctions de division avec le reste.
radialPosition.x = fmod(fmod(radialPosition.x, cellSize) + cellSize, cellSize);
Ensuite, vous devez ramener la nouvelle position aux coordonnées xy habituelles. Ici, nous utilisons la fonction sincos avec la composante x de la position radiale comme angle pour écrire le sinus à la coordonnée x de la position et le cosinus à la coordonnée y. Avec cette étape, nous obtenons une position normalisée. Pour obtenir la bonne direction à partir du centre, multipliez-la par la composante y de la position radiale, ce qui signifie la longueur.
Ensuite, nous pouvons également ajouter l'index de cellule et la mise en miroir, comme nous l'avons fait avec les cellules normales.
Il est nécessaire de calculer l'indice de cellule après avoir calculé la position radiale, mais avant de recevoir son reste de la division. Nous l'obtenons en divisant la composante x de la position radiale et en arrondissant le résultat vers le bas. Dans ce cas, l'indice peut également être négatif, ce qui pose problème si le nombre de cellules est impair. Par exemple, avec 3 cellules, on obtient 1 cellule avec un indice de 0, 1 cellule avec un indice de -1 et 2 demi-cellules avec les indices 1 et -2. Pour contourner ce problème, nous ajoutons le nombre de cellules à la variable arrondie à la variable, puis divisons par la taille de la cellule avec le reste.
Pour refléter cela, nous avons besoin que les coordonnées soient en radians, donc pour éviter de recalculer les coordonnées radiales en dehors de la fonction, nous y ajoutons une option en utilisant l'argument bool. Habituellement, dans les shaders, la ramification (si les constructions) n'est pas la bienvenue, mais dans ce cas, tous les pixels de l'écran suivront le même chemin, c'est donc normal.
La mise en miroir doit se produire après le bouclage de la coordonnée radiale, mais avant qu'elle ne soit reconvertie à sa position normale. Nous découvrons si nous devons inverser la cellule actuelle en divisant l'index de cellule par 2 avec le reste. Habituellement, cela devrait nous donner des zéros et des uns, mais dans mon cas, plusieurs deux apparaissent, ce qui est étrange, et pourtant nous pouvons le gérer. Pour éliminer les égalités, nous soustrayons simplement 1 de la variable flip, puis prenons la valeur absolue. Ainsi, les zéros et les deux deviennent des unités et les unités ne deviennent des zéros, comme nous en avons besoin, que dans l'ordre inverse.
Comme les zéros et les uns sont dans le mauvais ordre, nous effectuons une interpolation linéaire de la version inversée à la version inversée, et non l'inverse, comme précédemment. Pour inverser les coordonnées, nous soustrayons simplement la position de la taille de la cellule.
Espace de balancement
Mais changer l'espace n'est pas nécessaire de le répéter. Par exemple, dans le tutoriel sur les bases, nous l'avons tourné, déplacé et mis à l'échelle. Vous pouvez également effectuer les opérations suivantes: déplacer chaque axe sur la base de l'autre avec une onde sinusoïdale. Cela rendra les distances de la fonction de distance signée moins précises, mais jusqu'à ce qu'elles oscillent trop, tout ira bien.
Tout d'abord, nous calculons l'amplitude du changement de position en inversant les composantes x et y, puis en les multipliant par la fréquence d'oscillation. Ensuite, nous prenons le sinus de cette valeur et le multiplions par la quantité d'oscillation que nous voulons ajouter. Après cela, nous ajoutons simplement ce facteur d'oscillation à la position et appliquons à nouveau le résultat à la position.
Nous pouvons également animer cette ondulation, changer sa position, appliquer une ondulation à la position décalée et renvoyer l'espace. Pour que les nombres à virgule flottante ne deviennent pas trop grands, je fais la division avec le reste pi * 2 par la fréquence d'oscillation, cela correspond à l'oscillation (la sinusoïde se répète chaque pi * 2 unités), donc nous évitons les sauts et les décalages trop grands.
Code source
Bibliothèque SDF 2D
#ifndef SDF_2D #define SDF_2D
Shader de démonstration de base
Shader "Tutorial/036_SDF_Space_Manpulation/Mirror"{ Properties{ _InsideColor("Inside Color", Color) = (.5, 0, 0, 1) _OutsideColor("Outside Color", Color) = (0, .5, 0, 1) _LineDistance("Mayor Line Distance", Range(0, 2)) = 1 _LineThickness("Mayor Line Thickness", Range(0, 0.1)) = 0.05 [IntRange]_SubLines("Lines between major lines", Range(1, 10)) = 4 _SubLineThickness("Thickness of inbetween lines", Range(0, 0.05)) = 0.01 } SubShader{
Maintenant, vous connaissez toutes les bases des fonctions de distance de signe dont je me souviens. Dans le prochain tutoriel, je vais essayer de faire quelque chose d'intéressant avec eux.