Comment ça marche un shader pour l’eau ?

Passons aux explications à propos de l’effet d’eau dont je parle depuis quelques articles maintenant.

Pour réaliser un rendu de ce genre, il faut décomposer le problème en deux sous-problèmes : le reflet et la déformation appliquée au reflet et au "fond" de l’eau. Jettez à nouveau un oeil aux images juste au dessus si ce premier point n’est pas clair pour vous.

Avant même de déformer le reflet et le fond, il faut déjà avoir ces deux "images". On va donc réaliser deux rendus supplémentaires (à chaque image !) et envoyer le résultat dans des textures dédiées (c’est le pompeux "render to texture") et non directement à l’écran.

Pour le rendu du reflet, on va utiliser une caractéristique intéressante qu’offrent les cartes vidéo : le Plane Clipping. L’idée est simple : on demande à la carte de ne rendre que ce qui est au dessus d’un plan arbitraire, le plan étant ici la surface de l’eau.

Exemple sur une scène donnée :

On active le plan de coupe, on "retourne" la caméra (scaling de [1,1,-1]) et on retrouve ça :

Pour le fond de l’eau, il faudrait faire exactement l’inverse, c’est à dire rendre ce qui est de l’autre coté du plan de coupe, avec la caméra à l’endroit. Pour certaines raisons, je préfère rendre la totalité de la scène.

Notez que le résultat est passablement crade, puisque rendu et stocké dans une texture, de résolution assez faible (en général 512×512, bien que ce chiffre soit dépendant de la taille de la fenêtre de rendu). Il est possible d’imaginer beaucoup d’optimisations ici pour réduire le boulot voire supprimer cette passe.

Voilà donc deux nouvelles textures toutes prêtes à être déformées. Ces déformations seront réalisées par un shader, assez simple dans le principe : la première chose que réalise ce shader et une appliquation classique de "normal map", une technique très courante maintenant qui consiste a réaliser l’éclairage au niveau du pixel ("per pixel lighting") et non au niveau du vertex, et ce à partir d’une texture qui donne le "relief" de la face.

Pour réaliser cette normal map, il est possible de se baser sur une photo :

On va adapter cette photo pour lui donner une résolution intéressante (ici 512×512, la photo originale étant bien plus grande que celle qui est visible ici) et la rendre "répétable" (seamless) :

(Cette dernière opération peut être réalisée, par exemple, avec GIMP : filtres "carte" -> "rendre raccordable")

Pour la suite, il existe divers applis capable de générer (plus ou moins bien) une normal map depuis une texture standard. J’utilise pour cet exemple les outils d’ATI, ici avec TGAtoDOT3.exe :

Notez au passage les teintes de bleu/rose cette texture, qui sont caractéristiques des normal maps.
Le shader va donc utiliser cette texture pour "donner du relief" à la surface de l’eau, c’est à dire modifier la "luminosité" de chaque pixel de cette surface.

Je précise que ce dernier point n’a rien à voir avec d’éventuelles déformations, puisque qu’il faut ici faire intervenir une nouvelle texture : la DUDV map. Pour faire court, cette dernière map est la dérivée de la normal map, et à donc pour rôle de déformer le reflet et le fond de l’eau. Là encore, un outil de chez ATI va faire le job (TGAtoDUDV.exe) pour donner ça :

Ici aussi, les couleurs sont représentatives de ce type de map (rouge et jaune).

Le shader utilise donc maintenant :
– le reflet
– le fond de l’eau
– la normal map
– la DUDV map

En "mixant" tout ça (merci M. Fresnel), et en ajoutant un reflet spéculaire pour le soleil, il est possible d’arriver à un rendu tout à fait convaincant pour cette fameuse eau.

Le résumé est simple :
– On génère le reflet
– On génère le fond de l’eau
– On rend la scène comme d’habitude
– On dessine un grand carré qui représente la surface de l’eau, avec le shader activé.

Et pour notre scène, ça donne le résultat suivant :

Sympa, non ?

Si j’arrive à trouver le temps et que ça intéresse du monde ici, je vais tenter demain de créer un petit programme pour tester tout ça, avec la possibilité d’agir sur les différents paramètres et tester de nouvelles textures pour l’eau.


Publié

dans

par

Étiquettes :

Commentaires

20 réponses à “Comment ça marche un shader pour l’eau ?”

  1. Avatar de MetalX
    MetalX

    Petite question: Pour faire le mouvement de l’eau, tu scrolles juste les normal et DUDV maps ? En regardant la video, c’est pas super net, mais on dirait que c’est un peu plus évolué que ça.

  2. Avatar de oxi13
    oxi13

    Vraiment sympatique ton article.
    T’utilise quoi pour importer des models dans ton moteur ? (tu as créé ton propre format ?), de même pour créér une map.

  3. Avatar de Xfennec
    Xfennec

    MetalX: Non, c’est tout à fait ça. La seule chose ici, c’est que les maps ne défilent pas à la même vitesse. C’est ce qui donne cet effet "réaliste" de mouvement.

  4. Avatar de Xfennec
    Xfennec

    oxi13: En ce qui concerne les textures, Raydium n’utilise que du TGA (format sans pertes). Pour les meshes, c’est effectivement un format interne (fichiers .TRI), avec des scripts d’export pour Blender et 3DS. Le format est [très] très simple pour faciliter les opérations d’import, d’export et de traitement par des outils tiers.

  5. Avatar de Bernardo_Guy
    Bernardo_Guy

    Autre question, ne peut t’on pas créer une normal map procédurale, au lieu de la générer avec une bitmap?

  6. Avatar de Xfennec
    Xfennec

    Oui, bien sûr, mais l’idée me semble délicate à mettre en oeuvre.
    Si le but est de faire évoluer la forme des vagues (genre beau temps -> mauvais temps), par exemple, la masse des calculs à réaliser parait énorme (algo de génération de la normal map + calcul de la DUDV, le tout sur des textures de taille assez conséquente) pour faire l’évolution de manière fluide dans le temps. En tous cas, toujours plus que d’interpoler entre deux normal maps+DUDV prévues à l’avance.

    Peut être avait-tu une idée précise en tête ?

  7. Avatar de Gonzague
    Gonzague

    merci, c’était intéressant moi qui ai du faire un projet ( jeu vidéo en 3D ) cette année j’arrive a comprendre ! Bon courage pour la suite !

  8. Avatar de Ze_PilOt
    Ze_PilOt

    à noter que ton passage photo > normal map est completement faux. Ca donne quelque chose simplement parce que c’est plein de bruit. Tu aurais pris une photo de pelage de chat, d’herbe ou en fait de quasi n’importe quoi, ca aurait été pareil.
    Je pense que c’est une partie que tu devrais retravailler 🙂

    Pour qu’elle soit encore plus crédible, tu devrais aussi gérer la refraction, surtout pour de l’eau complement claire (à tient, autre chose à gérer : les particules qui salissent l’eau et la rende trouble selon la profondeur 🙂

  9. Avatar de andre3000
    andre3000

    Yeah…

    Et si tu avais été un vrai warrior, tu aurais modélisé les ondes avec des sinus, des cosinus et la théorie des ondes en milieu élastique.
    Et tu as pensé à l’évaporation de l’eau? Hein ?

    HA !

  10. Avatar de sebastien
    sebastien

    Je suis d’accord avec zepilot sur certains point, ta normal map est "fausse", dans le sens ou elle ne represent plus du tout la surface de ta photo.

    Sur 3ds, j’utilise toujours deux bruits imbriqués dans mon bump pour générer les rides de l’eau. Un bump suffit amplement puisque de toute la surface de ton eau est plate, tu économisera des ressources ( a moins que tu veuilles créer une mer démonté). Je met un premier "bruit" 3ds, puis un second 5 à 10 fois plus "petit" dans le slot du blanc du premier bruit, pour simuler une deuxième "onde".

    Il y a aussi deux cas de figure bien distinct quand tu veux creer de l’eau: un truc peu profond et son inverse. Tout va pour le mieux quand tu ne rencontre pas les deux cas en même temps, mais si tu te retrouves avec un cas qui mixe les deux cas de figure (un bord de plage avec vue sur la haute mer par exemple), ça se corse.

    Le truc chiant, c’est ce que ze pilot appelle "les particules qui salissent l’eau et la rende trouble selon la profondeur", qui serait plutôt a mettre sur le compte du fait que l’eau va filtrer ta lumière, en particulier le rouge, de plus en plus selon son "épaisseur".

    J’ai du me taper une scène comme ça l’année dernière, la meilleur solution que j’ai trouvé c’est avec Vray, le moteur pour 3ds, qui gère ce qu’il appele le "fog", une couleur qui agit un peu comme la couleur que tu vas mettre dans le slot de ta refraction, mais qui s’intensifie selon l’épaisseur de ton volume.

    un de mes tests:

    Le blanc du sable est le même de gauche a droite c’est le fog qui fait le reste (même si la il est un peu trop prononcé).

    Je n’ai foutrement aucune idée de comment adapter ça a un moteur en temps réel, mais dans l’idée c’est ça.

    Bon courage!

  11. Avatar de Bernardo_Guy
    Bernardo_Guy

    non, pas d’idée précise en tête, je n’y connais rien en 3d Temps réèl… Mais je m’y connais assez bien en 3d precalculée, et, comme pour générer le bump d’une surface d’eau, on utilise de preference une texture procédurale de type noise, je me demandasi si on pouvais faire pareil pour du temps réèl.

    Et j’imagine qu’une texture procédurale économise pas mal de mémoire graphique, mais je peux me tromper.
    +++

  12. Avatar de Xfennec
    Xfennec

    Quelques réponses en vrac à certaines réactions :
    "Le passage de la photo à la normal map est faux". Oui non mais on s’en fiche, la photo est uniquement un prétexte pour la génération de la normal map. Et d’ailleurs, une photo d’herbe ou n’importe quel nuage bruité risque effctivement de faire l’affaire aussi bien. Contrairement à du bump mapping, par exemple, le rôle de la normal map ici est particulièrement subtile.
    Pour illustration, l’image utilisée dans cet article est celle qui me donne pour l’instant le résultat le plus sympa pour un rendu type "mer calme", alors que j’ai déjà dû tester une bonne trentaine d’autres photos et de normal+dudv déjà prêtes. Comme quoi …. 🙂

    Autre point, la réfraction est gérée. Je n’ai pas utilisé ce terme pour ne pas avoir 50 commentaires de spécialistes de l’optique pour m’expliquer avec force et détails que la réfraction, tu comprends, c’est plus compliqué que ça … mais quand je parle de "déformation du fond de l’eau", il s’agit bien de ça.

    Enfin, vous avez raison sur le problème de la profondeur : l’eau reste aussi claire quelle que soit la distance parcourue par la lumière dans l’eau. Cherchant dans un premier temps un rendu type "piscine", je n’ai pas activé cette portion de code pour les captures, mais il est possible de résoudre ce problème de deux manières (de ce que j’en sais).
    La première consiste à extraire le ZBuffer de la carte dans une texture lors du rendu du fond de l’eau et d’utiliser ensuite cette info dans le shader. Pour ma part, j’utilise ce dont sebastien parle, c’est à dire le fog OpenGL, avec une astuce tout bête pour que le plan "near" du fog soit à la surface de l’eau, et non à une distance fixe de la caméra. Le "far" évolue en conséquence.

  13. Avatar de moSk
    moSk

    Moi j’y connait rien mais ce que je trouve le plus souvent génant dans les rendus d’eau c’est que les rivages ne sont pas marqués par un liseré d’écume ou des clapotements. Alors je sais pas si c’est possible de simuler ça mais si c’est le cas ce serait un grand plus.

  14. Avatar de Xfennec
    Xfennec

    moSK: Tout à fait ! C’est bien moi aussi le point qui me gêne le plus pour l’instant. J’ai quelques idées en tête, mais d’ici à les appliquer … Même FarCry, qui a un rendu de l’eau très correct donne une impression étrange sur les rivages :

  15. Avatar de KiCK
    KiCK

    par contre lors de ton rendu en plan de coupe, tu n’active pas les ombres? Parce qu’ensuite on se retrouve avec des objets sombre hors de l’eau alors que dans l’eau ils sont clairs. Ca ne choc pas aux premiers coup d’oeil mais ca serait un plus.
    (et cette ile ca en est où?)

    En tout cas chapeau pour les shaders, j’ai regardé un peu dans le code(pour le shader du bump), j’ai rien compris, je suppose que c’est un coup à prendre mais ca n’a pas l’air simple. En regardant un peu plus les normales maps, j’ai trouvé comment les générer avec blender, ce qui peut être assez utiles pour créer des textures de dingues.(en gros on crée un carrelage HD dans blender, on fait un rendu color, un rendu normal map et on applique sur un plan). C’ets un peu long lors de la création, mais après on gagne en qualité.

  16. Avatar de Xfennec
    Xfennec

    KiCK: l’éclairage est activé dans chaque partie du rendu, je ne vois pas trop de quoi tu parles … Si c’est le fait que dans la toute première image de cet article le fond semble plus clair que la surface, c’est un effet souhaité, le modèle étant construit de cette manière.

    Pour le coup des normal maps générées depuis Blender, c’est très intéressant ! Il faudrait qu’on arrive a tester cette fonctionnalité. Tu as un modèle tout pret ?

  17. Avatar de KiCK
    KiCK

    en fait la partie refleté dans l’eau n’a pas les mêmes zones d’ombre.

    pour les normales, je vais essayer de bricoler un truc.

  18. Avatar de Xfennec
    Xfennec

    Ooooh mais tu as raison mon salaud. Les normales me jouent des tours là (le scale -1 n’a pas l’air d’agir sur elles ! ) … Merci 🙂

  19. Avatar de KiCK
    KiCK

    j’ai toujours raison 😮

    Sinon tu as reçu mon mail?

  20. Avatar de KiCK
    KiCK

    dedans yavais ces normales map et color map faites avec blender, ca rend plutôt bien je dirais.

Laisser un commentaire