Je voulais une nébuleuse dans mon jeu
The Last Boundary . Ils ont l'air incroyables et l'espace sans eux n'est pas de l'espace, mais simplement des pixels blancs dispersés autour de l'arrière-plan. Mais comme je crée le jeu dans le style du "pixel art", je devais en quelque sorte faire en sorte que ma bibliothèque de bruit génère des images pixelisées.
Voici quelques exemples:
Dans les exemples monochromes, 8 couleurs sont utilisées et dans d'autres, 16 couleurs. Dans cet article, je vais vous expliquer comment j'ai créé une nébuleuse pixélisée pour The Last Boundary.
Lorsque nous travaillons avec une bibliothèque de bruit, telle que
LibNoise ,
quel que soit le moteur que vous utilisez (ou écrivez le vôtre), les valeurs sont généralement réparties dans la plage de
-1
Ă
1
. Il est théoriquement plus probable que le bruit 2D soit compris entre
-0.7
et
0.7
, mais certaines implémentations mettent le résultat à l'échelle, le traduisant dans l'intervalle de
-1
Ă
1
. Pour travailler avec des textures 2D, il est généralement converti en un intervalle de
0
Ă
1
, puis il se
RGB(255,255,255)
dans la plage de
RGB(0,0,0)
Ă
RGB(255,255,255)
.
Bruit Perlin généré à partir des coordonnées x,y
de chaque pixel mis à l'échelle à 0.3f
Ensuite, vous pouvez utiliser
le mouvement brownien fractionnaire pour donner Ă l'image une sensation de splendeur des nuages.
Le bruit de Perlin a été soumis à un mouvement brownien fractionnaire avec 8
octaves, fréquence 0.01
, régularité 0.5
et lacunarité 2.0
.J'ai remarqué qu'il existe de nombreuses implémentations incorrectes du bruit Perlin, du bruit simplex et du mouvement brownien fractionnaire (fBm) sur Internet. Il semble y avoir beaucoup de confusion sur ce qui est quoi. Assurez-vous que vous utilisez l'implémentation correcte, car si vous souhaitez créer la chaîne décrite ci-dessus, en cas d'implémentation incorrecte, vous risquez de ne pas obtenir les résultats requis.
Imaginons que nous voulons créer un effet de fumée, c'est-à -dire qu'une telle solution nous convienne. Mais notre jeu de pixel art aurait l'air étrange si tout un tas de nouvelles couleurs y apparaissaient de
RGB(0,0,0)
Ă
RGB(255,255,255)
. Soudain, 255 nouveaux grades de gris apparaîtront dans le jeu.
Nous devons les convertir en un nombre limité de couleurs. C'est ce que nous ferons plus tard. En attendant ...
Générer une nébuleuse aléatoire
J'ai répété pour des didacticiels prêts à l'emploi sur la génération de nébuleuses aléatoires, mais j'ai ajouté certaines de mes étapes et appliqué ma propre bibliothèque de bruit. Je l’ai écrit il y a quelques années parce que je voulais bien comprendre le bruit de Perlin et comment l’utiliser avec d’autres concepts pour créer des textures, etc.
Peut-être pouvez-vous répéter étape par étape après moi ou vous devrez faire des ajouts au code qui affecteront votre bruit. Je vais tout expliquer sauf la génération de bruit initiale et fBm pour que vous puissiez écrire le code vous-même; Je pense que l'on peut supposer que vous avez déjà la capacité de générer du bruit et du fBm.
Pour commencer, je vais montrer le résultat de la génération de la nébuleuse:
Résultat finiIl est important de noter qu'il n'est pas encore pixellisé. Il a une gamme complète de couleurs avec un ciel étoilé pixélisé. La nébuleuse nous pixelliserons plus tard.
La première chose à faire est de générer cinq textures différentes: rouge, verte, bleue, alpha et masque. Les textures rouge, verte et bleue sont nécessaires pour les canaux de couleur finale correspondants. En fait, je ne génère qu'un ou deux canaux de couleur, car il s'est avéré que l'utilisation des trois produit une nébuleuse incroyablement colorée qui a l'air moche. Toute couleur unique ou une combinaison de deux couleurs fera bien l'affaire.
Le canal Alpha est important car cela dépend si les étoiles inférieures brillent à travers la nébuleuse. Je vais illustrer cela en affichant le canal alpha de l'exemple ci-dessus.
Canal alpha prĂŞt de notre exemplePlus la zone est blanche, plus la valeur est proche de
1.0
, ce qui nous donne une valeur alpha de
255
. Plus la zone est noire, plus elle est transparente. Si vous regardez un exemple, vous pouvez voir que les zones noires correspondent aux zones dans lesquelles le ciel étoilé est visible.
Exemple de ciel étoiléCe ne sont pas les mêmes étoiles que dans l'exemple, car elles sont générées aléatoirement dans chaque capture d'écran. J'espère que cela ne vous empêche pas de comprendre comment la nébuleuse est générée.
Ma bibliothèque de bruit se compose de modules, suivant l'exemple de
Lib Noise . Tout dans cette bibliothèque est constitué de «modules» qui peuvent être enchaînés ensemble. Certains modules génèrent de nouvelles valeurs (module Perlin, valeur constante), d'autres les connectent (multiplication, ajout) et certains effectuent simplement des opérations sur la valeur (Lerp, Clamp).
Canaux de couleur
Peu importe que nous travaillions avec une, deux ou trois couleurs - les canaux rouge, vert et bleu sont générés de la même manière; J'utilise juste une valeur de graine différente pour eux. Mes valeurs de départ dépendent de l'heure actuelle du système.
Ci-dessous, ils sont tous présentés en niveaux de gris, mais théoriquement, ce sont simplement des valeurs pour l'un des trois canaux. Niveaux de gris est juste là pour illustrer les résultats.
1. Le bruit de Perlin
Comme ci-dessus, le bruit de Perlin sera le point de départ. Si vous voulez, vous pouvez utiliser du bruit simplex, il semble que son implémentation 2D n'appartienne pas à Ken Perlin, mais je peux me tromper. D'un point de vue mathématique, le bruit simplex utilise moins d'instructions, donc la génération d'une nébuleuse similaire sera plus rapide. Puisqu'il utilise des simplexes au lieu d'une grille, il crée un bruit légèrement plus beau, mais nous ne travaillerons pas beaucoup avec, donc ce n'est pas particulièrement important.
Le code réel n'est pas illustré ci-dessous, car dans les sources réelles
x,y
valeurs
x,y
ont été modifiées par fBm à l'étape 3. Il s'agit simplement de la coordonnée
x,y
de l'image, multipliée par le facteur d'échelle statique.
Bruit Perlin généré à partir des coordonnées x,y
de chaque pixel mis à l'échelle à 0.3f
. C'est-Ă -dire PixelValue = PerlinNoise(x * 0.3f, y * 0.3f)
Les valeurs créées par le bruit Perlin se situent approximativement dans la plage de
-1
Ă
1
, donc pour créer l'image en niveaux de gris habituelle illustrée ci-dessus, nous les convertissons à l'intervalle de
0
Ă
1
. J'ai testĂ© la portĂ©e des valeurs afin que la conversion produise le plus grand contraste (la valeur la plus basse correspond Ă
0
, la plus grande -
1
).
2. Multiplication
Le module suivant utilisé multiplie le bruit généré par
5
. Cela peut être considéré comme un ajustement du contraste. Les valeurs négatives sont plus sombres, les valeurs positives sont plus claires.
Je n'ai rien Ă montrer ici, car dans le processus de conversion des valeurs de l'intervalle de
-5
Ă
5
Ă l'intervalle de
0
Ă
1
résultat ne change pas.
3. Mouvement brownien fractionnaire (fBM)
Cette étape transforme le bruit en ce que beaucoup de gens considèrent comme un véritable «effet de bruit». Ici, nous exécutons des octaves d'échantillons de plus en plus petits à partir de la fonction de bruit (dans notre cas, la fonction est
perlin(x,y)
) pour ajouter du fluffiness.
Mouvement brownien fractionnaire du bruit Perlin illustré ci-dessus. 8
octaves, fréquence .01f
, régularité .5f
et 2.5f
Vous pouvez déjà voir l'origine de quelque chose d'intéressant. L'image ci-dessus n'est pas générée en mettant à l'échelle les coordonnées
x,y
des pixels, fBM le fait. Encore une fois, ces valeurs sont inversement converties en un intervalle de
0
Ă
1
en un intervalle possible de
-5
Ă
5
.
4. Restriction (pince)
Je vais maintenant limiter les valeurs Ă une plage de
-1
Ă
1
. Tout ce qui se trouve en dehors de cet intervalle sera complètement rejeté.
Le même fBm, limité à -1
Ă 1
La tâche de cette opération est de convertir les valeurs en un intervalle plus court tout en créant des dégradés plus nets et en augmentant la zone en blanc ou en noir. Ces zones mortes ou vides sont importantes pour l'effet de nébuleuse, que nous aborderons plus tard. Si nous n'avions pas multiplié par
5
au début, alors la pince n'aurait rien changé.
5. Ajoutez 1
Maintenant, nous prenons les valeurs de clamp et nous y ajoutons 1. Ainsi, nous transférons les valeurs dans l'intervalle de
0
Ă
2
. Après la conversion, les résultats seront les mêmes qu'avant.
6. Divisez par 2
Vous savez probablement ce qui se passera lorsque je divise le résultat par
2
(multipliez par
.5
). Dans l'image, rien ne changera Ă nouveau.
Les étapes 5 et 6 convertissent les valeurs dans une plage de
0
Ă
1
.
7. Créez une texture de distorsion
L'étape suivante consiste à créer une texture de distorsion. Je vais le faire avec du bruit Perlin (avec la nouvelle valeur de départ)> multiplier par 4> exécuter fBm. Dans ce cas, fBm utilise
5
octaves, une fréquence de
0.025
, une régularité de
0.5
et une lacunarité de
1.5
.
Texture de distorsionCette texture est nécessaire pour créer plus de détails que dans la texture existante de la nébuleuse. La nébuleuse est un nuage ondulé assez grand, et cette texture y apportera de petits changements. À travers elle, la nature de grille du bruit de Perlin commencera à émerger.
8. Décalez la texture de couleur en utilisant la texture de décalage
Ensuite, je vais prendre ces deux textures et utiliser l'une pour compenser les coordonnées de l'autre par un facteur. Dans notre cas, la combinaison ressemble à ceci:
Résultat de biaisLa texture de distorsion est utilisée pour modifier les coordonnées
x,y
nous recherchons dans les données de bruit source.
N'oubliez pas que les images ci-dessus sont uniquement à titre d'illustration. À chaque étape, nous n'avons en fait qu'une fonction de bruit. Nous lui passons la valeur
x,y
et il renvoie un nombre. À certaines étapes, l'intervalle de ce nombre peut être différent, mais ci-dessus, nous l'avons reconverti en niveaux de gris pour créer une image. L'image est créée en utilisant chaque coordonnée
x,y
de l'image comme
x,y
, transmise par la fonction de bruit.
Autrement dit, lorsque nous disons:
Donnez-moi la valeur du pixel du coin supérieur gauche avec X = 0 et Y = 0
La fonction nous renvoie un nombre. Si nous demandons cela Ă Perlin, nous savons que ce sera entre
-1
et
1
, si, comme ci-dessus, nous appliquons la pince, l'addition et la multiplication, nous obtenons une valeur entre
0
et
1
.
Après avoir compris cela, nous apprenons que la fonction de bruit de distorsion crée des valeurs dans la plage de
-1
Ă
1
. Par conséquent, pour effectuer le biais lorsque nous disons:
Donnez-moi la valeur du pixel dans le coin supérieur gauche avec le pixel X = 0 et Y = 0
le module de décalage demande d'abord à la fonction de décalage les coordonnées
x,y
. Le résultat est compris entre
-1
et
1
(comme c'était le cas ci-dessus). Il est ensuite multiplié par
40
(c'est le
coefficient que j'ai sélectionné). Le résultat sera une valeur comprise entre
-40
et
40
.
Ensuite, nous prenons cette valeur et l'ajoutons aux coordonnées des
x,y
que nous recherchions, et utilisons ce résultat pour rechercher la texture de la couleur. Nous avons coupé les valeurs négatives avec une pince à 0, car il est impossible de rechercher des coordonnées
x,y
négatives dans les fonctions de bruit (au moins dans ma bibliothèque de bruit).
Autrement dit, cela ressemble Ă ceci:
ColourFunction(x,y) = 0 1 DisplaceFunction(x,y) = -1 1 DoDisplace(x,y) = { v = DisplaceFunction(x,y) * factor clamp(v,0,40) x = x + v; y = y + v; if x < 0 then x = 0 if y < 0 then y = 0 return ColourFunction(x,y) }
J'espère que vous comprenez cela. En fait, nous ne regardons pas le
x,y
lequel nous étions, mais le décalage. Et puisque la
magnitude est également un gradient lisse, elle se déplace en douceur.
Il existe d'autres façons d'effectuer le décalage. Ma bibliothèque de bruit a un module qui crée un déplacement en spirale. Il peut être utilisé pour dessiner une texture, diminuant progressivement jusqu'à un certain nombre de points.
Voici un exemple .
C’est tout. Nous répétons les opérations ci-dessus trois fois, en utilisant de nouvelles valeurs de départ pour chaque canal de couleur. Vous pouvez créer un ou deux canaux. Je ne pense pas que ça vaille la peine d'en créer un troisième.
Canal alpha
Un canal alpha est créé de la même manière que les canaux de couleur:
- On commence par le bruit de Perlin
- Multipliez par
5
- fBM avec
8
octaves, fréquence 0.005
, régularité 0.5
et lacunarité 2.5
- Nous limitons les résultats en utilisant Clamp à l'intervalle de
-1
Ă 1
, ajoutons 1
, divisons par 2
(c'est-à -dire que nous décalons l'intervalle de -1
Ă 1
Ă l'intervalle de 0
Ă 1
. - Nous décalons légèrement le résultat dans le sens négatif. J'ai compensé de
0.4
. Grâce à cela, tout devient un peu plus sombre. - Nous limitons les résultats à un intervalle de
0
Ă 1
. Puisque nous avons tout déplacé, le rendant un peu plus sombre, en fait, nous avons créé plus de zones avec 0
, et certaines zones sont entrées dans des valeurs négatives.
Le résultat est une texture de canal alpha.
Texture alphaComme je l'ai dit, les zones noires seront transparentes et les zones blanches seront opaques.
Masques de canal
Il s'agit de la dernière texture utilisée pour créer des ombres superposées sur tout le reste. Cela commence comme toutes les autres textures:
- Perlin de bruit
- Multipliez par
5
- Nous effectuons fBm,
5
octaves, fréquence 0.01
, régularité 0.1
, lacunarité 0.1
. La régularité est faible, donc le nuage est moins dense - Effectuer un décalage d'intervalle de
-1
Ă 1
Ă un intervalle de 0
Ă 1
Mais nous créons deux de ces textures:
Masquer unMasque BNous exposons ces deux textures Ă ce que j'appelle le module
Select . En fait, nous utilisons la valeur du module A ou du module B. Le choix dépend de la valeur du module C. Il nécessite deux autres valeurs -
Select Point et
Falloff .
Si la valeur au point
x,y
module C est supĂ©rieure ou Ă©gale Ă
SelectPoint
, alors nous utilisons la valeur au point
x,y
module B. Si la valeur est infĂ©rieure ou Ă©gale Ă
SelectPoint - Falloff
, alors nous utilisons la valeur Ă
x,y
module A.
S'il se situe entre
SelectPoint - Falloff
et
SelectPoint
, alors nous effectuons une interpolation linéaire entre les valeurs
x,y
du module A et du module B.
float select(x, y, moduleA, moduleB, moduleC, selectPoint, falloff) { float s = moduleC(x,y); if(s >= selectPoint) return moduleB(x,y); else if(s <= selectPoint - falloff) return moduleA(x,y); else { float a = moduleA(x,y); float b = moduleB(x,y); return lerp(a, b, (1.0 / ((selectPoint - (selectPoint-falloff)) / (selectPoint - s))); } }
Dans notre cas, le module A est un module
constant avec une valeur de
0
. Le module B est la première texture du masque A et le
sélecteur (module C) est le deuxième masque de B.
SelectPoint
sera de
0.4
et
Falloff
sera de
0.1
. En conséquence, nous obtenons:
Masque ultimeEn augmentant ou en diminuant
SelectPoint
, nous
SelectPoint
ou
SelectPoint
la quantité de noir dans le masque. En augmentant ou en diminuant l'
falloff
, nous
falloff
ou diminuons les bords mous des masques. Au lieu d'un des masques, je pouvais utiliser le module
Constant avec une valeur de
1
, mais je voulais ajouter un peu d'aléatoire aux zones «non masquées».
Mélangez le canal de couleur et le masque
Maintenant, nous devons appliquer un masque Ă chacun des canaux de couleur. Cela se fait Ă l'aide du module
Blending . Il combine les pourcentages de valeurs de deux modules afin que la somme des valeurs soit de 100%.
Autrement dit, nous pouvons prendre 50% de la valeur en
x,y
module A et 50% de la valeur en
x,y
module B. Ou 75% et 25%, etc. Le pourcentage que nous prenons de chaque module dépend d'un autre module - module C. Si la valeur en
x,y
module C est
0
, alors nous prendrons 100% du module A et 0% du module B. Si c'est
1
, alors nous prenons valeurs inverses.
Combinez pour chaque texture de couleur.
- Module A - Valeur constante 0
- Le module B est le canal de couleur que nous avons déjà vu
- Module C - résultat du masque
Cela signifie que le bruit du canal de couleur sera affichĂ© uniquement lorsque le masque a des valeurs supĂ©rieures Ă
0
(zones plus proches du blanc), et l'amplitude de leur visibilité dépend de la valeur du masque.
Voici le résultat de notre exemple:
Résultat finalComparez cela à l'original avant d'appliquer le mélange avec un masque.
Avant de mélanger avec un masquePeut-être que cet exemple n'est pas très évident, mais en raison du hasard, il est difficile de sélectionner spécifiquement un bon exemple. L'effet du masque est de créer des zones plus sombres. Bien sûr, vous pouvez personnaliser le masque pour qu'il soit plus prononcé.
Il est important ici que le même masque soit appliqué à l'ensemble du canal de couleur, c'est-à -dire que les mêmes zones apparaissent dans l'ombre.
Nous combinons tout ensemble
Notre exemple fini initial:
Exemple prĂŞtIl utilise les canaux rouge, vert et alpha:
Canal rougeCanal vertCanal alphaEt puis nous les déposons sur notre ciel étoilé.
Tout semble maintenant assez bon, mais pas très adapté à un jeu de pixel art. Nous devons réduire le nombre de couleurs ...
Coupe médiane
Cette partie de l'article peut s'appliquer à tout. Disons que vous générez une texture de marbre et que vous souhaitez réduire le nombre de couleurs. C'est là que l'algorithme de coupe médiane est utile. Nous l'utiliserons pour réduire le nombre de couleurs dans la nébuleuse ci-dessus.
Cela se produit
avant qu'il ne se superpose au ciel étoilé. Le nombre de couleurs est complètement arbitraire.
L'algorithme de coupe médiane tel que décrit dans Wikipedia:
Supposons que nous ayons une image avec un nombre arbitraire de pixels et que nous voulons générer une palette de 16 couleurs. Mettez tous les pixels de l'image (c'est-à -dire leurs valeurs RVB ) dans la corbeille . Découvrez quel canal de couleur (rouge, vert ou bleu) parmi tous les pixels du panier a la plus grande plage de valeurs, puis triez les pixels en fonction des valeurs de ce canal. Par exemple, si le canal bleu a la plus grande plage de valeurs, le pixel avec la valeur RVB (32, 8, 16) est plus petit que le pixel avec la valeur RVB (1, 2, 24), car 16 <24. Après le tri du panier, placez la moitié supérieure des pixels dans un nouveau panier. (Cette étape a donné le nom à l'algorithme de coupe médiane; les paniers sont divisés par deux par la médiane de la liste des pixels.) Répétez le processus pour les deux paniers, ce qui nous donnera 4 paniers, puis répétez pour les 4 paniers, obtenez 8 paniers, puis répétez pour 8 paniers, nous obtenons 16 paniers. Nous faisons la moyenne des pixels dans chacun des paniers et obtenons une palette de 16 couleurs. Comme le nombre de paniers double à chaque itération, l'algorithme ne peut générer que de telles palettes, dont le nombre de couleurs est une puissance de deux . Par exemple, pour générer une palette de 12 couleurs, vous devez d'abord générer une palette de 16 couleurs, puis combiner d'une manière ou d'une autre certaines couleurs.
Source: https://en.wikipedia.org/wiki/Median_cut
Cette explication me semblait plutôt mauvaise et pas particulièrement utile. Lors de la mise en œuvre de l'algorithme, des images plutôt laides sont obtenues de cette manière. Je l'ai implémenté avec quelques changements:
- Nous stockons le conteneur de
boxes
avec la valeur indiquant l'intervalle (plus de détails ci-dessous). La box
stocke simplement un certain nombre dynamique de pixels de l'image d'origine. - Ajoutez tous les pixels de l'image d'origine comme première
et utilisez l'intervalle 0
- Bien que le nombre total de
inférieur au nombre de couleurs requis, nous continuons les étapes suivantes. - Si la valeur de l'intervalle est
0
, alors pour chaque case actuelle, nous déterminons le canal de couleur principal de cette box
, puis trions les pixels de cette box
par cette couleur. — Red, Green, Blue Alpha, . , redRange = Max(Red) - Min(Red)
. , . box
boxes
. , box
.- , 4 5
box
, boxes
. , , , . , , . box
( == ) boxes
. 0
( ). , , , — . .
Lorsque nous atteignons le nombre de cases égal au nombre de couleurs souhaité, nous faisons simplement la moyenne de tous les pixels de chaque case pour déterminer l'élément de palette qui convient le mieux à ces couleurs. Je viens d'utiliser la distance euclidienne, mais il existe des solutions perceptuelles qui peuvent faire mieux.Voici une image qui expliquera tout plus clairement. Pour la démonstration, j'utilise uniquement RGB, car alpha est difficile à montrer.Appliquons cette méthode à notre exemple d'image.L'originalCoupe médiane jusqu'à 16 couleursJ'ai découvert qu'en utilisant deux canaux de couleur, un bon effet est obtenu avec 16 couleurs. Mais gardez à l'esprit que nous utilisons ici le canal alpha, qui est également impliqué dans le calcul de la distance entre les couleurs. Donc, si vous ne vous souciez pas de la transparence, vous pouvez utiliser moins de couleurs. Étant donné que ma coupe médiane, contrairement à l'exemple de Wikipedia, peut utiliser un nombre arbitraire de couleurs (et pas seulement des puissances de deux), vous pouvez la personnaliser en fonction de vos besoins.De 16 à 2 couleurs,nous avons sélectionné une couleur dans chacune box
, en faisant simplement la moyenne de toutes les valeurs. Cependant, ce n'est pas le seul moyen. Vous avez peut-être remarqué que notre résultat par rapport à l'original n'est pas si brillant. Si vous en avez besoin, vous pouvez donner la préférence dans les intervalles supérieurs, en ajoutant du poids à la définition des intervalles. Ou vous pouvez facilement sélectionner 1, 2 ou 3 des couleurs les plus lumineuses de l'image et les ajouter à la palette. Par conséquent, si vous avez besoin de 16 couleurs, générez une palette de 13 couleurs et ajoutez manuellement vos couleurs vives.Une palette avec les trois couleurs les plus brillantesMaintenant, tout semble plutôt bien, mais l'image est trop inégale. Il a de grandes zones de la même couleur. Maintenant, nous devons les lisser.Dithering
Je n'ai pas besoin de vous dire ce qu'est le tramage, car vous travaillez déjà avec le pixel art. Donc, pour obtenir une image plus fluide, nous utiliserons l'un des algorithmes de tramage, dont il existe beaucoup.J'ai implémenté un algorithme de tramage Floyd-Steinberg simple . Il n'y a pas eu de mauvaises surprises. Cependant, l'effet a été assez fort. Voici à nouveau notre exemple:L'originalEnsuite, nous avons coupé la palette à 16 couleurs:Les valeurs sont mappées sur une palette de 16 couleurs.Et maintenant le tramage suivi d'une conversion en palette:Résultat fini avec tramage