Lichtschwert mit Polymer erstellen

Screenshot: Lichtschwert

Zusammenfassung

So haben wir mit Polymer eine leistungsstarke, über Mobilgeräte gesteuerte WebGL-Technologie entwickelt. Modulares und konfigurierbares Lichtschwert Wir sehen uns einige wichtige Details an unseres Projekts https://lightsaber.withgoogle.com/ damit Sie Zeit sparen können, wenn Sie beim nächsten Mal wütende Sturmtruppen.

Übersicht

Um Polymer oder WebComponents zu identifizieren, am besten damit beginnen, einen Auszug aus einem tatsächlichen Projekt zu teilen. Hier ist ein Beispiel von der Landingpage unseres Projekts. https://lightsaber.withgoogle.com. Es ist eine gewöhnliche HTML-Datei, die aber ein bisschen Magie enthält:

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

Heutzutage gibt es viele Möglichkeiten, einer HTML5-basierten Anwendung. APIs, Frameworks, Bibliotheken, Game Engines usw. Trotz der großen Auswahl ist es schwierig, eine ausgewogene Einrichtung zu erhalten. Kontrolle über hohe Grafikleistung und sauberen Modular- Struktur und Skalierbarkeit. Wir fanden heraus, dass Polymer uns helfen könnte, organisiert sein und gleichzeitig eine geringe Leistung Optimierungen vorgenommen. Wir haben unser Projekt sorgfältig in Komponenten zu verwandeln, um die Funktionen von Polymer optimal zu nutzen.

Modularität mit Polymer

Polymer ist eine Bibliothek, mit der ein wie Ihr Projekt aus wiederverwendbaren benutzerdefinierten Elementen aufgebaut wird. Sie können damit eigenständige, voll funktionsfähige Module verwenden, die in einem nur eine HTML-Datei erstellen. Sie enthalten nicht nur die Struktur (HTML-Markup), sondern auch und Inline-Styles und -Logik.

Sehen Sie sich das folgende Beispiel an:

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

Bei einem größeren Projekt kann es jedoch hilfreich sein, diese drei logischen -Komponenten (HTML, CSS, JS) und führen diese erst bei der Kompilierung zusammen. Eine Sache ist also haben wir jedem Element im Projekt einen separaten Ordner zugewiesen:

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

Der Ordner jedes Elements hat dieselbe interne Struktur mit separaten Verzeichnissen und Dateien für Logik (Coffee-Dateien), Stile (SCSS-Dateien) und Vorlage (Jade-Datei).

Hier ein Beispiel für ein sw-ui-logo-Element:

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

In der Datei .jade sehen Sie Folgendes:

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

Sie können sehen, wie Elemente übersichtlich angeordnet sind, indem Sie Stile hinzufügen und Logik aus separaten Dateien. Um unsere Stile in unser Polymer-Element aufzunehmen, verwenden wir Jades include-Anweisung, sodass wir Inline-CSS-Code nach der Kompilierung. Das Script-Element sw-ui-logo.js wird zur Laufzeit ausgeführt.

Modulare Abhängigkeiten mit Bower

Normalerweise belassen wir Bibliotheken und andere Abhängigkeiten auf Projektebene. In der Einrichtung oben sehen Sie jedoch ein bower.json, das sich im Ordner des Elements: Abhängigkeiten auf Elementebene. Bei diesem Ansatz geht es darum, dass bei vielen Elementen mit unterschiedlichen Abhängigkeiten nur die Abhängigkeiten geladen werden, die tatsächlich verwendet werden. Und wenn Sie ein Element entfernen, müssen Sie nicht daran denken, Abhängigkeit entfernen, da Sie damit auch die Datei bower.json entfernt haben die diese Abhängigkeiten deklariert. Jedes Element lädt unabhängig die sich darauf beziehen.

Um jedoch eine Duplizierung von Abhängigkeiten zu vermeiden, fügen wir eine .bowerrc-Datei ein. auch im Ordner jedes Elements gespeichert. So weiß die Laube, wo sie lagern soll. Abhängigkeiten, sodass wir sicherstellen können, dass nur eine am Ende im selben Verzeichnis:

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

Wenn mehrere Elemente THREE.js als Abhängigkeit deklarieren, installiert es für das erste Element und parst das zweite. erkennt es, dass diese Abhängigkeit bereits installiert ist und nicht herunterladen oder duplizieren Ebenso behält es diese Abhängigkeit solange es mindestens ein Element gibt, das sie in bower.json

Ein Bash-Skript findet alle bower.json-Dateien in der Struktur verschachtelter Elemente. Dann gibt sie diese Verzeichnisse nacheinander ein und führt bower install in alle:

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

Vorlage für schnelles neues Element

Es dauert jedes Mal etwas, wenn Sie ein neues Element erstellen möchten: Sie müssen den Ordner und die grundlegende Dateistruktur mit den richtigen Namen generieren. Also verwenden wir Slush, um einen einfachen Elementgenerator zu schreiben.

Sie können das Skript über die Befehlszeile aufrufen:

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

Das neue Element wird erstellt, einschließlich aller Dateistruktur und -inhalte.

Wir haben Vorlagen für die Elementdateien definiert, z.B. die Vorlage für die .jade-Datei sieht so aus:

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

Der Slush-Generator ersetzt die Variablen durch tatsächliche Elementpfade und ‑namen.

Verwenden von Gulp zum Erstellen von Elementen

Gulp hält den Build-Prozess unter Kontrolle. Um in unserer Struktur Elemente, die Gulp benötigen, um die folgenden Schritte auszuführen:

  1. Kompilieren Sie die Elemente .coffee Dateien in .js
  2. Kompilieren Sie die Elemente .scss Dateien in .css
  3. Kompilieren Sie die Elemente .jade-Dateien in .html, wobei die .css-Dateien eingebettet sind.

Im Detail:

Zusammenstellen der Elemente .coffee Dateien 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')));
});

Für die Schritte 2 und 3 verwenden wir Gulp und ein Compass-Plug-in, um scss in .css und .jade in .html zu kompilieren. Das entspricht dem Ansatz in Schritt 2 oben.

Einschließlich Polymerelementen

Um die Polymer-Elemente tatsächlich einzubeziehen, verwenden wir HTML-Importe.

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

Polymerelemente für die Produktion optimieren

Bei einem großen Projekt können viele Polymer-Elemente vorhanden sein. In unserem haben wir mehr als 50. Wenn Sie davon ausgehen, dass jedes Element separaten .js-Datei und einigen Bibliotheken, auf die verwiesen wird, ist es mehr als 100 separate Dateien. Das bedeutet, dass der Browser viele Anfragen stellen muss, mit Leistungsverlusten. Ähnlich wie beim Verketten und Reduzieren auf einen Angular-Build anwendbar wäre, „vulkanisieren“ wir das Polymer-Projekt für die Produktion beendet wird.

Vulkanisieren ist ein Polymertool, Vereinfacht die Abhängigkeitsstruktur in einer einzigen HTML-Datei, wodurch der Anzahl der Anfragen. Das ist vor allem bei Browsern sinnvoll, Webkomponenten nativ unterstützt.

CSP (Content Security Policy) und Polymer

Bei der Entwicklung sicherer Webanwendungen müssen Sie CSP implementieren. CSP ist ein Regelwerk, das Cross-Site-Scripting-Angriffe (XSS) verhindert: Ausführung von Skripts aus unsicheren Quellen oder Ausführen von Inline-Skripts aus HTML-Dateien.

Die von Vulcanize generierte optimierte, zusammengeführte und minimierte .html-Datei enthält jetzt den gesamten JavaScript-Code Inline in einem nicht CSP-konformen Format. Um dieses Problem zu beheben, verwenden wir ein Tool namens Crisper:

Klarer teilt Inline-Skripts aus einer HTML-Datei auf und fügt sie in eine externe JavaScript-Datei für die CSP-Compliance. Wir geben die vulkanisierte HTML-Datei also an Crisper weiter und erhalten zwei Dateien: elements.html und elements.js. In elements.html wird auch die generierte elements.js geladen.

Logische Struktur der Anwendung

In Polymer können Elemente alles Mögliche sein, von nicht-visuellen Elementen bis hin zu kleinen, eigenständige und wiederverwendbare UI-Elemente (wie Schaltflächen) zu größeren Modulen, „seiten“ und sogar komplette Anwendungen zu erstellen.

Eine logische Struktur der obersten Ebene der Anwendung
Eine logische Struktur der obersten Ebene unserer Anwendung, die mit Polymer-Elementen dargestellt wird.

Nachbearbeitung mit Polymer-Architektur mit über- und untergeordneten Elementen

In jeder 3D-Grafikpipeline gibt es immer einen letzten Schritt, bei dem Effekte werden als Overlay über dem gesamten Bild hinzugefügt. Dies ist die und Effekte wie Leuchten, Götterstrahlen, Tiefenschärfe, Bokeh-Effekt, Unschärfen usw. Die Effekte werden kombiniert und auf verschiedene Elemente, je nachdem, wie die Szene aufgebaut ist. In THREE.js einen benutzerdefinierten Shader für die Nachbearbeitung in JavaScript oder Dank seiner hierarchischen Struktur ist das mit Polymer möglich.

Sehen Sie sich den HTML-Code des Elements des Postprozessors an:

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

Wir geben die Effekte als verschachtelte Polymer-Elemente unter einer gemeinsamen Klasse an. Gehen Sie dann so vor: In sw-experience-postprocessor.js tun wir Folgendes:

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

Wir verwenden die HTML-Funktion und querySelectorAll von JavaScript, um alle Effekte, die als HTML-Elemente im Postprozessor verschachtelt sind, in der Reihenfolge, in dem sie angegeben wurden. Anschließend iterieren wir sie und fügen sie dem Composer hinzu.

Nehmen wir nun an, wir möchten den DOF-Effekt (Feldtiefe) entfernen und die Reihenfolge der Blüten- und Vignetteneffekte ändern. Dazu müssen wir nur die in etwa so aussehen:

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

und die Szene wird ausgeführt, ohne eine einzige Zeile Code zu ändern.

Rendering- und Aktualisierungsschleife in Polymer

Mit Polymer können wir auch Rendering und Engine-Updates elegant angehen. Wir haben ein timer-Element erstellt, das requestAnimationFrame verwendet und wie die aktuelle Zeit (t) und die Delta-Zeit - Zeit, die seit dem Letzter 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()

Anschließend binden wir die Properties t und dt mithilfe der Datenbindung an unser Suchmaschine (experience.jade):

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

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

Wir erfassen die Änderungen von t und dt in der Suchmaschine ändern, wird die _update-Funktion aufgerufen:

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

Wenn Sie jedoch eine hohe Framerate benötigen, sollten Sie die Datenbindung von Polymer im Render-Loop entfernen, um einige Millisekunden zu sparen, die zum Benachrichtigen der Elemente über die Änderungen erforderlich sind. Wir haben benutzerdefinierte Beobachter wie folgt implementiert:

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

Die Funktion addUpdateListener nimmt einen Rückruf an und speichert ihn im Array „callbacks“. In der Aktualisierungsschleife iterieren wir dann über jeden Callback und führen wir sie direkt mit den Argumenten dt und t aus und umgehen so die Datenbindung oder Auslösung von Ereignissen. Wenn ein Callback nicht mehr aktiv sein soll, haben wir eine removeUpdateListener-Funktion hinzugefügt, mit der Sie einen zuvor hinzugefügten Callback entfernen können.

Ein Lichtschwert in DREE.js

THREE.js abstrahiert die Low-Level-Details von WebGL und ermöglicht uns, zu diesem Problem. Unser Problem ist der Kampf gegen die Sturmtruppen. Waffe. Bauen wir also ein Lichtschwert.

Die leuchtende Klinge unterscheidet ein Lichtschwert eine zweihändige Waffe. Er besteht im Wesentlichen aus zwei Teilen: dem Balken und dem Weg. der beim Verschieben sichtbar ist. Wir haben einen hellen Zylinder mit einem dynamischen Verlauf erstellt, der dem Spieler folgt, während er sich bewegt.

Die Klinge

Das Blatt besteht aus zwei untergeordneten Flügeln. Ein inneres und ein äußeres. Beide sind THREE.js-Mesh-Netzwerke mit ihren jeweiligen Materialien.

Die innere Klinge

Für das innere Blatt haben wir ein spezielles Material mit einem benutzerdefinierten Shader verwendet. Mi. eine durch zwei Punkte erstellte Linie nehmen und die Linie zwischen diesen beiden projizieren Punkte in einer Ebene. Das Flugzeug wird gesteuert, mit dem Smartphone kämpfen müssen, vermittelt dies ein Gefühl von Tiefe und Orientierung bis zum Säbel.

Um den Eindruck eines runden, leuchtenden Objekts zu erzeugen, schauen wir orthogonaler Punktabstand eines beliebigen Punkts auf der Ebene vom Hauptpunkt die die beiden Punkte A und B wie unten dargestellt. Je näher ein Punkt am umso heller ist es.

Inneres Klingenlicht

In der folgenden Quelle wird gezeigt, wie wir vFactor berechnen, um die Intensität im Vertex-Shader zu steuern und dann im Fragment-Shader mit der Szene zu mischen.

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

};

Der äußere Klingen-Leucht

Für das äußere Leuchten rendern wir in einen separaten Rendererbuffer und verwenden einen Bloom-Effekt in der Postproduktion, den wir mit dem Endbild mischen, um das gewünschte Leuchten zu erzielen. Im Bild unten sehen Sie drei verschiedene Regionen, wenn Sie einen anständigen Säbel haben wollen. Der weiße Kern, die Mitte und der äußere Schein.

Äußeres Blatt

Lichtschwertpfad

Die Spur des Lichtschwerts ist entscheidend für den vollen Effekt, wie das Original zu sehen ist in der Star Wars-Serie. Wir haben den Weg mit einem Fächer aus Dreiecken basierend auf der Bewegung des Lichtschwerts. Diese Fans sind dann zur weiteren visuellen Optimierung an den Postprozessor übergeben. Um die Lüftergeometrie haben wir ein Liniensegment und basierend auf seiner vorherigen Transformation Mit der aktuellen Transformation generieren wir ein neues Dreieck im Mesh-Netzwerk. vom Schwanz nach einer bestimmten Länge ab.

Lichtschwertspur links
Lichtschwertpfad rechts

Sobald wir ein Netz haben, weisen wir ihm ein einfaches Material zu und übergeben es um einen flüssigen Effekt zu erzeugen. Wir nutzen denselben Bloom-Effekt, den äußeren Schein und erhalten einen gleichmäßigen Verlauf, wie du siehst:

Vollständiger Weg

Leuchte auf dem Weg

Damit das Endprodukt vollständig war, mussten wir einen Glanz um den eigentlichen Weg herum hinzufügen. Das lässt sich auf verschiedene Arten realisieren. Unsere Lösung, die wir gehen Sie hier nicht ins Detail. Aus Leistungsgründen war es, eine benutzerdefinierte Shader für diesen Puffer, der eine glatte Kante um eine Klemme des Renderbuffer. Diese Ausgabe wird im endgültigen Rendering kombiniert. Hier können Sie den Leuchten um den Weg zu sehen:

Weg mit Sonnenschein

Fazit

Polymer ist eine leistungsstarke Bibliothek und ein leistungsstarkes Konzept (wie WebComponents in allgemein). Es liegt ganz allein bei dir, was du damit machst. Dabei kann es sich um über eine UI-Schaltfläche zu einer WebGL-Anwendung in voller Größe. In den vorherigen Kapiteln haben wir Ihnen Tipps und Tricks zur effizienten Verwendung von Polymer und wie sich komplexere Module strukturieren lassen, gut. Außerdem haben wir Ihnen gezeigt, wie Sie in WebGL ein optisch aussehendes Lichtschwert erstellen. Vergiss also nicht, deine Polymer-Elemente zu vulkanisieren, wenn du all das kombinierst. vor der Bereitstellung auf dem Produktionsserver Wenn Sie die CSP-Konformität wahren möchten, sind Sie vielleicht mit der Macht verbunden.

Gameplay