Dans la
deuxième partie, nous avons examiné les lignes de flottabilité et de mousse. Dans cette dernière partie, nous appliquons la distorsion sous-marine comme effet de post-traitement.
Effets de réfraction et de post-traitement
Notre objectif est de transmettre visuellement la réfraction de la lumière dans l'eau. Nous avons déjà
expliqué comment créer ce type de distorsion dans un fragment shader pour une scène 2D. Ici, la seule différence est que nous devons comprendre quelle zone de l'écran est sous l'eau et lui appliquer uniquement de la distorsion.
Post-traitement
Dans le cas général, l'effet de post-traitement est tout effet appliqué à la scène entière après son rendu, par exemple des nuances de couleur ou l'
effet d'un ancien écran CRT . Au lieu de rendre la scène directement à l'écran, nous la rendons d'abord dans le tampon ou la texture, puis, en passant la scène à travers notre shader, nous la rendons à l'écran.
Dans PlayCanvas, vous pouvez personnaliser cet effet de post-traitement en créant un nouveau script. Appelons-le
Refraction.js et copions ce modèle dedans comme un blanc:
Ceci est similaire à un script standard, mais nous définissons une classe
RefractionPostEffect
qui peut être appliquée à la caméra. Pour le rendu, il a besoin de vertex et de fragment shaders. Les attributs sont déjà configurés, créons donc
Refraction.frag avec le contenu suivant:
precision highp float; uniform sampler2D uColorBuffer; varying vec2 vUv0; void main() { vec4 color = texture2D(uColorBuffer, vUv0); gl_FragColor = color; }
Et
Refraction.vert avec un vertex shader de base:
attribute vec2 aPosition; varying vec2 vUv0; void main(void) { gl_Position = vec4(aPosition, 0.0, 1.0); vUv0 = (aPosition.xy + 1.0) * 0.5; }
Attachez maintenant le script
Refraction.js à la caméra et attribuez les attributs appropriés aux shaders. Lorsque vous démarrez le jeu, vous verrez la scène de la même manière qu'auparavant. Il s'agit d'un post-effet vide qui restitue simplement la scène. Pour nous assurer que cela fonctionne, essayons de donner à la scène une teinte rouge.
Au lieu de simplement renvoyer la couleur à Refraction.frag, essayez de définir le composant rouge sur 1.0, ce qui devrait donner à l'image l'image ci-dessous.
Shader de distorsion
Pour créer une distorsion animée, nous devons ajouter une variable de temps uniforme, nous allons donc la créer à l'intérieur de ce constructeur post-effet dans Refraction.js:
var RefractionPostEffect = function (graphicsDevice, vs, fs) { var fragmentShader = "precision " + graphicsDevice.precision + " float;\n"; fragmentShader = fragmentShader + fs;
Maintenant à l'intérieur de la fonction de rendu, nous la passons à notre shader pour l'augmenter:
RefractionPostEffect.prototype = pc.extend(RefractionPostEffect.prototype, {
Maintenant, nous pouvons utiliser le même code de shader du didacticiel sur la distorsion de l'eau, transformant notre shader de fragment complet en ce qui suit:
precision highp float; uniform sampler2D uColorBuffer; uniform float uTime; varying vec2 vUv0; void main() { vec2 pos = vUv0; float X = pos.x*15.+uTime*0.5; float Y = pos.y*15.+uTime*0.5; pos.y += cos(X+Y)*0.01*cos(Y); pos.x += sin(XY)*0.01*sin(Y); vec4 color = texture2D(uColorBuffer, pos); gl_FragColor = color; }
Si tout est fait correctement, alors l'image entière devrait ressembler à complètement sous l'eau.
Tâche 1: assurez-vous que la distorsion ne s'applique qu'au bas de l'écran.
Masques de caméra
Nous avons presque fini. Il nous reste à appliquer cet effet de distorsion à la partie sous-marine de l'écran. La façon la plus simple à laquelle j'ai pensé est de restituer la scène avec la surface de l'eau rendue en blanc uni, comme le montre la figure ci-dessous.
Il restituera la texture que nous utilisons comme masque. Ensuite, nous transférerons cette texture dans notre shader de réfraction, ce qui ne déformera le pixel de l'image finie que lorsque le pixel correspondant dans le masque sera blanc.
Ajoutons un attribut booléen à la surface de l'eau pour savoir s'il est utilisé comme masque. Ajoutez ce qui suit à Water.js:
Water.attributes.add('isMask', {type:'boolean',title:"Is Mask?"});
Ensuite, comme d'habitude, nous pouvons le passer au shader en utilisant
material.setParameter('isMask',this.isMask);
. Déclarez-le ensuite dans Water.frag et coloriez le pixel en blanc si l'attribut est vrai.
Assurez-vous que cela fonctionne en activant la propriété «Is Mask?». dans l'éditeur et redémarrer le jeu. Il devrait être blanc, comme dans l'image ci-dessus.
Maintenant, pour restituer la scène, nous avons besoin d'une deuxième caméra. Créez une nouvelle caméra dans l'éditeur et appelez-la
CameraMask . Nous dupliquons également l'entité Water dans l'éditeur et
nommons le WaterMask en double. Assurez-vous que l'entité "L'eau est un masque?" est faux et WaterMask est vrai.
Pour commander le rendu d'une nouvelle caméra sur une texture plutôt que sur un écran, créez un nouveau script
CameraMask.js et attachez-le à la nouvelle caméra. Nous créons un
RenderTarget pour capturer la sortie de cette caméra:
Maintenant, après avoir lancé l'application, vous verrez que cette caméra ne s'affiche plus à l'écran. Nous pouvons obtenir la sortie de son rendu cible dans
Refraction.js comme suit:
Refraction.prototype.initialize = function() { var cameraMask = this.app.root.findByName('CameraMask'); var maskBuffer = cameraMask.camera.renderTarget.colorBuffer; var effect = new pc.RefractionPostEffect(this.app.graphicsDevice, this.vs.resource, this.fs.resource, maskBuffer);
Notez que je passe cette texture de masque comme argument au constructeur post-effet. Nous devons créer un lien vers celui-ci dans notre constructeur, il ressemblera à ceci:
Enfin, dans la fonction de rendu, nous passons le tampon à notre shader:
scope.resolve("uMaskBuffer").setValue(this.buffer);
Maintenant, pour m'assurer que tout cela fonctionne, je vous le laisse comme tâche.
Tâche 2: restituez le uMaskBuffer à l'écran pour vous assurer qu'il s'agit bien de la sortie de la deuxième caméra.
Les éléments suivants doivent être pris en compte: le rendu cible est configuré dans l'initialisation du script CameraMask.js et il doit être prêt au moment de l'appel de Refraction.js. Si les scripts fonctionnent différemment, nous obtenons une erreur. Pour vous assurer qu'ils fonctionnent dans le bon ordre, faites glisser CameraMask en haut de la liste d'entités dans l'éditeur, comme indiqué ci-dessous.
La deuxième caméra doit toujours avoir la même vue que la caméra d'origine, nous devons donc toujours suivre la position et la rotation du script CameraMask.js dans la mise à jour:
CameraMask.prototype.update = function(dt) { var pos = this.CameraToFollow.getPosition(); var rot = this.CameraToFollow.getRotation(); this.entity.setPosition(pos.x,pos.y,pos.z); this.entity.setRotation(rot); };
Dans l'initialisation, définissez
CameraToFollow
:
this.CameraToFollow = this.app.root.findByName('Camera');
Masques d'écrêtage
Les deux caméras rendent maintenant la même chose. Nous voulons que la caméra masque rende tout sauf l'eau réelle, et la vraie caméra rende tout sauf l'eau masque.
Pour ce faire, nous pouvons utiliser le masque d'écrêtage de la caméra. Il fonctionne de manière similaire
aux masques anti-collision . Un objet sera découpé (c'est-à-dire non rendu) si le résultat d'un
AND
du bit entre son masque et le masque de la caméra est 1.
Supposons que Water a le bit 2, WaterMask a le bit 3. Tous les bits sauf 3 doivent être définis pour une caméra réelle et tous les bits sauf 2 pour une caméra masque. La façon la plus simple de dire «tous les bits sauf N» est la suivante façon:
~(1 << N) >>> 0
En savoir plus sur les opérations au niveau du bit
ici .
Pour configurer les masques d'écrêtage de caméra, nous pouvons insérer ce qui suit au bas de l'initialisation du script
CameraMask.js :
Maintenant, dans Water.js, nous allons définir le bit 2 du masque du maillage Water, et sa version de masque sur le bit 3:
Maintenant, une espèce sera avec de l'eau claire et la seconde avec de l'eau blanche solide. L'image de gauche montre la vue de la caméra d'origine et à droite la vue de la caméra de masque.
Application de masque
Et maintenant la dernière étape! Nous savons que les zones sous-marines sont marquées de pixels blancs. Nous avons juste besoin de vérifier si nous sommes dans un pixel blanc, et sinon, désactivez la distorsion dans
Refraction.frag :
Et cela devrait résoudre notre problème!
Il convient également de noter que, puisque la texture du masque est initialisée au démarrage, lorsque vous redimensionnez la fenêtre au moment de l'exécution, elle ne correspondra plus à la taille de l'écran.Lissage
Vous remarquerez peut-être que les bords de la scène sont maintenant un peu nets. Cela est arrivé car après avoir appliqué le post-effet, nous avons perdu le lissage.
Nous pouvons appliquer un lissage supplémentaire au-dessus de notre effet comme un autre post-effet. Heureusement, il existe une autre variable dans le
magasin PlayCanvas que nous pouvons utiliser. Accédez à la
page des actifs de script , cliquez sur le gros bouton de téléchargement vert et sélectionnez votre projet dans la liste qui apparaît. Le script apparaîtra à la racine de la fenêtre Actifs sous le nom
posteffect-fxaa.js . Attachez-le simplement à l'entité Appareil photo et votre scène commencera à paraître beaucoup mieux!
Réflexions en conclusion
Si vous arrivez ici, vous pouvez vous féliciter! Dans ce didacticiel, nous avons couvert plusieurs techniques. Vous devez maintenant vous sentir en confiance lorsque vous travaillez avec des vertex shaders, le rendu dans les textures, l'application d'effets de post-traitement, l'écrêtage sélectif d'objets, l'utilisation du tampon de profondeur et le travail avec le mélange et la transparence. Bien que nous ayons implémenté tout cela dans PlayCanvas, vous pouvez rencontrer tous ces concepts généraux de l'infographie sous une forme ou une autre sur n'importe quelle plate-forme.
Toutes ces techniques sont également applicables à de nombreux autres effets. Une application particulièrement intéressante trouvée pour les vertex shaders, que j'ai trouvée dans le
rapport sur le graphique Abzu , où les développeurs expliquent comment ils ont utilisé les vertex shaders pour animer efficacement des dizaines de milliers de poissons à l'écran.
Maintenant, vous avez un bel effet d'eau que vous pouvez appliquer dans vos jeux! Vous pouvez le personnaliser et ajouter vos propres détails. Beaucoup plus peut être fait avec de l'eau (je n'ai même pas mentionné de types de reflets). Voici quelques idées.
Ondes sonores
Au lieu d'animer simplement les vagues avec une combinaison de cosinus et de sinus, vous pouvez échantillonner la texture de sorte que les vagues semblent un peu plus naturelles et moins prévisibles.
Traces dynamiques de mousse
Au lieu de lignes d'eau complètement statiques à la surface, vous pouvez dessiner dans la texture lorsque vous déplacez des objets pour créer des traces dynamiques de mousse. Cela peut être fait de différentes manières, de sorte que cette tâche en soi peut devenir un projet.
Code source
Le projet PlayCanvas terminé peut être trouvé
ici . Notre référentiel possède également un
port de projet sous Three.js .