Créer un sabre laser avec Polymer

Capture d'écran Sabre laser

Résumé

Comment nous avons utilisé Polymer pour créer une technologie WebGL très performante, contrôlée sur les 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 quels 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>

Il existe de nombreuses options pour créer des modèles une application HTML5. API, frameworks, bibliothèques, moteurs de jeu, etc. Malgré tous les choix, il est difficile d'obtenir une configuration qui soit un bon mix entre contrôle des hautes performances des graphismes et modulaire épurée la structure et l'évolutivité. Polymer pouvait nous aider à maintenir du projet tout en permettant des performances de bas niveau des optimisations, et nous avons soigneusement élaboré la manière dont nous avons décomposé notre projet en composants pour exploiter au mieux les capacités de Polymer.

Modularité avec Polymer

Polymer est une bibliothèque qui permet vous permet de créer votre projet à partir d'éléments personnalisés réutilisables. Il vous permet d'utiliser des modules autonomes entièrement fonctionnels contenus dans un 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 éléments (HTML, CSS, JS) et ne les fusionner qu'au moment de la compilation. Une chose nous avons attribué à chaque élément du projet son propre dossier:

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')

Incluez des styles pour vous assurer que tout est organisé de façon claire et la logique à partir de fichiers distincts. Pour inclure nos styles dans notre modèle nous utilisons l'instruction include de Jade, ce qui nous permet d'avoir du code CSS intégré le contenu du fichier après la 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 dossier de l'élément: dépendances au niveau de l'élément. L'idée derrière cette approche est que lorsqu'une situation où vous avez de nombreux éléments dépendances, nous pouvons faire en sorte de charger uniquement les dépendances qui sont réellement utilisées. Si vous supprimez un élément, vous n'avez pas besoin de penser à 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 deuxième, 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"

Chaque fois que vous voulez créer un élément, cela prend un peu de temps : le dossier et la structure de base des fichiers avec les noms corrects. 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 éléments .coffee fichiers dans .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')));
});

Aux étapes 2 et 3, nous utilisons gulp et un plug-in Boussole pour compiler scss afin de .css et .jade à .html, selon une approche semblable à 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 Polymers 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 concaténation et de minimisation, nous applicable à un build Angular, nous "vulcanons" le projet Polymer 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 optimisé, concaténé et réduit dont la taille a été réduite par Vulcanize intègre tout le code JavaScript dans un environnement . Pour résoudre ce problème, nous utilisons un outil appelé Crisper :

Crisper divise les scripts intégrés à partir d'un fichier HTML et les place en un seul, fichier JavaScript externe pour la conformité 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 n'importe quoi, d'un utilitaire non visuel à de petits, des éléments d'interface utilisateur autonomes et réutilisables (comme des boutons) à des modules plus grands, comme "pages" et même de rédiger des applications complètes.

<ph type="x-smartling-placeholder">
</ph> 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 tout pipeline de graphismes 3D, il y a toujours une dernière étape où les effets sont ajoutés au-dessus de l'ensemble de l'image comme une sorte de 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 le code JavaScript querySelectorAll pour trouver toutes les effets imbriqués en tant qu'éléments HTML dans le post-traitement, dans l'ordre dans lesquels elles ont été spécifiées. 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 de manière élégante. 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()

Ensuite, nous utilisons 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

Si vous êtes managé de FPS, vous voudrez peut-être supprimer les données de Polymer de liaison dans la boucle de rendu pour gagner quelques millisecondes nécessaires pour informer sur les 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 conçue en forme de cylindre brillant et un sentier dynamique qui le suit au fur et à mesure que 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 plus l'axe principal 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" )

};

Éclat 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 postprocesseur 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.

Traînée de sabre laser à 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 fleur que nous avons appliqué le halo de la pale externe pour laisser une trace lisse, comme vous pouvez le voir:

Le sentier complet

Lueur autour de la piste

Pour que la dernière pièce soit complète, nous avons dû gérer l'éclat autour de qui peuvent être créées de différentes manières. La solution que nous n'entrez pas dans les détails ici, 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). C'est à vous de décider de ce que vous en créez. 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