Créer un sabre laser avec Polymer

Capture d'écran du sabre laser

Résumé

Découvrez comment nous avons utilisé Polymer pour créer un WebGL hautes performances, contrôlé par les appareils mobiles Sabre laser modulaire et configurable. Nous examinons certains détails clés de notre projet https://lightsaber.withgoogle.com/ pour vous faire gagner du temps lorsque vous créerez le vôtre la prochaine fois que vous rencontrerez des Stormtroopers en colère.

Présentation

Si vous vous demandez ce que sont les composants Polymer ou WebComponents, il serait préférable de commencer par partager un extrait d'un projet opérationnel réel. Voici un exemple tiré de la page d’accueil de notre projet https://lightsaber.withgoogle.com. Il est un fichier HTML standard, mais qui contient un peu de magie:

<!-- Element-->
<dom-module id="sw-page-landing">
    <!-- Template-->
    <template>
    <style>
        <!-- include elements/sw/pages/sw-page-landing/styles/sw-page-landing.css-->
    </style>
    <div class="centered content">
        <sw-ui-logo></sw-ui-logo>
        <div class="connection-url-wrapper">
        <sw-t key="landing.type" class="type"></sw-t>
        <div id="url" class="connection-url">.</div>
        <sw-ui-toast></sw-ui-toast>
        </div>
    </div>
    <div class="disclaimer epilepsy">
        <sw-t key="disclaimer.epilepsy" class="type"></sw-t>
    </div>
    <sw-ui-footer state="extended"></sw-ui-footer>
    </template>
    <!-- Polymer element script-->
    <script src="scripts/sw-page-landing.js"></script>
</dom-module>

Beaucoup de possibilités s'offrent à vous aujourd'hui pour créer une application HTML5. API, frameworks, bibliothèques, moteurs de jeu, etc. Malgré tous les choix, il est difficile de trouver une configuration qui soit un bon mix entre contrôle des hautes performances des graphismes et modulaire épurée la structure et l'évolutivité. Nous avons constaté que Polymer pouvait nous aider à organiser le projet tout en permettant des optimisations de performances de bas niveau. Nous avons donc soigneusement conçu la façon dont nous avons décomposé notre projet en composants afin de tirer le meilleur parti des fonctionnalités de Polymer.

Modularité avec Polymer

Polymer est une bibliothèque qui offre de nombreuses possibilités de contrôle de la façon dont votre projet est créé à partir d'éléments personnalisés réutilisables. Il vous permet d'utiliser des modules autonomes et entièrement fonctionnels contenus dans un seul fichier HTML. Ils contiennent non seulement la structure (balisage HTML), mais aussi la logique et les styles intégrés.

Examinez l'exemple ci-dessous:

<link rel="import" href="bower_components/polymer/polymer.html">

<dom-module id="picture-frame">
    <template>
    <!-- scoped CSS for this element -->
    <style>
        div {
        display: inline-block;
        background-color: #ccc;
        border-radius: 8px;
        padding: 4px;
        }
    </style>
    <div>
        <!-- any children are rendered here -->
        <content></content>
    </div>
    </template>

    <script>
    Polymer({
        is: "picture-frame",
    });
    </script>
</dom-module>

Mais pour un projet plus vaste, il peut être utile de séparer ces trois (HTML, CSS, JS) et ne les fusionner qu'au moment de la compilation. Nous avons donc attribué un dossier distinct à chaque élément du projet :

src/elements/
|-- elements.jade
`-- sw
    |-- debug
    |   |-- sw-debug
    |   |-- sw-debug-performance
    |   |-- sw-debug-version
    |   `-- sw-debug-webgl
    |-- experience
    |   |-- effects
    |   |-- sw-experience
    |   |-- sw-experience-controller
    |   |-- sw-experience-engine
    |   |-- sw-experience-input
    |   |-- sw-experience-model
    |   |-- sw-experience-postprocessor
    |   |-- sw-experience-renderer
    |   |-- sw-experience-state
    |   `-- sw-timer
    |-- input
    |   |-- sw-input-keyboard
    |   `-- sw-input-remote
    |-- pages
    |   |-- sw-page-calibration
    |   |-- sw-page-connection
    |   |-- sw-page-connection-error
    |   |-- sw-page-error
    |   |-- sw-page-experience
    |   `-- sw-page-landing
    |-- sw-app
    |   |-- bower.json
    |   |-- scripts
    |   |-- styles
    |   `-- sw-app.jade
    |-- system
    |   |-- sw-routing
    |   |-- sw-system
    |   |-- sw-system-audio
    |   |-- sw-system-config
    |   |-- sw-system-environment
    |   |-- sw-system-events
    |   |-- sw-system-remote
    |   |-- sw-system-social
    |   |-- sw-system-tracking
    |   |-- sw-system-version
    |   |-- sw-system-webrtc
    |   `-- sw-system-websocket
    |-- ui
    |   |-- experience
    |   |-- sw-preloader
    |   |-- sw-sound
    |   |-- sw-ui-button
    |   |-- sw-ui-calibration
    |   |-- sw-ui-disconnected
    |   |-- sw-ui-final
    |   |-- sw-ui-footer
    |   |-- sw-ui-help
    |   |-- sw-ui-language
    |   |-- sw-ui-logo
    |   |-- sw-ui-mask
    |   |-- sw-ui-menu
    |   |-- sw-ui-overlay
    |   |-- sw-ui-quality
    |   |-- sw-ui-select
    |   |-- sw-ui-toast
    |   |-- sw-ui-toggle-screen
    |   `-- sw-ui-volume
    `-- utils
        `-- sw-t

Et le dossier de chaque élément a la même structure interne avec des des répertoires et des fichiers pour la logique (fichiers Coffee), les styles (fichiers Scss) et (fichier Jade).

Voici un exemple d'élément sw-ui-logo:

sw-ui-logo/
|-- bower.json
|-- scripts
|   `-- sw-ui-logo.coffee
|-- styles
|   `-- sw-ui-logo.scss
`-- sw-ui-logo.jade

Et si vous examinez le fichier .jade:

// Element
dom-module(id='sw-ui-logo')

    // Template
    template
    style
        include elements/sw/ui/sw-ui-logo/styles/sw-ui-logo.css

    img(src='[[url]]')

    // Polymer element script
    script(src='scripts/sw-ui-logo.js')

Vous pouvez voir comment les éléments sont organisés de manière claire en incluant des styles et une logique provenant de fichiers distincts. Pour inclure nos styles dans nos éléments Polymer, nous utilisons l'instruction include de Jade. Nous obtenons ainsi le contenu réel du fichier CSS intégré après compilation. L'élément de script sw-ui-logo.js va au moment de l'exécution.

Dépendances modulaires avec Bower

Normalement, nous conservons les bibliothèques et autres dépendances au niveau du projet. Cependant, dans la configuration ci-dessus, vous remarquerez un bower.json qui se trouve dans le dossier de l'élément: dépendances au niveau de l'élément. L'idée derrière cette approche est que dans une situation où vous avez de nombreux éléments avec différentes dépendances, nous pouvons faire en sorte de charger uniquement les dépendances qui sont réellement utilisé. Si vous supprimez un élément, vous n'avez pas besoin de vous souvenir de supprimer sa dépendance, car vous aurez également supprimé le fichier bower.json qui déclare ces dépendances. Chaque élément charge indépendamment les dépendances qui s’y rapportent.

Toutefois, pour éviter la duplication des dépendances, nous incluons un fichier .bowerrc. dans le dossier de chaque élément. Cela indique à bower où stocker afin de s'assurer qu'il n'y en a qu'une à la fin répertoire:

{
    "directory" : "../../../../../bower_components"
}

De cette façon, si plusieurs éléments déclarent THREE.js comme dépendance, une fois Bower l'installe pour le premier élément et commence à analyser le second, il détecte que cette dépendance est déjà installée et n'effectue pas téléchargez-le à nouveau ou dupliquez-le. De même, il maintient cette dépendance à condition qu'au moins un élément le définisse son bower.json.

Un script bash recherche tous les fichiers bower.json dans la structure des éléments imbriqués. Ensuite, il entre dans ces répertoires un par un et exécute bower install dans chacun d'entre eux:

echo installing bower components...
modules=$(find /vagrant/app -type f -name "bower.json" -not -path "*node_modules*" -not -path "*bower_components*")
for module in $modules; do
    pushd $(dirname $module)
    bower install --allow-root -q
    popd
done

Modèle "Nouvel élément rapide"

Cela prend un peu de temps chaque fois que vous souhaitez créer un élément : générer le dossier et la structure de fichiers de base avec les noms appropriés. Nous utilisons donc Slush pour écrire un générateur d'éléments simple.

Vous pouvez appeler le script à partir de la ligne de commande:

$ slush element path/to/your/element-name

Le nouvel élément est créé, y compris l'ensemble de la structure et du contenu des fichiers.

Nous avons défini des modèles pour les fichiers d'éléments, par exemple le modèle de fichier .jade se présente comme suit:

// Element
dom-module(id='<%= name %>')

    // Template
    template
    style
        include elements/<%= path %>/styles/<%= name %>.css

    span This is a '<%= name %>' element.

    // Polymer element script
    script(src='scripts/<%= name %>.js')

Le générateur Slush remplace les variables par les chemins et les noms réels des éléments.

Utiliser Gulp pour créer des éléments

Gulp garde le contrôle du processus de compilation. Et dans notre structure, pour construire les éléments dont nous avons besoin de Gulp pour suivre les étapes suivantes:

  1. Compiler les fichiers .coffee des éléments en .js
  2. Compiler les éléments .scss fichiers dans .css
  3. Compiler les éléments .jade dans .html, intégrant les fichiers .css.

Plus en détail:

La compilation des éléments .coffee fichiers dans .js

gulp.task('elements-coffee', function () {
    return gulp.src(abs(config.paths.app + '/elements/**/*.coffee'))
    .pipe($.replaceTask({
        patterns: [{json: getVersionData()}]
    }))
    .pipe($.changed(abs(config.paths.static + '/elements'), {extension: '.js'}))
    .pipe($.coffeelint())
    .pipe($.coffeelint.reporter())
    .pipe($.sourcemaps.init())
    .pipe($.coffee({
    }))
    .on('error', gutil.log)
    .pipe($.sourcemaps.write())
    .pipe(gulp.dest(abs(config.paths.static + '/elements')));
});

Pour les étapes 2 et 3, nous utilisons gulp et un plug-in Compass pour compiler scss en .css et .jade en .html, selon une approche similaire à l'étape 2 ci-dessus.

Avec des éléments en polymère

Pour inclure les éléments Polymer, nous utilisons des importations HTML.

<link rel="import" href="elements.html">

<!-- Polymer -->
<link rel="import" href="../bower_components/polymer/polymer.html">

<!-- Custom elements -->
<link rel="import" href="sw/sw-app/sw-app.html">
<link rel="import" href="sw/system/sw-system/sw-system.html">
<link rel="import" href="sw/system/sw-routing/sw-routing.html">
<link rel="import" href="sw/system/sw-system-version/sw-system-version.html">
<link rel="import" href="sw/system/sw-system-environment/sw-system-environment.html">
<link rel="import" href="sw/pages/sw-page-landing/sw-page-landing.html">
<link rel="import" href="sw/pages/sw-page-connection/sw-page-connection.html">
<link rel="import" href="sw/pages/sw-page-calibration/sw-page-calibration.html">
<link rel="import" href="sw/pages/sw-page-experience/sw-page-experience.html">
<link rel="import" href="sw/ui/sw-preloader/sw-preloader.html">
<link rel="import" href="sw/ui/sw-ui-overlay/sw-ui-overlay.html">
<link rel="import" href="sw/ui/sw-ui-button/sw-ui-button.html">
<link rel="import" href="sw/ui/sw-ui-menu/sw-ui-menu.html">

Optimiser les éléments Polymer pour la production

Un grand projet peut finir par comporter de nombreux éléments Polymer. Dans notre nous en avons plus de cinquante. Si vous considérez que chaque élément a une fichier .js distinct et dont certaines ont des bibliothèques référencées, il devient supérieur à 100 fichiers distincts. Cela signifie que le navigateur doit effectuer beaucoup de requêtes, avec une perte de performances. Comme pour un processus de concatenaison et de minification que nous appliquerions à un build Angular, nous "vulcanisons" le projet Polymer à la fin pour la production.

Vulcanize est un outil Polymer qui aplatit l'arborescence des dépendances en un seul fichier HTML, ce qui réduit le nombre de requêtes. Cela est particulièrement utile pour les navigateurs prend en charge les composants Web de manière native.

CSP (Content Security Policy) et Polymer

Lorsque vous développez des applications Web sécurisées, vous devez implémenter CSP. CSP est un ensemble de règles qui empêchent les attaques par script intersites (XSS) : l'exécution de scripts provenant de sources non sécurisées ou l'exécution de scripts intégrés ; à partir de fichiers HTML.

Le fichier .html unique, optimisé, concaténé et réduit généré par Vulcanize contient désormais tout le code JavaScript intégré dans un format non conforme au CSP. Pour résoudre ce problème, nous utilisons un outil appelé Crisper :

Crisper divise les scripts intégrés d'un fichier HTML et les place dans un seul fichier JavaScript externe pour se conformer au CSP. Nous transmettons donc le modèle vulcanisé HTML dans Crisper et vous obtenez deux fichiers: elements.html et elements.js Dans elements.html, il se charge également de charger le a généré elements.js.

Structure logique de l'application

Dans Polymer, les éléments peuvent être de tout type, d'une utilitaire non visuelle à de petits éléments d'interface utilisateur autonomes et réutilisables (comme des boutons), en passant par des modules plus importants tels que des "pages" et même la composition d'applications complètes.

Une structure logique de premier niveau de l&#39;application <ph type="x-smartling-placeholder">
</ph> Une structure logique de premier niveau de notre application représentée par Éléments polymères.

Post-traitement avec Polymer et une architecture parent-enfant

Dans n'importe quel pipeline graphique 3D, il existe toujours une dernière étape au cours de laquelle des effets sont ajoutés sur l'ensemble de l'image en tant que superposition. Il s'agit de la l'étape de post-traitement, qui implique des effets tels que des éclats, des rayons divins, la profondeur de champ, le bokeh, le floutage, etc. Les effets sont combinés et appliqués différents éléments en fonction de la construction de la scène. Dans THREE.js, nous vous pouvez créer un nuanceur personnalisé pour le post-traitement en JavaScript c'est possible avec Polymer grâce à sa structure parent-enfant.

Si vous examinez le code HTML de notre élément de post-traitement:

<dom-module id="sw-experience-postprocessor">
    <!-- Template-->
    <template>
    <sw-experience-effect-bloom class="effect"></sw-experience-effect-bloom>
    <sw-experience-effect-dof class="effect"></sw-experience-effect-dof>
    <sw-experience-effect-vignette class="effect"></sw-experience-effect-vignette>
    </template>
    <!-- Polymer element script-->
    <script src="scripts/sw-experience-postprocessor.js"></script>
</dom-module>

Nous spécifions les effets en tant qu'éléments Polymer imbriqués dans une classe commune. Ensuite, Dans sw-experience-postprocessor.js, nous procédons ainsi:

effects = @querySelectorAll '.effect'
@composer.addPass effect.getPass() for effect in effects

Nous utilisons la fonctionnalité HTML et querySelectorAll de JavaScript pour trouver tous les effets imbriqués en tant qu'éléments HTML dans le post-traitement, dans l'ordre dans lequel ils ont été spécifiés. Nous effectuons ensuite une itération sur eux et les ajoutons au compositeur.

Maintenant, supposons que nous voulons supprimer l'effet DOF (Profondeur de champ) et modifier l'ordre des effets de fleur et de vignetage. Tout ce que nous avons à faire est de modifier la définition du post-traitement par quelque chose comme:

<dom-module id="sw-experience-postprocessor">
    <!-- Template-->
    <template>
    <sw-experience-effect-vignette class="effect"></sw-experience-effect-vignette>
    <sw-experience-effect-bloom class="effect"></sw-experience-effect-bloom>
    </template>
    <!-- Polymer element script-->
    <script src="scripts/sw-experience-postprocessor.js"></script>
</dom-module>

et la scène va s'exécuter, sans modifier une seule ligne de code.

Boucle de rendu et de mise à jour dans Polymer

Avec Polymer, nous pouvons aussi aborder le rendu et les mises à jour du moteur avec élégance. Nous avons créé un élément timer qui utilise requestAnimationFrame et calcule telles que l'heure actuelle (t) et le temps différentiel (temps écoulé depuis le dernière image (dt):

Polymer
    is: 'sw-timer'

    properties:
    t:
        type: Number
        value: 0
        readOnly: true
        notify: true
    dt:
        type: Number
        value: 0
        readOnly: true
        notify: true

    _isRunning: false
    _lastFrameTime: 0

    ready: ->
    @_isRunning = true
    @_update()

    _update: ->
    if !@_isRunning then return
    requestAnimationFrame => @_update()
    currentTime = @_getCurrentTime()
    @_setT currentTime
    @_setDt currentTime - @_lastFrameTime
    @_lastFrameTime = @_getCurrentTime()

    _getCurrentTime: ->
    if window.performance then performance.now() else new Date().getTime()

Nous utilisons ensuite la liaison de données pour lier les propriétés t et dt à notre moteur (experience.jade):

sw-timer(
    t='{ % templatetag openvariable % }t}}',
    dt='{ % templatetag openvariable % }dt}}'
)

sw-experience-engine(
    t='[t]',
    dt='[dt]'
)

Nous écoutons aussi les modifications de t et de dt dans le moteur et chaque fois que les valeurs changent, la fonction _update est appelée:

Polymer
    is: 'sw-experience-engine'

    properties:
    t:
        type: Number

    dt:
        type: Number

    observers: [
    '_update(t)'
    ]

    _update: (t) ->
    dt = @dt
    @_physics.update dt, t
    @_renderer.render dt, t

Toutefois, si vous avez besoin d'augmenter le nombre d'images par seconde, vous pouvez supprimer la liaison de données de Polymer dans la boucle de rendu pour économiser quelques millisecondes nécessaires pour informer les éléments des modifications. Nous avons implémenté les observateurs personnalisés comme suit:

sw-timer.coffee :

addUpdateListener: (listener) ->
    if @_updateListeners.indexOf(listener) == -1
    @_updateListeners.push listener
    return

removeUpdateListener: (listener) ->
    index = @_updateListeners.indexOf listener
    if index != -1
    @_updateListeners.splice index, 1
    return

_update: ->
    # ...
    for listener in @_updateListeners
        listener @dt, @t
    # ...

La fonction addUpdateListener accepte un rappel et l'enregistre dans son de rappels. Ensuite, dans la boucle de mise à jour, nous effectuons une itération sur chaque rappel. nous l'exécutons directement avec les arguments dt et t, en contournant la liaison de données ou le déclenchement d'événements. Une fois qu'un rappel n'est plus censé être actif, nous avons ajouté une removeUpdateListener, qui vous permet de supprimer un rappel ajouté précédemment.

Sabre laser dans THREE.js

THREE.js élimine les détails de bas niveau de WebGL et nous permet de nous concentrer sur le problème. Notre problème est d'affronter les Stormtroopers, arme. Faisons donc un sabre laser.

La lame brillante différencie un sabre laser des anciens. arme à deux mains. Il se compose principalement de deux parties: la poutre et la traînée. que l'on voit lorsqu'on le déplace. Nous l'avons créée avec une forme cylindrique lumineuse et une traînée dynamique qui la suit lorsque le joueur se déplace.

The Blade

La lame se compose de deux sous-pales. Un intérieur et un extérieur. Il s'agit tous deux de maillages THREE.js avec leurs matériaux respectifs.

La lame intérieure

Pour la lame intérieure, nous avons utilisé un matériau personnalisé avec un nuanceur personnalisé. Mer à partir d'une droite créée par deux points et projeter la ligne entre ces deux dans un plan. C'est ce que vous contrôlez avec votre mobile, cela donne une impression de profondeur et d'orientation au sabre.

Pour donner l'impression qu'il s'agit d'un objet rond et lumineux, nous observons les distance orthogonale de n'importe quel point du plan par rapport au point principal reliant les deux points A et B comme ci-dessous. Plus un point est proche de l'axe principal, plus il est lumineux.

Illumination des lames intérieures

La source ci-dessous montre comment nous calculons un vFactor pour contrôler l'intensité. dans le nuanceur de sommets pour qu'il se fonde dans la scène nuanceur de fragments.

THREE.LaserShader = {

    uniforms: {
    "uPointA": {type: "v3", value: new THREE.Vector3(0, -1, 0)},
    "uPointB": {type: "v3", value: new THREE.Vector3(0, 1, 0)},
    "uColor": {type: "c", value: new THREE.Color(1, 0, 0)},
    "uMultiplier": {type: "f", value: 3.0},
    "uCoreColor": {type: "c", value: new THREE.Color(1, 1, 1)},
    "uCoreOpacity": {type: "f", value: 0.8},
    "uLowerBound": {type: "f", value: 0.4},
    "uUpperBound": {type: "f", value: 0.8},
    "uTransitionPower": {type: "f", value: 2},
    "uNearPlaneValue": {type: "f", value: -0.01}
    },

    vertexShader: [

    "uniform vec3 uPointA;",
    "uniform vec3 uPointB;",
    "uniform float uMultiplier;",
    "uniform float uNearPlaneValue;",
    "varying float vFactor;",

    "float getDistanceFromAB(vec2 a, vec2 b, vec2 p) {",

        "vec2 l = b - a;",
        "float l2 = dot( l, l );",
        "float t = dot( p - a, l ) / l2;",
        "if( t < 0.0 ) return distance( p, a );",
        "if( t > 1.0 ) return distance( p, b );",
        "vec2 projection = a + (l * t);",
        "return distance( p, projection );",

    "}",

    "vec3 getIntersection(vec4 a, vec4 b) {",

        "vec3 p = a.xyz;",
        "vec3 q = b.xyz;",
        "vec3 v = normalize( q - p );",
        "float t = ( uNearPlaneValue - p.z ) / v.z;",
        "return p + (v * t);",

    "}",

    "void main() {",

        "vec4 a = modelViewMatrix * vec4(uPointA, 1.0);",
        "vec4 b = modelViewMatrix * vec4(uPointB, 1.0);",
        "if(a.z > uNearPlaneValue) a.xyz = getIntersection(a, b);",
        "if(b.z > uNearPlaneValue) b.xyz = getIntersection(a, b);",
        "a = projectionMatrix * a; a /= a.w;",
        "b = projectionMatrix * b; b /= b.w;",
        "vec4 p = projectionMatrix * modelViewMatrix * vec4(position, 1.0);",
        "gl_Position = p;",
        "p /= p.w;",
        "float d = getDistanceFromAB(a.xy, b.xy, p.xy) * gl_Position.z;",
        "vFactor = 1.0 - clamp(uMultiplier * d, 0.0, 1.0);",

    "}"

    ].join( "\n" ),

    fragmentShader: [

    "uniform vec3 uColor;",
    "uniform vec3 uCoreColor;",
    "uniform float uCoreOpacity;",
    "uniform float uLowerBound;",
    "uniform float uUpperBound;",
    "uniform float uTransitionPower;",
    "varying float vFactor;",

    "void main() {",

        "vec4 col = vec4(uColor, vFactor);",
        "float factor = smoothstep(uLowerBound, uUpperBound, vFactor);",
        "factor = pow(factor, uTransitionPower);",
        "vec4 coreCol = vec4(uCoreColor, uCoreOpacity);",
        "vec4 finalCol = mix(col, coreCol, factor);",
        "gl_FragColor = finalCol;",

    "}"

    ].join( "\n" )

};

L'éclairage de la lame extérieure

Pour le halo externe, nous effectuons le rendu dans un tampon de rendu distinct et utilisons un et se fondre dans l'image finale pour obtenir l'éclat souhaité. L'image ci-dessous montre les trois régions différentes si vous voulez un sabre décent. à savoir le cœur blanc, le milieu une teinte bleue et un halo extérieur.

Lame extérieure

Sentier du sabre laser

La traînée du sabre laser est la clé de l'effet complet, comme le montre l'original. de la série Star Wars. Nous avons créé la piste avec un fan de triangles générés de manière dynamique en fonction du mouvement du sabre laser. Ces fans sont alors transmis au post-processeur pour une amélioration visuelle supplémentaire. Pour créer le une forme de ventilateur, nous avons un segment de droite basé sur sa transformation précédente et "current transform", nous générons un nouveau triangle dans le maillage, de la queue après une certaine longueur.

Trajet de sabre laser vers la gauche
Sentier du sabre laser à droite

Une fois que nous avons un maillage, nous lui attribuons un matériau simple et nous le transmettons pour créer un effet fluide. Nous utilisons le même effet de floraison que celui que nous avons appliqué à l'éclat extérieur de la lame et obtenons une traînée fluide, comme vous pouvez le voir :

Le sentier complet

Lueur autour de la piste

Pour que la pièce finale soit complète, nous avons dû gérer l'aura autour du tracé réel, qui peut être créée de plusieurs façons. La solution que nous nous n'allons pas y entrer en détail. Pour des raisons de performances, c'est de créer pour ce tampon, qui crée un bord lisse autour d'une pince RenderBuffer. Nous combinons ensuite cette sortie dans le rendu final. Ici, vous pouvez voir l'éclat qui entoure la piste:

Sentier illuminé

Conclusion

Polymer est un concept et une bibliothèque puissants (tout comme les WebComponents sont général). Ce que vous créez ne dépend que de vous. Il peut s’agir de n’importe quoi, d'un bouton d'interface utilisateur à une application WebGL en taille réelle. Dans les chapitres précédents, voici quelques conseils et astuces pour utiliser efficacement Polymer en production et comment structurer des modules plus complexes bien. Nous vous avons également montré comment créer un sabre laser d'aspect esthétique avec WebGL. Si vous combinez tout cela, n'oubliez pas de Vulcaniser vos éléments Polymer avant le déploiement sur le serveur de production et, si vous n'oubliez pas d'utiliser Crisper, Si vous souhaitez rester en conformité avec les CSP, vous pouvez être obligé de faire appel à vous !

Séquence de jeu