Guide du débutant pour le codage des graphiques Shaders Partie 2

ShaderToy, que nous avons utilisé dans le didacticiel précédent de cette série, est idéal pour les tests rapides et les expériences, mais il est plutôt limité. Vous ne pouvez pas contrôler les données envoyées au shader, par exemple, entre autres choses. Avoir votre propre environnement où vous pouvez exécuter des shaders signifie que vous pouvez faire toutes sortes d'effets fantaisistes, et que vous pouvez les appliquer à vos propres projets.!

Nous allons utiliser Three.js comme cadre pour exécuter des shaders dans le navigateur. WebGL est l'API Javascript qui nous permettra de rendre les shaders. Three.js facilite ce travail. 

Si vous n'êtes pas intéressé par JavaScript ou la plate-forme Web, ne vous inquiétez pas: nous ne nous concentrerons pas sur les spécificités du rendu Web (mais si vous souhaitez en savoir plus sur le cadre, consultez ce didacticiel). La mise en place de shaders dans le navigateur est le moyen le plus rapide de commencer, mais vous familiariser avec ce processus vous permettra de configurer et d'utiliser facilement des shaders sur la plate-forme de votre choix.. 

La mise en place

Cette section vous guidera dans la configuration locale des shaders. Vous pouvez suivre sans rien télécharger avec ce CodePen pré-construit:

Vous pouvez créer et éditer ceci sur CodePen.

Bonjour Three.js!

Three.js est un framework JavaScript qui gère beaucoup de code standard pour WebGL dont nous aurons besoin pour le rendu de nos shaders. Le moyen le plus simple de commencer consiste à utiliser une version hébergée sur un CDN.. 

Voici un fichier HTML que vous pouvez télécharger et qui contient juste une scène de base de Threejs. 

Essayez de sauvegarder ce fichier sur le disque, puis ouvrez-le dans votre navigateur Web. Vous devriez voir un écran noir. Ce n'est pas très excitant, alors ajoutons un cube pour nous assurer que tout fonctionne. 

Pour créer un cube, nous devons définir sa géométrie et son matériau, puis les ajouter à la scène. Ajouter cet extrait de code sous la mention Ajoutez votre code ici:

géométrie var = nouvelle THREE.BoxGeometry (1, 1, 1); var material = new THREE.MeshBasicMaterial (color: 0x00ff00); // nous le rendons en vert var cube = new THREE.Mesh (géométrie, matériau); // Ajoute-le à l'écran scene.add (cube); cube.position.z = -3; // Déplace le cube en arrière afin que nous puissions le voir.

Nous n'entrerons pas dans les détails dans tout ce code, car nous nous intéressons davantage à la partie shader. Mais si tout se passe bien, vous devriez voir un cube vert au centre de l'écran:

Pendant que nous y sommes, faisons-le tourner. le rendrefonction exécute chaque image. Nous pouvons accéder à la rotation du cube à travers cube.rotation.x (ou .y ou .z). Essayez d’incrémenter cela, afin que votre fonction de rendu ressemble à ceci:

fonction render () cube.rotation.y + = 0.02; requestAnimationFrame (rendu); renderer.render (scène, caméra); 

Défi: Pouvez-vous le faire tourner le long d'un axe différent? Qu'en est-il des deux axes en même temps?

Maintenant que vous avez tout configuré, ajoutons des shaders!

Ajout de Shaders

À ce stade, nous pouvons commencer à réfléchir au processus de mise en œuvre des shaders. Vous êtes susceptible de vous retrouver dans une situation similaire, quelle que soit la plate-forme sur laquelle vous prévoyez d'utiliser des shaders: vous avez tout configuré, et des éléments sont dessinés à l'écran, maintenant, comment accéder au GPU?

Étape 1: Chargement dans le code GLSL

Nous utilisons JavaScript pour construire cette scène. Dans d'autres situations, vous utiliserez peut-être C ++, Lua ou tout autre langage. Les shaders, peu importe, sont écrits dans un spécial Langue d'ombrageLe langage d’ombrage d’OpenGL est GLSL(OuvrirGL Savoir Language). Puisque nous utilisons WebGL, qui est basé sur OpenGL, GLSL est ce que nous utilisons..

Alors, comment et où écrit-on notre code GLSL? La règle générale est que vous voulez charger votre code GLSL en tant que chaîne. Vous pouvez ensuite l'envoyer pour qu'il soit analysé et exécuté par le GPU.. 

En JavaScript, vous pouvez le faire en jetant simplement tout votre code en ligne dans une variable comme ceci:

var shaderCode = "Tout votre code de shader ici;"

Cela fonctionne, mais comme JavaScript ne permet pas de créer facilement des chaînes multilignes, cela n’est pas très pratique pour nous. La plupart des gens ont tendance à écrire le code shader dans un fichier texte et à lui donner une extension de .glsl ou .frag (court pour fragment shader), puis chargez simplement ce fichier dans.

Ceci est valable, mais nous allons écrire notre code de shader dans un nouveau

Nous lui donnons l'ID de fragShader est pour que nous puissions y accéder plus tard. Le type code shader est en fait un type de script bidon qui n'existe pas. (Vous pouvez y mettre n'importe quel nom et cela fonctionnerait). Nous faisons cela pour que le code ne soit pas exécuté et ne soit pas affiché dans le code HTML..

Maintenant, jetons dans un shader très basique qui ne fait que rendre blanc.

(Les composants de vec4 dans ce cas correspondent à la valeur de rgba, comme expliqué dans le tutoriel précédent.)

Enfin, nous devons charger dans ce code. Nous pouvons le faire avec une simple ligne JavaScript qui trouve l'élément HTML et extrait le texte intérieur:

var shaderCode = document.getElementById ("fragShader"). innerHTML;

Cela devrait aller sous votre code de cube.

Rappelez-vous: seuls les éléments chargés en tant que chaîne seront analysés en tant que code GLSL valide (c'est-à-dire, void main () …. Le reste n'est que du code HTML.) 

Vous pouvez créer et éditer ceci sur CodePen.

Étape 2: Appliquer le shader

La méthode d'application du shader peut varier selon la plate-forme utilisée et l'interface avec le GPU. Ce n'est jamais une étape compliquée, cependant, et une recherche rapide dans Google nous montre comment créer un objet et y appliquer des shaders avec Three.js.

Nous devons créer un matériau spécial et lui attribuer notre code shader. Nous allons créer un plan en tant qu'objet shader (mais nous pourrions tout aussi bien utiliser le cube). C'est tout ce que nous devons faire:

// Créer un objet pour appliquer les shaders à var material = new THREE.ShaderMaterial (fragmentShader: shaderCode) var geometry = new THREE.PlaneGeometry (10, 10); var sprite = new THREE.Mesh (géométrie, matériau); scene.add (sprite); sprite.position.z = -1; // Déplacez-le afin que nous puissions le voir.

A présent, vous devriez voir un écran blanc:

Vous pouvez créer et éditer ceci sur CodePen.


Si vous modifiez le code dans le shader en l’ajustant à une autre couleur, vous devriez voir la nouvelle couleur.!

Défi: Pouvez-vous définir une partie de l'écran en rouge et une autre partie en bleu? (Si vous êtes bloqué, la prochaine étape devrait vous donner un indice!)

Étape 3: envoi de données

À ce stade, nous pouvons faire ce que nous voulons avec notre shader, mais nous n’avons pas grand chose à dire. pouvez faire. Nous avons seulement la position de pixel intégrée gl_FragCoord pour travailler avec, et si vous vous en souvenez, ce n'est pas normalisé. Nous devons avoir au moins les dimensions de l'écran. 

Pour envoyer des données à notre shader, nous devons l’envoyer comme ce qu’on appelle un uniforme variable. Pour ce faire, nous créons un objet appelé les uniformes et y ajouter nos variables. Voici la syntaxe pour envoyer la résolution:

uniformes var = ; uniforms.resolution = type: 'v2', valeur: new THREE.Vector2 (window.innerWidth, window.innerHeight);
Chaque variable uniforme doit avoir un type et un valeur. Dans ce cas, c'est un vecteur à 2 dimensions avec la largeur et la hauteur de la fenêtre comme coordonnées. Le tableau ci-dessous (tiré de la documentation Three.js) répertorie tous les types de données que vous pouvez envoyer et leurs identificateurs:
Chaîne de type uniforme Type GLSL Type JavaScript
'i', '1i'
int
Nombre
'f', '1f' flotte
Nombre
'v2'
vec2
TROIS.Vector2
'v3'
vec3
TROIS.Vecteurs3
'c' vec3
TROIS.Couleur
'v4' vec4
TROIS.Vector4
'm3' mat3
TROIS.Matrix3
'm4' mat4
TROIS.Matrix4
't' sampler2D
TROIS.Texture
't' samplerCube
TROIS.CubeTexture
Pour l’envoyer au shader, modifiez le Matière Shader instiateur pour l'inclure, comme ceci:
var material = new THREE.ShaderMaterial (uniformes: uniformes, fragmentShader: shaderCode)

Nous n'avons pas encore fini! Maintenant que notre shader reçoit cette variable, nous devons en faire quelque chose. Créons un dégradé de la même manière que dans le didacticiel précédent: en normalisant nos coordonnées et en les utilisant pour créer notre valeur de couleur.

Modifiez votre code de shader pour qu'il ressemble à ceci:

uniform vec2 resolution; // les variables uniformes doivent être déclarées ici en premier void main () // Nous pouvons maintenant normaliser notre coordonnée vec2 pos = gl_FragCoord.xy / resolution.xy; // Et crée un dégradé! gl_FragColor = vec4 (1.0, pos.x, pos.y, 1.0); 

Et vous devriez voir un joli dégradé!

Vous pouvez créer et éditer ceci sur CodePen.

Si vous êtes un peu confus quant à la manière dont nous avons réussi à créer un si joli dégradé avec seulement deux lignes de code shader, consultez la première partie de cette série de didacticiels pour une analyse en profondeur de la logique sous-jacente..

Défi: Pouvez-vous diviser l'écran en 4 sections égales avec des couleurs différentes? Quelque chose comme ça:

Étape 4: Mise à jour des données

C'est bien de pouvoir envoyer des données à notre shader, mais que se passe-t-il si nous devons les mettre à jour? Par exemple, si vous ouvrez l'exemple précédent dans un nouvel onglet, puis redimensionnez la fenêtre, le dégradé ne se met pas à jour car il utilise toujours les dimensions d'écran initiales..

Pour mettre à jour vos variables, vous devez généralement renvoyer la variable uniforme et la mettre à jour. Avec Three.js, cependant, il suffit de mettre à jour le les uniformes objet dans notre rendre fonction-pas besoin de le renvoyer au shader. 

Voici donc à quoi ressemble notre fonction de rendu après avoir effectué ce changement:

fonction render () cube.rotation.y + = 0.02; uniforms.resolution.value.x = window.innerWidth; uniforms.resolution.value.y = window.innerHeight; requestAnimationFrame (rendu); renderer.render (scène, caméra); 

Si vous ouvrez le nouveau CodePen et redimensionnez la fenêtre, les couleurs changent (bien que la taille initiale de la fenêtre reste identique). Il est plus facile de voir cela en regardant les couleurs dans chaque coin pour vérifier qu'elles ne changent pas.

Remarque: Envoyer des données au GPU de cette manière est généralement coûteux. L'envoi d'une poignée de variables par image est acceptable, mais votre débit d'images peut vraiment ralentir si vous envoyez des centaines par image. Cela ne semble peut-être pas être un scénario réaliste, mais si vous avez plusieurs centaines d’objets à l’écran et que tous doivent être éclairés, par exemple avec des propriétés différentes, les choses peuvent rapidement devenir incontrôlables. Nous en apprendrons plus sur l'optimisation de nos shaders dans de futurs articles.!

Défi: Pouvez-vous faire changer les couleurs au fil du temps? (Si vous êtes bloqué, voyez comment nous l'avons fait dans la première partie de cette série de tutoriels.)

Étape 5: Traitement des textures

Quelle que soit la manière dont vous chargez vos textures ou dans quel format, vous les enverrez à votre shader de la même manière sur toutes les plateformes, sous forme de variables uniformes..

Une note rapide sur le chargement de fichiers en JavaScript: vous pouvez charger des images à partir d’une URL externe sans trop de peine (ce que nous allons faire ici), mais si vous voulez charger une image localement, vous rencontrerez des problèmes d’autorisation, car JavaScript ne peut pas, et ne devrait pas, accéder normalement aux fichiers de votre système. Le moyen le plus simple de contourner ce problème est de démarrer un serveur Python local, ce qui est plus simple qu'il n'y paraît peut-être..

Three.js nous fournit une petite fonction pratique pour charger une image en tant que texture:

THREE.ImageUtils.crossOrigin = "; // Nous permet de charger une image externe var tex = THREE.ImageUtils.loadTexture (" https://tutsplus.github.io/Beginners-Guide-to-Shaders/Part2/SIPI_Jelly_Beans.jpg ");

La première ligne doit juste être définie une fois. Vous pouvez y placer n'importe quelle URL vers une image. 

Ensuite, nous voulons ajouter notre texture à la les uniformes objet.

uniforms.texture = type: 't', valeur: tex;

Enfin, nous voulons déclarer notre variable uniforme dans notre code shader et la dessiner de la même manière que dans le didacticiel précédent, avec texture2D une fonction:

résolution uniforme vec2; texture uniforme sampler2D; void main () vec2 pos = gl_FragCoord.xy / resolution.xy; gl_FragColor = texture2D (texture, pos); 

Et vous devriez voir de délicieux bonbons à la gelée, étirés sur notre écran:

Vous pouvez créer et éditer ceci sur CodePen.

(Cette image est une image de test standard dans le domaine de l'infographie, prise par le Signal and Image Processing Institute de l'Université de Californie du Sud (d'où les initiales IPI). )

Défi: Pouvez-vous faire passer la texture de la couleur à l’échelle de gris au fil du temps? (Encore une fois, si vous êtes bloqué, nous l'avons fait dans la première partie de cette série.)

Étape de bonus: appliquer des shaders à d'autres objets

L'avion que nous avons créé n'a rien de spécial. Nous aurions pu appliquer tout cela sur notre cube. En fait, nous pouvons simplement changer la ligne de géométrie plane:

var geometry = new THREE.PlaneGeometry (10, 10);

à:

géométrie var = nouvelle THREE.BoxGeometry (1, 1, 1);

Voilà, les bonbons sur un cube:

Vous pouvez créer et éditer ceci sur CodePen.

Maintenant, vous pensez peut-être, "Attends, ça ne ressemble pas à une projection correcte d'une texture sur un cube!". Et vous auriez raison; Si nous regardons en arrière dans notre shader, nous verrons que tout ce que nous avons réellement fait a été de dire "mapper tous les pixels de cette image sur l'écran". Le fait que ce soit sur un cube signifie simplement que les pixels extérieurs sont supprimés.. 

Si vous vouliez l'appliquer de manière à ce qu'il ressemble physiquement au cube, cela impliquerait de réinventer un moteur 3D (ce qui semble un peu ridicule si nous utilisons déjà un moteur 3D et nous pouvons simplement le demander à dessinez la texture de chaque côté individuellement). Cette série de didacticiels traite davantage de l'utilisation de shaders pour faire des choses que nous ne pourrions pas réaliser autrement, nous n'entrerons donc pas dans des détails comme celui-ci. (Udacity propose un excellent cours sur les bases du graphisme 3D, si vous souhaitez en savoir plus!)

Prochaines étapes

À ce stade, vous devriez être capable de faire tout ce que nous avons fait dans ShaderToy, sauf que vous avez maintenant la liberté d'utiliser toutes les textures que vous voulez sur toutes les formes que vous aimez et, espérons-le, sur la plate-forme de votre choix.. 

Avec cette liberté, nous pouvons maintenant configurer un système d’éclairage, avec des ombres et des reflets réalistes. C’est ce sur quoi la prochaine partie se concentrera, ainsi que des astuces et des techniques d’optimisation des shaders.!