mirror of
https://github.com/patriciogonzalezvivo/thebookofshaders
synced 2024-11-08 01:10:27 +00:00
266 lines
16 KiB
Markdown
266 lines
16 KiB
Markdown
## Couleurs
|
|
|
|
![Paul Klee - Charte Couleur (1931)](klee.jpg)
|
|
|
|
Jusqu'ici, nous avons manipulé des vecteurs mais nous n'avons pas encore pris le temps de voir comment marchent ces variables.
|
|
Avant d'aller plus loin, il est important d'en savoir plus sur ces variables.
|
|
Aborder la couleur est un bon moyen de se pencher sur la question.
|
|
|
|
Si vous connaissez la Programmation Orientée Objet, vous aurez remarqué que nous accédons aux données des vecteurs comme on le ferait avec des ```struct``` en C, grâce à des **accesseurs / mutateurs** ( **getters / setters** en anglais).
|
|
|
|
[NDT]bien que cette pratique soit méconnue, il es possible d'utiliser des ```struct``` en GLSL, [plus d'informations ici](https://github.com/KhronosGroup/WebGL/blob/master/sdk/tests/conformance/glsl/misc/shader-with-array-of-structs-uniform.html) [/NDT]
|
|
|
|
```glsl
|
|
vec3 red = vec3(1.0,0.0,0.0);
|
|
red.x = 1.0;
|
|
red.y = 0.0;
|
|
red.z = 0.0;
|
|
```
|
|
|
|
Dans l'exemple ci dessus, *x*, *y* et *z* permettent d'**accéder** aux 3 valeurs contenues dans l'objet ```red``` de type ```vec3```, ce sont les **accesseurs** aux propriétés de ```red```.
|
|
|
|
Définir une couleur avec *x*, *y* et *z* peut être un peu déroutant, c'est pourquoi il existe différentes manières d'accéder aux valeurs des propriétés des vectuers.
|
|
Les valeurs de ```.x```, ```.y``` et ```.z``` peuvent être récupérées avec les **accesseurs** ```.r```, ```.g``` et ```.b```, ou ```.s```, ```.t``` et ```.p```. (```.s```, ```.t``` et ```.p``` sont généralement utilisées pour encoder les coordonnées spatiales des textures, nous verrons ça dans les chapitres suivants).
|
|
Il est également possible d'accéder aux valeurs des propriétés des vecteurs par leur position d'index dans l'objet: ```[0]```, ```[1]``` and ```[2]```.
|
|
|
|
Les lignes suivantes montrent les différentes manières d'accéder aux données:
|
|
|
|
```glsl
|
|
vec4 vector;
|
|
vector[0] = vector.r = vector.x = vector.s;
|
|
vector[1] = vector.g = vector.y = vector.t;
|
|
vector[2] = vector.b = vector.z = vector.p;
|
|
vector[3] = vector.a = vector.w = vector.q;
|
|
```
|
|
|
|
[NDT]on peut les utiliser ces **accesseurs** de façon indépendante ; le code suivant crée un clone *newColor* du vecteur *color* en utilisant chaque fois un accesseur différent.
|
|
```glsl
|
|
vec4 color = vec4( 1.,0.,0.5,1. );
|
|
vec4 newColor = vec4( color[0], color.g, color.z, color.q );
|
|
```
|
|
|
|
Il est possible de combiner les propriétés en **concaténant** les accesseurs: si on veut exploiter les valeurs ```.r```, ```.g``` et ```.b``` d'un vecteur 4 sans se soucier de ```.a``` (l'alpha), on peut écrire:
|
|
|
|
```glsl
|
|
vec4 color = vec4( 1.,0.,0.5,1. );
|
|
vec4 newColor = vec4( color.rgb, 1.0 );
|
|
```
|
|
|
|
Ce qui revient à cloner chaque proriété ```.r```, ```.g``` et ```.b``` du vecteur ```color``` sauf la dernière ```.a```.
|
|
|
|
Dans ce cas, ```color.rgb``` est interprété comme un vecteur de type ```vec3``` et il contient les valeurs ```.r```, ```.g``` et ```.b``` du ```vec4``` *color*.
|
|
De même, si on écrit:
|
|
|
|
```glsl
|
|
vec4 color = vec4( 1.,0.,0.5,1. );
|
|
vec3 newColor = vec3( color.xy, 1.0 );
|
|
```
|
|
On va utiliser les valeurs ```.x``` et ```.y``` de *color* pour construire un ```vec3``` dont les valeurs ```.r``` et ```.g``` seront les mêmes que les valeurs ```.r``` et ```.g``` du vecteur *color* et où la valeur ```.b``` sera ```1.0```.
|
|
|
|
Dernière chose, l'ordre dans lequel on **concatène** les accesseurs est important.
|
|
Si on veut construire un vecteur à partir d'un autre mais en inversant l'ordre des propriétés, on peut l'écrire comme suit:
|
|
|
|
```glsl
|
|
vec3 color = vec3( 1.0, 0.0, 0.5 );
|
|
vec3 newColor = color.bgr;
|
|
```
|
|
|
|
le vecteur *newColor* va copier les propriétés de *color* mais au lieu de les copier dans l'ordre "normal":```.r```, ```.g``` et ```.b```,
|
|
il va les copier dans l'ordre inverse: ```.b```, ```.g``` et ```.r```.
|
|
|
|
```glsl
|
|
color.r => 1.0
|
|
color.g => 0.0
|
|
color.b => 0.5
|
|
et
|
|
newColor.r => 0.5
|
|
newColor.g => 0.0
|
|
newColor.b => 1.0
|
|
```
|
|
|
|
Il en découle que si les déclarations suivantes sont équivalentes:
|
|
|
|
```glsl
|
|
color.rgba = color.xyzw = color.stpq
|
|
```
|
|
|
|
ces déclarations ne le sont pas:
|
|
|
|
```glsl
|
|
color.rgba != color.argb != color.rbga != color.abgr etc.
|
|
color.xyzw != color.wxyz != color.xzyw != color.wzyx etc.
|
|
color.stpq != color.qstp != color.sptq != color.qpts etc.
|
|
```
|
|
|
|
C'est une fonctionnalité très puissante ; elle permet de stocker les informations dans un format compact.
|
|
Un exemple d'utilisation, si on veut décrire un rectangle, on peut se servir soit de 2 ```vec2``` décrivant respectivement le coin supérieur gauche et le coin inférieur droit,
|
|
ou bien, utiliser un seul ```vec4``` dont l'**accesseur** ```.xy``` renverra un ```vec2``` décrivant le coin supérieur gauche, et l'**accesseur** ```.zw``` renverra un ```vec2``` décrivant le coin inférieur droit.
|
|
|
|
[/NDT]
|
|
|
|
Ces différentes manière d'accéder aux variables à l'intérieur des vecteurs sont simplement là pour nous aider à écrire un code lisible.
|
|
|
|
Cette souplesse d'utilisation est le point d'entrée qui vous permettra de penser aux espaces cartésiens (le "vrai" espace) et colorimétriques de façon interchangeable.
|
|
|
|
La concaténation prend tout son sens lorsqu'on veut pouvoir combiner des vecteurs dans un ordre arbitraire pour les mélanger (cette propriété s'appelle le *swizzle*).
|
|
|
|
```glsl
|
|
vec3 jaune, magenta, vert;
|
|
|
|
// crée le jaune
|
|
jaune.rg = vec2(1.0); // Assigne 1. au canaux rouges et vert du vecteur jaune
|
|
jaune[2] = 0.0; // Assigne 0. au canal bleu du vecteur jaune
|
|
|
|
// crée le magenta
|
|
magenta = jaune.rbg; // Assigne the valeur en intervertissant le vert et le bleu ( rbg au lieu de rgb )
|
|
|
|
// crée le vert
|
|
vert.rgb = jaune.bgb; // Assigne le canal bleu du jaune (0) aux canaux rouges et bleus
|
|
```
|
|
|
|
#### Pour la boîte à outils
|
|
|
|
Si vous êtes incapables de choisir des couleurs par le biais des chiffres, c'est normal ; cela peut être très contre-intuitif.
|
|
|
|
Heureusement pour nous, il existe de nombreux programmes qui nous simplifient la tâche.
|
|
Trouvez celui qui vous convient et apprenez à formater vos couleurs en ```vec3``` ou ```vec4```.
|
|
Par exemple, voici les gabarits que j'utilise avec [Spectrum](http://www.eigenlogik.com/spectrum/mac):
|
|
|
|
```
|
|
vec3({{rn}},{{gn}},{{bn}})
|
|
vec4({{rn}},{{gn}},{{bn}},1.0)
|
|
```
|
|
|
|
### Mélanger les couleurs
|
|
|
|
A présent que nous savons définir les couleurs, il est temps de les utiliser avec ce que nous avons déjà appris.
|
|
|
|
En GLSL, il existe une fonction extrêmement utile [```mix()```](../glossary/?search=mix), qui permet de mélanger deux valeurs en fonction d'un pourcentage.
|
|
|
|
[NDT]comme vu au chapitre précédent, ```mix()``` permet d'interpoler entre 2 valeurs grâce à une troisième valeur T.
|
|
exactement comme ```smoothstep()``` à la différence que cette fois, la fonction ```mix()``` prend en argument des vecteurs au lieu de *floats*.
|
|
[/NDT]
|
|
|
|
Pouvez vous deviner ce que devra être le pourcentage?
|
|
Evidemment, une valeur normalisée entre *0.0* et *1.0*!
|
|
Ce qui nous va très bien, après tout ce temps passé sur la palissade du chapitre précédent, il est enfin temps de frapper!
|
|
|
|
![](mix-f.jpg)
|
|
|
|
Observez la ligne 18: notez que nous utilisons la valeur absolue (```abs()```) d'une fonction de sinus (```sin()```) prenant le temps en argumanet pour contrôler le mélange entre ```colorA``` et ```colorB```.
|
|
|
|
<div class="codeAndCanvas" data="mix.frag"></div>
|
|
|
|
|
|
Montrez de quoi vous êtes capable
|
|
|
|
* Créez une transition expressive entre les couleurs. Pensez à une émotion en particulier.
|
|
* quelle couleur représente le mieux cette émotion? comment apparaît-elle? comment disparaît-elle?
|
|
* pensez à une autre émotion et à la couleur correspondante, changez les couleur A et B dans le code puis utilisez les fonctions de formes pour opérer la transition.
|
|
|
|
Robert Penner a développé une série de fonctions destinées à créer des animations, connues sous le nom d'[easing functions](http://easings.net/).
|
|
Vous pouvez utiliser [cet exemple](../edit.php#06/easing.frag) comme base de recherche mais les meilleures transitions seront celles que vous ferez vous mêmes.
|
|
|
|
### Jouer avec les dégradés
|
|
|
|
La fonction [```mix()```](../glossary/?search=mix) peut faire plus.
|
|
Au lieu de passer un seul ```float``` pour faire l'interpolation, nous pouvons passer successivement plusieurs valeurs de transition.
|
|
Dans l'exemple suivant, nous passons les valeurs ```r```, ```g``` et ```b``` d'un ```vec3``` (```pct``` pour *pourcentage* ) pour contrôler le mélange des canaux.
|
|
|
|
![](mix-vec.jpg)
|
|
|
|
Regardez l'exemple suivant.
|
|
Comme au chapitre précédent, nous branchons la valeur de transition sur la valeur normalisée de *x* et nous la visualisons par une ligne.
|
|
Au début, rien d'exceptionnel, le dégradé est linéaire sur les trois canaux.
|
|
|
|
A présent, décommentez la ligne 25 et regardez ce qui se passe, puis décommentez les lignes 26 et 27.
|
|
Rappelez vous que les lignes représentent la quantité de mélange entre ```colorA``` et ```colorB``` par canal de couleur.
|
|
|
|
<div class="codeAndCanvas" data="gradient.frag"></div>
|
|
|
|
Vous aurez sans doute reconnu les 3 fonctions de forme du chapitre précédent aux lignes 25,26,27.
|
|
A vous de jouer! Il est temps d'explorer et de créer des dégradés intéressants en ré-utilisant ce que nous avons vu au chapitre précédent.
|
|
Essayez les exercices suivants:
|
|
|
|
![William Turner - The Fighting Temeraire (1838)](turner.jpg)
|
|
|
|
* Composez un dégradé qui ressemble au coucher de soleil ci-dessus
|
|
|
|
* Animez une transition entre lever et coucher de soleil en utilisant ```u_time```.
|
|
|
|
* pouvez vous créer un arc-en-ciel avec ce que vous avez appris jusqu'à présent?
|
|
|
|
* Utilisez la fonction ```step()``` pour créer un drapeau.
|
|
|
|
### HSB
|
|
|
|
On ne peut pas parler de couleur sans parler d'espace colorimétrique. Vous le savez sans doute mais il existe plusieurs façons d'organiser les canaux rouge, vert, bleu.
|
|
|
|
[HSB](http://en.wikipedia.org/wiki/HSL_and_HSV) signifie "Hue, Saturation, Brightness (ou Value)" soit en bon français "Teinte, Saturation, Luminosité", c'est une manière plus intuitive d'organiser les couleurs.
|
|
Prenez un instant pour lire et essayer de comprendre les méthodes ```rgb2hsv()``` et ```hsv2rgb()``` dans le code suivant.
|
|
|
|
En indexant la Teinte (Hue) sur la position en *x* et la Luminosité (Brigthness) sur la position en *y*, nous obtenons un spectre des couleurs complet.
|
|
Cette distribution spatiale des couleurs peut être très pratique ; il est plus simple de choisir une couleur dans un espace HSB que dans un espace RGB.
|
|
|
|
<div class="codeAndCanvas" data="hsb.frag"></div>
|
|
|
|
### HSB et coordonnées polaires
|
|
|
|
A l'origine, HSB a été conçu pour être représenté dans un système de coordonnées polaires.
|
|
[NDT]Par opposition à un système de coordonnées cartésien décrit par 2 axes *X* et *Y* orthogonaux, un système de coordonnées polaires, est décrit par des *angles* et des *rayons*.[/NDT]
|
|
Pour dessiner notre fonction HSB, nous devons obtenir la position du centre du canvas de manière à connaître l'*angle* et la *distance* de chaque fragment au centre.
|
|
Pour cela nous allons utiliser la méthode [```length()```](../glossary/?search=length) et [```atan(y,x)```](../glossary/?search=atan) qui est l'équivalent GLSL de la méthode ```atan2(y,x)```.
|
|
|
|
Lorsqu'on utilise des vecteurs avec des fonctions trigonométriques les variables de type ```vec2```, ```vec3``` et ```vec4``` sont considérées comme des vecteurs géométriques même si elles représentent des couleurs.
|
|
Nous commencerons à traiter les couleurs et les vecteurs géométriques de façon similaire, en fait, vous devriez comprendre assez vite que cette flexibilité d'utilisation est une force.
|
|
|
|
**Note:** Si vous vous demandez s'il existe d'autres fonctions géométriques que [```length```](../glossary/?search=length)
|
|
comme: [```distance()```](../glossary/?search=distance), [```dot()```](../glossary/?search=dot), [```cross```](../glossary/?search=cross), [```normalize()```](../glossary/?search=normalize), [```faceforward()```](../glossary/?search=faceforward), [```reflect()```](../glossary/?search=reflect) et [```refract()```](../glossary/?search=refract), la réponse est oui.
|
|
GLSL expose également des méthodes pour comparer les vecteurs entres eux: [```lessThan()```](../glossary/?search=lessThan), [```lessThanEqual()```](../glossary/?search=lessThanEqual), [```greaterThan()```](../glossary/?search=greaterThan), [```greaterThanEqual()```](../glossary/?search=greaterThanEqual), [```equal()```](../glossary/?search=equal) et [```notEqual()```](../glossary/?search=notEqual).
|
|
|
|
Une fois que nous avons récupéré l'angle entre le centre et le fragment en cours, nous devons le normaliser.
|
|
Ligne 27, [```atan(y,x)```](../glossary/?search=atan) nous retourne un angle en radians compris entre *-PI* et *PI* (~-3.14 to ~3.14), pour le normaliser, nous devons diviser cet angle par *2 x PI*, la macro ```TWO_PI``` en début de code nous permet de stocker cette valeur.
|
|
En divisant l'angle par ```TWO_PI```, nous obtenons un chiffre compris entre *-0.5* (```-PI / TWO_PI```) et *0.5* (```PI / TWO_PI```) auquel il nous suffit d'ajouter 0.5 pour qu'il soit compris entre *0.0* et *1.0*.
|
|
|
|
Le rayon (la distance du centre au fragment) retournera une valeur max de 0.5 en *x* et en *y* (parce que nous calculons la distance depuis le centre du canvas),
|
|
nous devons donc doubler cette valeur (en la multipliant par 2) pour obtenir un *rayon* compris entre *0.0* et *1.0*.
|
|
|
|
Vous voyez que tout est question de ramener les valeurs dans l'espace entre *0.0* et *1.0*, un espace *normalisé*.
|
|
|
|
<div class="codeAndCanvas" data="hsb-colorwheel.frag"></div>
|
|
|
|
Essayez les exercices suivants:
|
|
|
|
* Modifiez l'exemple des coordonnées polaires pour faire tourner les couleurs.
|
|
|
|
* Utilisez les fonctions de formes avec les fonctions de conversion HSB vers RGB pour étendre une valeur de teinte spécifique et rétrécir le reste.
|
|
|
|
![William Home Lizars - Red, blue and yellow spectra, with the solar spectrum (1834)](spectrums.jpg)
|
|
|
|
* si vous regardez attentivement la roue des couleurs qu'utilisent les sélecteurs de couleurs (voir l'image ci-dessous), ils affichent un spectre différent basé sur du RYB.
|
|
Par exemple, la couleur opposée au rouge devrait être le vert mais dans nos exemples, c'est le Cyan.
|
|
Pouvez vous trouver un moyen d'arranger ça de manière à obtenir exactement le meme rendu que sur l'image? [indice: c'est un bon moment pour vous servir des fonctions de formes.]
|
|
|
|
![](colorwheel.png)
|
|
|
|
* Lisez [le livre de Josep's Alvers: l'interaction des couleurs](http://www.goodreads.com/book/show/111113.Interaction_of_Color) et servez vous des exemples suivants pour vous entraîner à reproduire ses exemples.
|
|
|
|
<div class="glslGallery" data="160505191155,160505193939,160505200330,160507154604,160507160421" data-properties="clickRun:editor,openFrameIcon:false,showAuthor:false"></div>
|
|
|
|
#### A propos des fonctions et des arguments
|
|
|
|
Avant de passer au chapitre suivant, attardons nous un peu sur les fonctions du dernier exemple.
|
|
Vous remarquerez un ```in``` avant les types des arguments.
|
|
C'est ce qu'on appelle un [*qualifier*](http://www.shaderific.com/glsl-qualifiers/#inputqualifier) et dans ce cas précis, cela signifie que la variable est en lecture seule.
|
|
Dans les exemples suivants, nous verrons qu'il est également possible de donner les *qualifiers* ```out``` et ```inout``` aux variables passées aux fonctions.
|
|
Cette dernière, ```inout```, est équivalente à passer un argument *par référence* en C ; cela nous donne la possibilité de modifier la valeur de la variable passée en argument.
|
|
|
|
```glsl
|
|
int newFunction(in vec4 aVec4, // lecture seule
|
|
out vec3 aVec3, // écriture seule
|
|
inout int aInt); // lecture / écriture
|
|
```
|
|
|
|
Vous ne le savez pas encore et vous pourriez ne pas le croire mais nous avons à présent tout ce qu'il nous faut pour dessiner à peu près n'importe quoi.
|
|
Au prochain chapitre, nous verrons comment combiner ces techniques pour *mélanger* l'espace. Oui... *mélanger* l'espace.
|