Construire des shaders avec Babylon.js et WebGL théorie et exemples

Dans le discours liminaire du deuxième jour de // Build 2014 (voir 2: 24-2: 28), les évangélistes de Microsoft, Steven Guggenheimer et John Shewchuk, ont présenté en démonstration comment l’aide d’Oculus Rift avait été ajoutée à Babylon.js. Et l'un des éléments clés de cette démo a été le travail que nous avons effectué sur un shader spécifique pour simuler des objectifs, comme vous pouvez le voir sur cette image:

J'ai également présenté une session avec Frank Olivier et Ben Constable à propos des graphiques sur IE et Babylon.js. 

Ceci m’amène à l’une des questions que les gens me posent souvent à propos de Babylon.js: "Que voulez-vous dire par shaders?"Dans cet article, je vais vous expliquer le fonctionnement des shaders et vous donner quelques exemples de types courants de shaders..

La théorie

Avant de commencer à expérimenter, nous devons d'abord voir comment les choses fonctionnent en interne.

En ce qui concerne la 3D à accélération matérielle, nous discutons de deux processeurs: le processeur principal et le GPU. Le GPU est une sorte de CPU extrêmement spécialisé.

Le GPU est une machine à états que vous avez configurée à l'aide de la CPU. Par exemple, le processeur va configurer le GPU pour qu'il rende des lignes au lieu de triangles. Ou il va définir que la transparence est activée, et ainsi de suite.

Une fois que tous les états sont définis, la CPU définit ce qu’il faut rendre: la géométrie, composée d’une liste de points (appelée sommets et stocké dans un tableau appelé vertex buffer) et une liste d’index (les faces, ou triangles, stockés dans un tableau appelé tampon d'index).

La dernière étape pour la CPU consiste à définir le mode de rendu de la géométrie. Pour cette tâche spécifique, la CPU définira shaders pour le GPU. Les shaders sont un morceau de code que le GPU exécutera pour chacun des sommets et des pixels qu'il doit restituer..

Premièrement, un peu de vocabulaire: considérez un sommet (sommets quand il y en a plusieurs) comme un «point» dans un environnement 3D (par opposition à un point dans un environnement 2D).

Il existe deux types de shaders: les vertex shaders et les pixels (ou fragments)..

Pipeline graphique

Avant de creuser dans les shaders, faisons un pas en arrière. Pour restituer les pixels, le GPU prendra la géométrie définie par le CPU et procédera comme suit:

À l'aide du tampon d'index, trois sommets sont rassemblés pour définir un triangle: le tampon d'index contient une liste d'indices de sommets. Cela signifie que chaque entrée dans la mémoire tampon d'index est le numéro d'un sommet dans la mémoire tampon de vertex. Ceci est vraiment utile pour éviter la duplication de sommets. 

Par exemple, le tampon d'index suivant est une liste de deux faces: [1 2 3 1 3 4]. La première face contient le sommet 1, le sommet 2 et le sommet 3. La deuxième face contient le sommet 1, le sommet 3 et le sommet 4. Il y a donc quatre sommets dans cette géométrie: 

Le vertex shader est appliqué sur chaque sommet du triangle. L'objectif principal du vertex shader est de produire un pixel pour chaque sommet (la projection sur l'écran 2D du sommet 3D): 

En utilisant ces trois pixels (qui définissent un triangle 2D sur l’écran), le GPU interpolera toutes les valeurs attachées au pixel (au moins sa position) et le pixel shader sera appliqué sur chaque pixel inclus dans le triangle 2D afin de: générer une couleur pour chaque pixel: 

Ce processus est effectué pour chaque visage défini par le tampon d'index.. 

De toute évidence, en raison de sa nature parallèle, le processeur graphique est capable de traiter cette étape pour plusieurs visages simultanément et d'obtenir ainsi de très bonnes performances..

GLSL

Nous venons de voir que pour rendre les triangles, le processeur graphique a besoin de deux shaders: le vertex shader et le pixel shader. Ces shaders sont écrits en utilisant un langage appelé GLSL (Graphics Library Shader Language). Il ressemble à c.

Pour Internet Explorer 11, nous avons développé un compilateur pour transformer GLSL en HLSL (langage de shader de haut niveau), qui est le langage de shader de DirectX 11. Cela permet à IE11 de s’assurer que le code de shader est sûr (vous ne voulez pas utiliser WebGL pour réinitialiser votre ordinateur!):

Voici un exemple d'un vertex shader commun:

float highp de précision; // Attributs attribut vec3 position; attribut vec2 uv; // Uniformes uniformes mat4 worldViewProjection; // Variation variable vec2 vUV; void main (void) gl_Position = worldViewProjection * vec4 (position, 1.0); VUV = UV; 

Structure du vertex shader

Un vertex shader contient les éléments suivants:

  • Les attributs: Un attribut définit une partie d'un sommet. Par défaut, un sommet doit au moins contenir une position (un vecteur3: x, y, z). Mais en tant que développeur, vous pouvez décider d'ajouter plus d'informations. Par exemple, dans l’ancien shader, il existe un vecteur2 nommé uv (coordonnées de texture qui nous permettent d'appliquer une texture 2D sur un objet 3D).
  • Uniformes: Un uniforme est une variable utilisée par le shader et définie par le CPU. Le seul uniforme que nous avons ici est une matrice utilisée pour projeter la position du sommet (x, y, z) à l'écran (x, y).
  • Variant: Les variables variables sont des valeurs créées par le vertex shader et transmises au pixel shader. Ici, le vertex shader transmettra une VUV (une simple copie de uv) valeur au pixel shader. Cela signifie qu'un pixel est défini ici avec une position et des coordonnées de texture. Ces valeurs seront interpolées par le GPU et utilisées par le pixel shader. 
  • principale: La fonction nommée principale() est le code exécuté par le GPU pour chaque sommet et doit au moins produire une valeur pour gl_position (la position sur l'écran du sommet actuel). 

Nous pouvons voir dans notre échantillon que le vertex shader est assez simple. Il génère une variable système (commençant par gl_) nommé gl_position définir la position du pixel associé, et définit une variable variable appelée VUV

Le vaudou derrière les matrices

Dans notre shader, nous avons une matrice nommée worldViewProjection. Nous utilisons cette matrice pour projeter la position du sommet sur la gl_position variable. C'est cool, mais comment pouvons-nous obtenir la valeur de cette matrice? C'est un uniforme, il faut donc le définir côté CPU (en utilisant JavaScript).

C'est l'une des parties complexes de la 3D. Vous devez comprendre les mathématiques complexes (ou vous devrez utiliser un moteur 3D, comme Babylon.js, que nous verrons plus tard).

le worldViewProjection La matrice est la combinaison de trois matrices différentes:

L'utilisation de la matrice résultante nous permet de transformer des sommets 3D en pixels 2D tout en tenant compte du point de vue et de tout ce qui concerne la position / l'échelle / la rotation de l'objet en cours..

C’est votre responsabilité en tant que développeur 3D: créer et maintenir cette matrice à jour.

Retour aux Shaders

Une fois que le vertex shader est exécuté sur chaque sommet (trois fois, ensuite), nous avons trois pixels avec un gl_position et un VUV valeur. Le GPU interpolera ensuite ces valeurs sur chaque pixel contenu dans le triangle produit par ces pixels..

Ensuite, pour chaque pixel, il exécutera le pixel shader:

float highp de précision; variant vec2 vUV; sampler2D textureSampler uniforme; void main (void) gl_FragColor = texture2D (textureSampler, vUV); 

Structure de shader de pixel (ou fragment)

La structure d'un pixel shader est similaire à un vertex shader:

  • Variant: Les variables variables sont des valeurs créées par le vertex shader et transmises au pixel shader. Ici, le pixel shader recevra un VUV valeur du vertex shader. 
  • Uniformes: Un uniforme est une variable utilisée par le shader et définie par le CPU. Le seul uniforme que nous avons ici est un échantillonneur, qui est un outil utilisé pour lire les couleurs de texture.
  • principale: La fonction nommée principale est le code exécuté par le GPU pour chaque pixel et doit au moins produire une valeur pour gl_FragColor (la couleur du pixel actuel). 

Ce pixel shader est assez simple: il lit la couleur de la texture en utilisant les coordonnées de texture du vertex shader (qui à son tour l'a obtenue du vertex).

Voulez-vous voir le résultat d'un tel shader? C'est ici:

Ceci est rendu en temps réel; vous pouvez faire glisser la sphère avec votre souris.

Pour atteindre ce résultat, vous devrez faire face à une lot de code WebGL. En effet, WebGL est une API très puissante, mais de bas niveau, et vous devez tout faire vous-même, de la création des tampons à la définition des structures de vertex. Vous devez également faire tous les calculs et définir tous les états et gérer le chargement de texture, etc.

Trop dur? BABYLON.ShaderMatériel à la rescousse

Je sais ce que vous pensez: les shaders sont vraiment cool, mais je ne veux pas m'embêter avec la plomberie interne de WebGL ni même avec les mathématiques.

Et c'est bon! C’est une demande parfaitement légitime, et c’est exactement pourquoi j’ai créé Babylon.js.

Permettez-moi de vous présenter le code utilisé par la précédente démo Rolling Sphere. Tout d’abord, vous aurez besoin d’une simple page Web:

   Babylon.js          

Vous remarquerez que les shaders sont définis par > Mots clés. Avec Babylon.js, vous pouvez également les définir dans des fichiers séparés (.fx des dossiers).

Vous pouvez obtenir Babylon.js ici ou sur notre dépôt GitHub. Vous devez utiliser la version 1.11 ou supérieure pour avoir accès à BABYLON.Matériel standard.

Et enfin le code JavaScript principal est le suivant:

"use strict"; document.addEventListener ("DOMContentLoaded", startGame, false); fonction startGame () if (BABYLON.Engine.isSupported ()) var canvas = document.getElementById ("renderCanvas"); var engine = new BABYLON.Engine (canvas, false); var scene = new BABYLON.Scene (moteur); var camera = nouvelle BABYLON.ArcRotateCamera ("Caméra", 0, Math.PI / 2, 10, BABYLON.Vector3.Zero (), scène); camera.attachControl (toile); // Création d'une sphère var sphere = BABYLON.Mesh.CreateSphere ("Sphere", 16, 5, scene); var amigaMaterial = new BABYLON.ShaderMaterial ("amiga", scène, vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode",, attributs: ["position", "uv"], uniformes: ["worldViewProjection"]] ) amigaMaterial.setTexture ("textureSampler", nouveau BABYLON.Texture ("amiga.jpg", scène)); sphere.material = amigaMaterial; engine.runRenderLoop (function () sphere.rotation.y + = 0.05; scene.render ();); ;

Vous pouvez voir que j'utilise un BABYLON.Matériel Shader se débarrasser de tout le fardeau de la compilation, de la liaison et du traitement des shaders.

Lorsque vous créez un BABYLON.Matériel Shader, vous devez spécifier l'élément DOM utilisé pour stocker les shaders ou le nom de base des fichiers contenant les shaders. Si vous choisissez d'utiliser des fichiers, vous devez créer un fichier pour chaque shader et utiliser le modèle de nom de fichier suivant: nom_base.vertex.fx et basename.fragment.fx. Ensuite, vous devrez créer le matériau comme ceci:

var cloudMaterial = new BABYLON.ShaderMaterial ("cloud", scène, "./myShader", attributs: ["position", "uv"], uniformes: ["worldViewProjection"]);

Vous devez également spécifier les noms des attributs et des uniformes que vous utilisez. Ensuite, vous pouvez définir directement la valeur de vos uniformes et de vos échantillonneurs à l'aide du setTexture, setFloat, setFloats, setColor3, setColor4, setVector2, setVector3, setVector4, et setMatrix les fonctions.

Assez simple, droit?

Vous souvenez-vous de la précédente worldViewProjection matrice? Utiliser Babylon.js et BABYLON.Matériel Shader, Vous n'avez rien à craindre! le BABYLON.Matériel Shader va automatiquement le calculer pour vous parce que vous le déclarez dans la liste des uniformes.

BABYLON.Matériel Shader peut également gérer les matrices suivantes pour vous:

  • monde 
  • vue 
  • projection 
  • worldView 
  • worldViewProjection 

Pas besoin de maths plus longtemps. Par exemple, chaque fois que vous exécutez sphère.rotation.y + = 0.05, la matrice mondiale de la sphère est générée pour vous et transmise au GPU.

CYOS: créez votre propre shader

Alors allons plus gros et créons une page où vous pourrez créer dynamiquement vos propres shaders et voir le résultat immédiatement. Cette page utilisera le même code que celui décrit précédemment et utilisera un BABYLON.Matériel Shader objet pour compiler et exécuter des shaders que vous allez créer.

J'ai utilisé l'éditeur de code ACE pour CYOS. C'est un éditeur de code incroyable avec des surligneurs de syntaxe. N'hésitez pas à jeter un coup d'oeil ici. Vous pouvez trouver CYOS ici.

En utilisant la première zone de liste déroulante, vous pourrez sélectionner des shaders prédéfinis. Nous verrons chacun d'entre eux juste après.

Vous pouvez également modifier le maillage (l'objet 3D) utilisé pour prévisualiser vos shaders à l'aide de la deuxième zone de liste déroulante..

le Compiler bouton est utilisé pour créer un nouveau BABYLON.Matériel Shader de vos shaders. Le code utilisé par ce bouton est le suivant: 

// Compile shaderMaterial = new BABYLON.ShaderMaterial ("shader", scène, vertexElement: "vertexShaderCode", fragmentElement: "fragmentShaderCode",, attributs: ["position", "normal", "normal", "uv"], uniformes: ["world", "worldView", "worldViewProjection"]); var refTexture = new BABYLON.Texture ("ref.jpg", scène); refTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE; refTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE; var amigaTexture = new BABYLON.Texture ("amiga.jpg", scène); shaderMaterial.setTexture ("textureSampler", amigaTexture); shaderMaterial.setTexture ("refSampler", refTexture); shaderMaterial.setFloat ("time", 0); shaderMaterial.setVector3 ("cameraPosition", BABYLON.Vector3.Zero ()); shaderMaterial.backFaceCulling = false; mesh.material = shaderMaterial;

Brutalement simple, non? Le matériel est prêt à vous envoyer trois matrices pré-calculées (monde, worldView et worldViewProjection). Les sommets viendront avec les coordonnées de position, normales et de texture. Deux textures sont également déjà chargées pour vous:

amiga.jpgref.jpg

Et enfin, voici le renderLoop où je mets à jour deux uniformes pratiques:

  • un appelé temps afin d'obtenir des animations amusantes 
  • un appelé cameraPosition pour obtenir la position de la caméra dans vos shaders (ce qui sera utile pour les équations d'éclairage) 
engine.runRenderLoop (function () mesh.rotation.y + = 0.001; if (shaderMaterial) shaderMaterial.setFloat ("heure", heure); heure + = 0,02; shaderMaterial.setVector3 ("cameraPosition", camera.position) ; scene.render (););

Grâce au travail effectué sur Windows Phone 8.1, vous pouvez également utiliser CYOS sur votre Windows Phone (il est toujours temps de créer un shader):

Shader de base

Commençons donc par le tout premier shader défini sur CYOS: le shader Basic.

Nous connaissons déjà ce shader. Il calcule le gl_position et utilise les coordonnées de texture pour obtenir une couleur pour chaque pixel.

Pour calculer la position du pixel, nous avons juste besoin de la worldViewProjection matrice et position du sommet:

float highp de précision; // Attributs attribut vec3 position; attribut vec2 uv; // Uniformes uniformes mat4 worldViewProjection; // Variation variable vec2 vUV; void main (void) gl_Position = worldViewProjection * vec4 (position, 1.0); VUV = UV; 

Coordonnées de texture (uv) sont transmis sans modification au pixel shader.

S'il vous plaît noter que nous devons ajouter float mediump de précision; sur la première ligne pour les vertex et pixel shaders, car Chrome le requiert. Il définit que, pour de meilleures performances, nous n'utilisons pas de valeurs flottantes de précision complète.

Le pixel shader est encore plus simple, car nous avons juste besoin d'utiliser les coordonnées de texture et d'aller chercher une couleur de texture:

float highp de précision; variant vec2 vUV; sampler2D textureSampler uniforme; void main (void) gl_FragColor = texture2D (textureSampler, vUV); 

Nous avons vu précédemment que le textureSampler l'uniforme est rempli de la texture “amiga”, le résultat est donc le suivant:

Shader noir et blanc

Continuons avec un nouveau shader: le shader noir et blanc.

Le but de ce shader est d’utiliser le précédent mais avec un mode de rendu "noir et blanc uniquement". Pour ce faire, on peut garder le même vertex shader, mais le pixel shader doit être légèrement modifié.

La première option que nous avons est de ne prendre qu'un seul composant, tel que le vert:

float highp de précision; variant vec2 vUV; sampler2D textureSampler uniforme; void main (void) gl_FragColor = vec4 (texture2D (textureSampler, vUV) .ggg, 1,0); 

Comme vous pouvez le voir, au lieu d’utiliser .rgb (cette opération s'appelle un balancer), Nous avons utilisé .ggg.

Mais si nous voulons un effet noir et blanc très précis, il serait préférable de calculer la luminance (qui prend en compte toutes les composantes de la couleur):

float highp de précision; variant vec2 vUV; sampler2D textureSampler uniforme; vide principal (vide) luminance flottante = point (texture2D (textureSampler, vUV) .rgb, vec3 (0,3, 0,59, 0,11)); gl_FragColor = vec4 (luminance, luminance, luminance, 1,0); 

L'opération de points (ou produit de points) est calculée comme suit:

résultat = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z

Donc dans notre cas:

luminance = r * 0,3 + g * 0,59 + b * 0,11 (Ces valeurs sont basées sur le fait que l'œil humain est plus sensible au vert)

Ça a l'air cool, n'est-ce pas?

Cell Shading Shader

Passons maintenant à un shader plus complexe: le shader d'ombrage des cellules..

Celui-ci nécessitera que nous obtenions la normale du sommet et sa position dans le pixel shader. Ainsi, le vertex shader ressemblera à ceci:

float highp de précision; // Attributs attribut vec3 position; attribut vec3 normal; attribut vec2 uv; // Uniformes uniformes mat4 world; monde mat4 worldViewProjection uniforme; // Variation variable vec3 vPositionW; variant vec3 vNormalW; variant vec2 vUV; void main (void) vec4 outPosition = worldViewProjection * vec4 (position, 1.0); gl_Position = outPosition; vPositionW = vec3 (world * vec4 (position, 1.0)); vNormalW = normaliser (vec3 (world * vec4 (normal, 0,0))); VUV = UV; 

Veuillez noter que nous utilisons également la matrice du monde, car position et normal sont stockés sans aucune transformation et nous devons appliquer la matrice du monde pour prendre en compte la rotation de l'objet..

Le pixel shader est le suivant:

float highp de précision; // Lumières variables vec3 vPositionW; variant vec3 vNormalW; variant vec2 vUV; // Refend uniform sampler2D textureSampler; void principal (void) float ToonThresholds [4]; ToonThresholds [0] = 0.95; ToonThresholds [1] = 0,5; ToonThresholds [2] = 0,2; ToonThresholds [3] = 0.03; Flotter ToonBrightnessLevels [5]; ToonBrightnessLevels [0] = 1.0; ToonBrightnessLevels [1] = 0,8; ToonBrightnessLevels [2] = 0,6; ToonBrightnessLevels [3] = 0,35; ToonBrightnessLevels [4] = 0,2; vec3 vLightPosition = vec3 (0, 20, 10); // Light vec3 lightVectorW = normaliser (vLightPosition - vPositionW); // diffuse float ndl = max (0., dot (vNormalW, lightVectorW)); vec3 color = texture2D (textureSampler, vUV) .rgb; if (ndl> ToonThresholds [0]) color * = ToonBrightnessLevels [0];  else if (ndl> ToonThresholds [1]) color * = ToonBrightnessLevels [1];  else if (ndl> ToonThresholds [2]) color * = ToonBrightnessLevels [2];  else if (ndl> ToonThresholds [3]) color * = ToonBrightnessLevels [3];  else color * = ToonBrightnessLevels [4];  gl_FragColor = vec4 (color, 1.); 

Le but de ce shader est de simuler une lumière et, au lieu de calculer une ombrage lisse, nous considérerons que la lumière s’appliquera en fonction de seuils de luminosité spécifiques. Par exemple, si l’intensité lumineuse est comprise entre 1 (maximum) et 0.95, la couleur de l'objet (extraite de la texture) sera appliquée directement. Si l'intensité est comprise entre 0.95 et 0.5, la couleur sera atténuée par un facteur de 0.8, etc.

Donc, il y a principalement quatre étapes dans ce shader:

  • Tout d'abord, nous déclarons des constantes de seuils et de niveaux.
  • Ensuite, nous devons calculer l'éclairage à l'aide de l'équation de Phong (en supposant que la lumière ne bouge pas): 
vec3 vLightPosition = vec3 (0, 20, 10); // Light vec3 lightVectorW = normaliser (vLightPosition - vPositionW); // diffuse float ndl = max (0., dot (vNormalW, lightVectorW));

L'intensité de la lumière par pixel dépend de l'angle entre la normale et la direction de la lumière.

  • Ensuite, nous obtenons la couleur de texture pour le pixel.
  • Et finalement nous vérifions le seuil et appliquons le niveau à la couleur.

Le résultat ressemble à un objet de dessin animé: 

Phong Shader

Nous avons utilisé une partie de l'équation de Phong dans le shader précédent. Alors essayons d'utiliser le tout maintenant.

Le vertex shader est clairement simple ici, car tout se fera dans le pixel shader:

float highp de précision; // Attributs attribut vec3 position; attribut vec3 normal; attribut vec2 uv; // Uniformes uniformes mat4 worldViewProjection; // Variation variable vec3 vPosition; variant vec3 vNormal; variant vec2 vUV; void main (void) vec4 outPosition = worldViewProjection * vec4 (position, 1.0); gl_Position = outPosition; VUV = UV; vPosition = position; vNormal = normal; 

Selon l'équation, vous devez calculer les parties diffuse et spéculaire en utilisant la direction de la lumière et la normale du sommet:

float highp de précision; // Variation variable vec3 vPosition; variant vec3 vNormal; variant vec2 vUV; // Uniformes uniformes mat4 world; // Refend uniform vec3 cameraPosition; sampler2D textureSampler uniforme; void main (void) vec3 vLightPosition = vec3 (0, 20, 10); // Valeurs mondiales vec3 vPositionW = vec3 (world * vec4 (vPosition, 1.0)); vec3 vNormalW = normaliser (vec3 (world * vec4 (vNormal, 0.0))); vec3 viewDirectionW = normaliser (cameraPosition - vPositionW); // Light vec3 lightVectorW = normaliser (vLightPosition - vPositionW); vec3 color = texture2D (textureSampler, vUV) .rgb; // diffuse float ndl = max (0., dot (vNormalW, lightVectorW)); // Specular vec3 angleW = normalize (viewDirectionW + lightVectorW); float specComp = max (0., dot (vNormalW, angleW)); specComp = pow (specComp, max (1., 64.)) * 2 .; gl_FragColor = vec4 (couleur * ndl + vec3 (specComp), 1.); 

Nous avons déjà utilisé la partie diffuse dans le shader précédent, il nous faut donc ici ajouter la partie spéculaire. Cette image d'un article de Wikipedia explique le fonctionnement du shader:

Par Brad Smith, alias Rainwarrior.

Le résultat sur notre sphère:

Jeter le shader

Pour le discard shader, je voudrais introduire un nouveau concept: le Jeter mot-clé. Ce shader éliminera tous les pixels non rouges et créera l'illusion d'un objet "creusé".

Le vertex shader est le même que celui utilisé par le shader de base:

float highp de précision; // Attributs attribut vec3 position; attribut vec3 normal; attribut vec2 uv; // Uniformes uniformes mat4 worldViewProjection; // Variation variable vec2 vUV; void main (void) gl_Position = worldViewProjection * vec4 (position, 1.0); VUV = UV; 

Le pixel shader devra tester la couleur et utiliser Jeter lorsque, par exemple, la composante verte est trop élevée:

float highp de précision; variant vec2 vUV; // Refend uniform sampler2D textureSampler; void main (void) vec3 color = texture2D (textureSampler, vUV) .rgb; si (couleur.g> 0,5) à éliminer;  gl_FragColor = vec4 (color, 1.); 

Le résultat est amusant:

Wave Shader

Nous avons beaucoup joué avec les pixel shaders, mais je voulais aussi vous montrer que nous pouvons faire beaucoup de choses avec les vertex shaders..

Pour le shader de vagues, nous allons réutiliser le shader de pixels de Phong.

Le vertex shader utilisera l'uniforme appelé temps pour obtenir des valeurs animées. En utilisant cet uniforme, le shader générera une onde avec les positions des sommets:

float highp de précision; // Attributs attribut vec3 position; attribut vec3 normal; attribut vec2 uv; // Uniformes uniformes mat4 worldViewProjection; temps de flottement uniforme; // Variation variable vec3 vPosition; variant vec3 vNormal; variant vec2 vUV; vide principal (vide) vec3 v = position; v.x + = sin (2.0 * position.y + (time)) * 0.5; gl_Position = worldViewProjection * vec4 (v, 1.0); vPosition = position; vNormal = normal; VUV = UV; 

Un sinus est appliqué à position.y, et le résultat est le suivant:

Cartographie sphérique de l'environnement

Celui-ci était en grande partie inspiré par ce tutoriel. Je vous laisse lire cet excellent article et jouer avec le shader associé. 

Fresnel Shader

J'aimerais terminer cet article avec mon préféré: le shader de Fresnel.

Ce shader est utilisé pour appliquer une intensité différente en fonction de l'angle entre la direction de la vue et la normale du sommet..

Le vertex shader est le même que celui utilisé par le shading de cellules, et nous pouvons facilement calculer le terme de Fresnel dans notre pixel shader (car nous avons la position normale et la position de la caméra, qui peuvent être utilisées pour évaluer la direction de la vue):

float highp de précision; // Lumières variables vec3 vPositionW; variant vec3 vNormalW; // Refend uniform vec3 cameraPosition; sampler2D textureSampler uniforme; vide principal (vide) vec3 color = vec3 (1., 1., 1.); vec3 viewDirectionW = normaliser (cameraPosition - vPositionW); // Fresnel float fresnelTerm = dot (viewDirectionW, vNormalW); fresnelTerm = clamp (1,0 - fresnelTerm, 0., 1.); gl_FragColor = vec4 (color * fresnelTerm, 1.); 

Votre shader?

Vous êtes maintenant mieux préparé pour créer votre propre shader. N'hésitez pas à utiliser les commentaires ici ou sur le forum Babylon.js pour partager vos expériences!

Si vous voulez aller plus loin, voici quelques liens utiles:

  • Babylon.js repo 
  • Forum Babylon.js 
  • CYOS
  • GLSL sur Wikipedia 
  • Documentation GLSL

Et quelques autres apprentissages que j'ai créés sur le sujet:

  • Introduction à WebGL 3D avec HTML5 et Babylon.JS
  • Graphiques de pointe en HTML

Ou, revenons en arrière, les séries d'apprentissage sur JavaScript de notre équipe: 

  • Conseils pratiques sur les performances pour rendre votre code HTML / JavaScript plus rapide (une série en sept parties allant de la conception réactive aux jeux occasionnels en passant par l'optimisation des performances)
  • La plate-forme Web moderne Jump Start (les bases de HTML, CSS et JS)
  • Développement d'une application Windows universelle avec HTML et JavaScript Jump Start (utilisez le JS que vous avez déjà créé pour créer une application)

Et bien sûr, vous pouvez toujours utiliser certains de nos outils gratuits pour créer votre prochaine expérience Web: Communauté Visual Studio, Azure Trial et des outils de test multi-navigateurs pour Mac, Linux ou Windows..

Cet article fait partie de la série Web de développement Web de Microsoft. Nous sommes ravis de partager Microsoft Edge et le nouveau Moteur de rendu EdgeHTML avec toi. Obtenez des machines virtuelles gratuites ou testez à distance sur votre appareil Mac, iOS, Android ou Windows @ http://dev.modern.ie/.