Creazione di una spada laser con Polymer

Screenshot Spada laser

Riepilogo

Come abbiamo utilizzato Polymer per creare un servizio WebGL ad alte prestazioni controllato da dispositivi mobili Spada laser modulare e configurabile. Esaminiamo alcuni dettagli chiave del nostro progetto https://lightsaber.withgoogle.com/ per aiutarti a risparmiare tempo quando ne creerai il tuo la prossima volta che ti imbatterai in un pacchetto di stormtrooper arrabbiati.

Panoramica

Se ti stai chiedendo cosa siano Polymer o WebComponents, abbiamo pensato che fosse meglio iniziare condividendo un estratto di un progetto funzionante reale. Ecco un esempio tratto dalla pagina di destinazione del nostro progetto https://lightsaber.withgoogle.com. È un normale file HTML, ma che contiene alcuni elementi magici:

<!-- 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>

Di conseguenza, oggi esistono molte opzioni per creare un'applicazione basata su HTML5. API, framework, librerie, motori di gioco ecc. Nonostante tutte le scelte disponibili, è difficile trovare un buon mix tra il controllo su grafiche ad alte prestazioni e una struttura modulare struttura e scalabilità. Abbiamo scoperto che Polymer poteva aiutarci a mantenere progetto organizzato, pur consentendo un rendimento di basso livello ottimizzazioni e abbiamo creato con cura il modo in cui abbiamo suddiviso il nostro progetto in componenti per sfruttare al meglio le capacità di Polymer.

Modularità con i polimeri

Polymer è una libreria che consente ha molta potenza sulle modalità di creazione del tuo progetto a partire da elementi personalizzati riutilizzabili. Consente di utilizzare moduli autonomi e completamente funzionali contenuti in un un singolo file HTML. Contengono non solo la struttura (markup HTML), ma anche stili e logica incorporati.

Dai un'occhiata all'esempio riportato di seguito:

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

Ma in un progetto più grande potrebbe essere utile separare questi tre (HTML, CSS, JS) e uniscili solo al momento della compilazione. Pertanto, abbiamo assegnato a ogni elemento del progetto una cartella separata:

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

Inoltre, la cartella di ogni elemento ha la stessa struttura interna con directory e file per la logica (file Caffè), gli stili (file CSS) e modello (file Jade).

Ecco un esempio di elemento sw-ui-logo:

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

Se esamini il file .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')

Puoi vedere come sono organizzate le cose in modo chiaro includendo stili e logica da file separati. Per includere i nostri stili nei modelli Polymer usiamo l'istruzione include di Jade, quindi abbiamo un vero e proprio CSS incorporato i contenuti dei file dopo la compilazione. L'elemento script sw-ui-logo.js vengono eseguiti in fase di runtime.

Dipendenze modulari con Bower

In genere, manteniamo le librerie e altre dipendenze a livello di progetto. Tuttavia, nella configurazione precedente noterai un bower.json che si trova nella cartella dell'elemento: le dipendenze a livello di elemento. L'idea alla base di questo approccio è che, in una situazione in cui sono presenti molti elementi con dipendenze diverse, possiamo assicurarci di caricare solo quelle effettivamente utilizzate. E se rimuovi un elemento, non devi ricordarti di rimuovi la dipendenza perché avrai rimosso anche il file bower.json che dichiara queste dipendenze. Ogni elemento carica in modo indipendente e dipendenze correlate.

Tuttavia, per evitare una duplicazione di dipendenze, includiamo un file .bowerrc anche nella cartella di ogni elemento. Questa informazione indica a bower dove archiviare in modo da assicurarci che ne sia solo una alla fine directory:

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

In questo modo, se più elementi dichiarano THREE.js come dipendenza, una volta bower lo installa per il primo elemento e inizia ad analizzare il secondo. si accorge che questa dipendenza è già installata scaricarlo di nuovo o duplicarlo. Allo stesso modo, manterrà purché esista almeno un elemento che lo definisce ancora è bower.json.

Uno script bash trova tutti i file bower.json nella struttura degli elementi nidificati. Quindi inserisce queste directory una per una ed esegue bower install in ognuno di essi:

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

Modello rapido nuovo elemento

Ogni volta che vuoi creare un nuovo elemento occorre un po' di tempo: la generazione la struttura delle cartelle e dei file di base con i nomi corretti. Quindi usiamo Slush per scrivere un generatore di elementi semplice.

Puoi chiamare lo script dalla riga di comando:

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

Viene creato il nuovo elemento, inclusi tutti i contenuti e la struttura del file.

Abbiamo definito i modelli per i file degli elementi, ad esempio il modello di file .jade ha il seguente aspetto:

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

Il generatore di Slush sostituisce le variabili con percorsi e nomi di elementi effettivi.

Utilizzo di Gulp per creare elementi

Gulp tiene sotto controllo il processo di compilazione. Nella nostra struttura, per creare gli elementi, abbiamo bisogno che Gulp segua i seguenti passaggi:

  1. Compila gli elementi .coffee file in .js
  2. Compila i file .scss degli elementi in .css
  3. Compila gli elementi .jade file in .html, incorporamento dei file .css.

In dettaglio:

Compilazione degli elementi .coffee file in .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')));
});

Per i passaggi 2 e 3 utilizziamo gulp e un plug-in Compass per compilare scss in .css e .jade in .html, in un approccio simile a quello descritto sopra.

Includere elementi polimeri

Per includere effettivamente gli elementi Polymer, utilizziamo le importazioni 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">

Ottimizzazione degli elementi di Polymer per la produzione

Un progetto di grandi dimensioni può finire per avere molti elementi Polymer. Nel nostro progetto, ne abbiamo più di 50. Se consideri che ogni elemento ha una file .js separati e alcuni con librerie a cui viene fatto riferimento, diventa più di 100 file separati. Ciò significa che il browser deve effettuare molte richieste, con una perdita di prestazioni. Analogamente a un processo di concatenazione e minimizzazione si applicherebbe a una build Angular, "vulcanizziamo" il progetto Polymer per la produzione.

Vulcanize è uno strumento Polymer che appiattisce la struttura ad albero delle dipendenze in un unico file HTML, riducendo il numero di richieste. Ciò è particolarmente utile per i browser che non supportare in modo nativo i componenti web.

CSP (Content Security Policy) e Polymer

Durante lo sviluppo di applicazioni web sicure, devi implementare CSP. CSP è un insieme di regole che prevengono gli attacchi cross-site scripting (XSS): esecuzione di script da origini non sicure o esecuzione di script incorporati da file HTML.

Ora il file .html unico, ottimizzato, concatenato e minimizzato generato da Vulcanize contiene tutto il codice JavaScript in linea in un formato non conforme al CSP. Per risolvere il problema, utilizziamo uno strumento chiamato Crisper.

Crisper suddivide gli script in linea di un file HTML e li inserisce in un unico file JavaScript esterno per la conformità al CSP. Quindi passiamo la formazione di file HTML tramite Crisper e alla fine ci sono due file: elements.html e elements.js. All'interno di elements.html, si occupa anche di caricare generato elements.js.

Struttura logica dell'applicazione

In Polymer, gli elementi possono essere qualsiasi cosa, da un'utilità non visiva a piccoli elementi dell'interfaccia utente autonomi e riutilizzabili (come i pulsanti), a moduli più grandi come le "pagine" e persino la composizione di applicazioni complete.

Una struttura logica di primo livello dell&#39;applicazione
Una struttura logica di primo livello della nostra applicazione, rappresentata con Elementi polimerici.

Post-elaborazione con Polymer e architettura padre-figlio

In qualsiasi pipeline di grafica 3D, c'è sempre un ultimo passaggio in cui gli effetti vengono aggiunti all'intera immagine come una sorta di overlay. Questo è il fase di post-elaborazione e coinvolge effetti come bagliori, raggi profondità di campo, bokeh, sfocature e così via. Gli effetti vengono combinati e applicati elementi diversi a seconda di come è realizzata la scena. In THREE.js, potresti creare uno shaker personalizzato per la post-elaborazione in JavaScript possiamo farlo con Polymer, grazie alla sua struttura padre-figlio.

Se esamini il codice HTML dell'elemento del nostro post-processor:

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

Gli effetti vengono specificati come elementi Polymer nidificati in una classe comune. Poi, in sw-experience-postprocessor.js lo facciamo:

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

Utilizziamo la funzionalità HTML e querySelectorAll di JavaScript per trovare tutti gli effetti nidificati come elementi HTML all'interno del post-processor, nell'ordine in cui sono stati specificati. Eseguiamo l'iterazione su questi elementi e li aggiungiamo al compositore.

Supponiamo ora di voler rimuovere l'effetto DOF (profondità di campo) e cambiare l'ordine degli effetti Bloom e Vignetta. Non dobbiamo fare altro che modificare la definizione del post-processor in modo da ottenere qualcosa di simile a quanto segue:

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

e la scena verrà eseguita, senza modificare una sola riga di codice vero e proprio.

Loop di rendering e loop di aggiornamento in Polymer

Con Polymer possiamo affrontare con eleganza anche il rendering e gli aggiornamenti del motore. Abbiamo creato un elemento timer che utilizza requestAnimationFrame e calcola come l'ora corrente (t) e il tempo delta - tempo trascorso dal ultimo frame (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()

Quindi, utilizziamo l'associazione di dati per associare le proprietà t e dt alla nostra motore (experience.jade):

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

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

Inoltre, ascoltiamo le modifiche di t e dt nel motore e ogni volta che viene eseguita cambiano, la funzione _update viene chiamata:

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

Se però sei in cerca di FPS, potresti rimuovere i dati di Polymer binding nel ciclo di rendering per risparmiare il paio di millisecondi necessari per la notifica elementi sulle modifiche. Abbiamo implementato gli osservatori personalizzati nel seguente modo:

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 funzione addUpdateListener accetta un callback e lo salva nel suo di callback di Google. Quindi, nel loop di aggiornamento, eseguiamo l'iterazione di ogni callback lo eseguiamo direttamente con gli argomenti dt e t, aggirando l'associazione di dati dell'attivazione di un evento. Quando non doveva più essere attivo il callback, abbiamo aggiunto removeUpdateListener che consente di rimuovere un callback aggiunto in precedenza.

Una spada laser in THREE.js

THREE.js astrae i dettagli di basso livello di WebGL e ci permette di concentrarci per risolvere il problema. Il nostro problema è che dobbiamo combattere gli Stormtrooper e ci serve un'arma. Costruiamo una spada laser.

La lama luminosa è ciò che distingue una spada laser da qualsiasi altra arma a due mani. È composta principalmente da due parti: la trave e il sentiero visibile quando lo si sposta. L'abbiamo costruita con una luminosa forma cilindrica e una scia dinamica che la segue mentre il giocatore si muove.

The Blade

La lama è composta da due lame secondarie. Uno interno e uno esterno. Entrambi sono mesh THREE.js con i rispettivi materiali.

Lama interna

Per la lama interna abbiamo utilizzato un materiale personalizzato con uno shaker personalizzato. Me prendere una linea creata da due punti e proiettare la linea tra questi due punti punti su un aereo. Questo aereo è fondamentalmente ciò che controlli quando con il tuo cellulare, ti dà un senso di profondità e orientamento alla sciabola.

Per creare la sensazione di un oggetto luminoso rotondo, osserviamo la distanza del punto ortogonale di qualsiasi punto del piano rispetto a quello principale linea che unisce i due punti A e B come indicato di seguito. Più un punto è vicino sull'asse principale, più luminoso è.

Bagliore interno della lama

La fonte riportata di seguito mostra come calcoliamo un valore vFactor per controllare l'intensità in Vertex Shader per utilizzarlo poi per fondersi con la scena Snippet Shader.

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

};

Bagliore della lama esterna

Per il bagliore esterno, il rendering viene eseguito in un buffer di rendering separato e utilizziamo un l'effetto bloom di post-elaborazione e si fonde con l'immagine finale per ottenere l'incandescenza desiderata. L'immagine di seguito mostra le tre diverse regioni in cui che bisogna avere se vuoi una sciabola decente. ossia il nucleo bianco, al centro bagliore blu scuro e il bagliore esterno.

Lama esterna

Sentiero con spada laser

La scia della spada laser è la chiave per il pieno effetto dell'originale della serie Star Wars. Abbiamo creato il sentiero con un fan di triangoli generati dinamicamente in base al movimento della spada laser. Questi ventilatori vengono quindi vengono passate al post-processore per ulteriori miglioramenti visivi. Per creare il alla geometria di ventaglio abbiamo un segmento di linea, basato sulla trasformazione precedente e nella trasformazione attuale generiamo un nuovo triangolo nella mesh, dalla coda dopo una certa lunghezza.

Scia della spada laser a sinistra
Sentiero con spada laser a destra

Una volta ottenuta una mesh, le assegniamo un materiale semplice e la passiamo al post-processore per creare un effetto uniforme. Usiamo lo stesso effetto di fioritura che che abbiamo applicato al bagliore della pala esterna e ottenere una scia uniforme come puoi vedere:

Il percorso completo

Luce notturna lungo il percorso

Per completare il pezzo abbiamo dovuto gestire il bagliore intorno al creato in vari modi. La nostra soluzione non entrare in dettaglio qui, per motivi legati alle prestazioni era la creazione di un Shar per questo buffer che crea un bordo liscio attorno a un morsetto renderingbuffer. Combiniamo quindi questo output nel rendering finale, qui puoi osserva il bagliore che circonda il sentiero:

Sentiero con bagliore

Conclusione

Polymer è una libreria e un concetto efficaci (come WebComponents generale). Sta a te decidere cosa farne. Può essere qualsiasi cosa, un semplice pulsante UI per un'applicazione WebGL di dimensioni standard. Nei capitoli precedenti ti abbiamo mostrato alcuni suggerimenti utili su come usare Polymer in modo efficiente in produzione e a strutturare moduli più complessi che beh. Ti abbiamo anche mostrato come creare una spada laser dall'aspetto accattivante in WebGL. Quindi, se combini tutto questo, ricorda di sottoporre a vulcanizzazione gli elementi Polymer prima della distribuzione sul server di produzione e, se non dimentichi di usare Crisper Se vuoi mantenere la conformità CSP, fai in modo che la forza sia con te.

Gameplay