Tworzenie miecza świetlnego przy użyciu polimeru

Zrzut ekranu z mieczem świetlnym

Podsumowanie

Jak użyliśmy Polymeru do stworzenia wydajnej mobilnej technologii WebGL Miecz świetlny, który jest modułowy i można skonfigurować. Przyjrzymy się najważniejszym szczegółom naszego projektu https://lightsaber.withgoogle.com/, aby zaoszczędzić Ci czas, gdy następnym razem natkniesz się na grupę wściekłych Szturmowców.

Omówienie

Jeśli zastanawia się Pan/Pani, czym są Polymer lub WebKomponent, najlepiej jest zacząć od udostępnienia fragmentu z faktycznego działającego projektu. Oto przykład wzięty ze strony docelowej naszego projektu https://lightsaber.withgoogle.com. Jest to zwykły plik HTML, który ma w środku trochę magii:

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

Jeśli chcesz utworzyć aplikację opartą na HTML5, masz do wyboru wiele opcji. Interfejsy API, platformy, biblioteki, silniki gier itp. Konfiguracja jest trudna do skonfigurowania, mimo że jest tu wiele możliwości między kontrolą nad wysoką wydajnością grafiki a przejrzystą modułową do ich struktury i skalowalności. Odkryliśmy, że technologia Polymer może pomóc nam utrzymać projekt, który jest zorganizowany przy jednoczesnej pracy na niskim poziomie. i staraliśmy się opracować sposób, w jaki zrealizowaliśmy nasz projekt, aby jak najlepiej wykorzystać możliwości platformy Polymer.

Modułowe działanie w przypadku Polymer

Polymer to biblioteka, która umożliwia daje większą kontrolę nad sposobem tworzenia projektu z wykorzystaniem elementów niestandardowych wielokrotnego użytku. Umożliwia korzystanie z samodzielnych, w pełni funkcjonalnych modułów znajdujących się w jednym pliku HTML. Mają nie tylko strukturę (znaczniki HTML), ale też style wbudowane i logika.

Spójrz na ten przykład:

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

Jednak w większym projekcie pomocne może być oddzielenie tych 3 elementów logicznych: (HTML, CSS, JS) i scalać tylko podczas kompilacji. Jedna rzecz każdy element projektu ma oddzielny folder:

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

Foldery każdego elementu mają taką samą strukturę wewnętrzną z osobnymi katalogi i pliki logiczne (pliki kawa), style (pliki CSS) oraz (plik jade).

Oto przykładowy element sw-ui-logo:

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

Jeśli spojrzysz na plik .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')

Możesz zobaczyć, jak wszystko jest zorganizowane w przejrzysty sposób, dodając style i logikę w osobnych plikach. Aby włączyć nasze style do korzystamy z instrukcji include Jade, więc mamy wbudowany kod CSS zawartości pliku po skompilowaniu. Element skryptu sw-ui-logo.js będzie uruchamianych w czasie działania.

Zależności modułowe w Bower

Zwykle zapisujemy biblioteki i inne zależności na poziomie projektu. Jednak w powyższej konfiguracji zobaczysz element bower.json, który znajduje się folder elementów: zależności na poziomie elementu. Idea tego podejścia jest to sytuacja, w której mamy wiele elementów o różnych możemy upewnić się, że ładują się tylko te zależności, które są w rzeczywistości. Jeśli usuniesz jakiś element, nie musisz pamiętać, usuń zależność, ponieważ usuniesz też plik bower.json deklarującej te zależności. Każdy element niezależnie wczytuje zależności między nimi.

Aby jednak uniknąć powielania zależności, plik .bowerrc jest też dołączany do folderu każdego elementu. Informuje, gdzie przechowywać dane aby mieć pewność, że w kodzie w hierarchii jest tylko jedna zależności katalogu:

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

Dzięki temu, jeśli wiele elementów deklaruje THREE.js jako zależność, raz Bower instaluje go dla pierwszego elementu i zaczyna analizować drugi, zauważy, że ta zależność jest już zainstalowana i nie będzie pobierz go jeszcze raz lub zduplikuj. Zachowa ona również tę zależność plików, o ile jest co najmniej jeden element, który nadal je definiuje to bower.json.

Skrypt bash znajduje wszystkie pliki bower.json w strukturze elementów zagnieżdżonych. Następnie kolejno wchodzi do tych katalogów i wykonuje funkcję bower install każdy z nich:

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

Szybki nowy szablon elementu

Za każdym razem, gdy chcesz utworzyć nowy element, może minąć trochę czasu: generowanie z podstawową strukturą plików i właściwymi nazwami. Wykorzystujemy więc Slush, aby napisać prosty generator elementów.

Możesz wywołać ten skrypt z poziomu wiersza poleceń:

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

Tworzony jest nowy element, w tym cała struktura pliku i jego zawartość.

Zdefiniowaliśmy szablony dla plików elementów, np. szablon pliku .jade wygląda tak:

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

Generator Slush zastępuje zmienne rzeczywistymi ścieżkami i nazwami elementów.

Używanie Gulp do tworzenia elementów

Gulp zapewnia kontrolę procesu kompilacji. W naszej strukturze, elementy, które Gulp musi wykonać, aby wykonać te czynności:

  1. Kompiluj elementy .coffee plików do folderu .js
  2. Kompiluj elementy .scss plików do folderu .css
  3. Kompiluj elementy .jade do .html z umieszczonymi plikami .css.

Więcej informacji:

Kompilowanie plików .coffee elementów do pliku .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')));
});

W krokach 2 i 3 używamy gulpa i wtyczki compass do kompilowania scss na .css oraz .jade na .html w sposób podobny do opisanego powyżej.

W tym elementy polimerowe

Aby uwzględnić elementy Polymer, używamy importu 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">

Optymalizacja pierwiastków polimerowych do produkcji

Duży projekt może zawierać wiele elementów Polymer. W naszym tych projektów jest ponad pięćdziesiąt. Jeśli weźmiemy pod uwagę, że każdy element ma osobny plik .js, a niektóre z nich odwołują się do bibliotek, okaże się, że jest to ponad 100 osobnych plików. Oznacza to, że przeglądarka musi wysyłać wiele żądań, z utratą skuteczności. Podobnie jak w procesie konkatenacji i minifikacji w przypadku kompilacji Angular, „wulkanizujemy” projekt Polymer do produkcji.

Vulcanize to narzędzie polimerowe, które spłaszcza drzewo zależności do pojedynczego pliku HTML, zmniejszając liczby żądań. Jest to szczególnie przydatne w przeglądarkach, które nie natywną obsługę komponentów sieciowych.

CSP (Content Security Policy) i Polymer

Podczas tworzenia bezpiecznych aplikacji internetowych należy wdrożyć CSP. CSP to zestaw reguł, które zapobiegają atakom typu cross-site scripting (XSS): uruchamianie skryptów z niebezpiecznych źródeł lub wykonywanie wbudowanych skryptów z plików HTML.

Wygenerowano ten zoptymalizowany, połączony i zminifikowany plik .html. firmy Vulcanize zawiera kod JavaScript w źródle niezgodnym z CSP. . Aby go rozwiązać, używamy narzędzia Wyraźniejszy.

Crisper rozdziela wbudowane skrypty z pliku HTML i umieszcza je w jednym, zewnętrznego pliku JavaScript pod kątem zgodności z CSP. W tym samym czasie mijamy pliki HTML za pomocą Crisper. Zostaną utworzone 2 pliki: elements.html oraz elements.js W ramach elements.html odpowiada też za wczytywanie wygenerowanego elements.js.

Struktura logiczna aplikacji

W Polymer pierwiastki mogą być wszystko, od niewizualnej użyteczności po małe, samodzielnych i wielokrotnego użytku elementów interfejsu (np. przycisków) i większych modułów, takich jak „strony” a nawet tworzyć pełne aplikacje.

Struktura logiczna najwyższego poziomu aplikacji
Najwyższej struktury logicznej naszej aplikacji, reprezentowaną przez Elementy polimerowe.

Przetwarzanie końcowe z użyciem architektury Polymer i architektura nadrzędny-podrzędny

W każdym potoku graficznym 3D zawsze jest ostatni etap, w którym efekty są dodawane do całego zdjęcia jako nakładki. To jest i obejmuje takie efekty, jak poświaty, promienie boskie, głębi ostrości, efektu bokeh, rozmycia itp. Efekty są łączone i stosowane do różne elementy w zależności od sposobu budowania sceny. W THREE.js może utworzyć niestandardowy program do cieniowania do przetwarzania końcowego w JavaScripcie lub jest to możliwe dzięki strukturze Polymer.

Jeśli spojrzymy na kod HTML elementu pośredniczącego:

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

Efekty określamy jako zagnieżdżone elementy Polymer w ramach wspólnej klasy. Następnie: w sw-experience-postprocessor.js robimy to:

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

Do znajdowania wszystkich efektów zagnieżdżonych jako elementy HTML w postprocessorze w kolejności, w jakiej zostały określone, używamy funkcji HTML i elementu JavaScript querySelectorAll. Następnie przeprowadzamy je iterację i dodajemy do narzędzia kompozycyjnego.

Teraz załóżmy, że chcemy usunąć efekt głębi ostrości (DOF). zmienić kolejność kwitnienia kwiatów i efekty winietowe. Wystarczy, że zmodyfikujemy może to być np. definicja podmiotu przetwarzającego dane osobowe, np.:

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

i scena będzie działać bez zmiany ani jednej linii kodu.

pętla renderowania i pętla aktualizacji w Polymerze

Dzięki Polymer można także elegancko podejść do renderowania i aktualizacji silnika. Utworzyliśmy element timer, który korzysta z metody requestAnimationFrame i oblicza takie jak bieżący czas (t) i czas delta, czyli czas, który upłynął od ostatnia klatka (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()

Następnie używamy wiązania danych, by powiązać właściwości t i dt z wyszukiwarka (experience.jade):

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

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

Obserwujemy zmiany wartości tdt w silniku i każdoraz, gdy się zmieniają, wywołujemy funkcję _update:

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

Jeśli jednak zależy Ci na FPS, warto usunąć dane Polymer powiązanie w pętli renderowania w celu zaoszczędzenia kilku milisekund wymaganych do powiadomienia o zmianach. Wprowadziliśmy takie obserwacje:

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

Funkcja addUpdateListener przyjmuje wywołanie zwrotne i zapisuje je w swoim tablicy wywołań zwrotnych. Następnie w pętli aktualizacji powtarzamy każde wywołanie zwrotne. uruchamiamy je bezpośrednio za pomocą argumentów dt i t, omijając wiązanie danych lub uruchamiania zdarzeń. Gdy wywołanie zwrotne nie jest już aktywne, dodaliśmy Funkcja removeUpdateListener, która umożliwia usunięcie wcześniej dodanego wywołania zwrotnego.

Miecz świetlny w THREE.js

THREE.js ogranicza szczegółowość technologii WebGL i pozwala nam skupić się dotyczącą problemu. Naszym problemem jest walka z szturmowcami i potrzebujemy broni. Zbudujmy więc miecz świetlny.

Świecące ostrze odróżnia miecz świetlny od dwuręczna broń. Składa się głównie z dwóch części: belki i szlaku co jest widoczne przy jej przesuwaniu. Jest to jasny cylinder z dynamicznym śladem, który podąża za ruchami gracza.

Ostrze

Ostrze składa się z 2 podłoży. Wewnętrzna i zewnętrzna. Obie sieci są siatkami THREE.js zawierającymi odpowiednie materiały.

Wewnętrzne ostrze

Wewnętrzną osłonę użyliśmy niestandardowego materiału z dostosowanym cieniowaniem. Śr wybierz linię utworzoną przez dwa punkty i rzutuj linię między tymi dwoma punktami punkty w samolocie. To właśnie ten samolot kontrolujesz, i grają na komórce. Daje to poczucie głębi i orientacji do miecza.

Aby stworzyć wrażenie świecącego się przedmiotu, patrzymy na ortogonalna odległość dowolnego punktu na płaszczyźnie od głównego łączący dwa punkty A i B, jak poniżej. Im bliżej punktu tym jaśniejszy jest obraz.

Światło wewnętrznego ostrza

Poniżej źródło pokazuje, jak obliczamy vFactor, aby kontrolować intensywność w shaderze wierzchołka, a następnie używać go do mieszania ze sceną w shaderze fragmentu.

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

};

Poświata zewnętrznego ostrza

Aby uzyskać efekt poświaty, renderujemy oddzielny bufor renderowania i wykorzystujemy efekt bloom w postprocessingu, a następnie łączymy go z ostatecznym obrazem. Na grafice poniżej widać 3 różne regiony, potrzebne, jeśli chcecie mieć porządną szafę. Chodzi o białe jądro, niebieską poświatę w środku i poświatę na zewnątrz.

Ostrze zewnętrzne

Szlak mieczy świetlnych

Ślad miecza świetlnego jest kluczowy dla pełnego efektu, tak jak w pierwotnym mieczu w serii Gwiezdne Wojny. Wyruszyliśmy na szlak z wielbiciele stworzonych trójkątów dynamicznie w zależności od ruchu miecza świetlnego. Ci fani są są przekazywane do późniejszego ulepszenia wizualnego. Aby utworzyć mamy geometrię wentylatora. i bieżące przekształcenie generujemy w siatce nowy trójkąt, z ogona po określonej długości.

Szlak z mieczem świetlnym w lewo
Szlak z mieczem świetlnym po prawej

Gdy mamy już siatkę, przypisujemy do niej prosty materiał i przekazujemy postprocesorem w celu uzyskania płynnego efektu. Używamy tego samego efektu kwitnienia zastosowaliśmy poświatę zewnętrznego ostrza i pozyskaliśmy gładki ślad, jak widać:

Cały szlak

Świecące wokół szlaku

Aby dokończyć ostatni element, musieliśmy zmierzyć się ze światłem które można stworzyć na wiele sposobów. Nasze rozwiązanie nie podawać tutaj szczegółów. Ze względu na wydajność trzeba było utworzyć niestandardowy program do cieniowania dla tego bufora, który tworzy gładką krawędź wokół bufor renderowania. Następnie łączymy dane wyjściowe w ostatecznym renderowaniu, zobacz poświatę, która otacza szlak:

Szlak pełen blasku

Podsumowanie

Polymer to zaawansowana biblioteka i koncepcja (tak samo jak WebKomponent ogólne). To, co na nim zrobisz, zależy tylko od Ciebie. Może to być wszystko, od prostego przycisku w interfejsie do pełnowymiarowej aplikacji WebGL. W poprzednich rozdziałach omówiliśmy kilka porad i wskazówek, jak efektywnie korzystać z technologii Polymer, w środowisku produkcyjnym oraz jak tworzyć bardziej złożone moduły, które również zapewniają cóż. Pokazaliśmy również, jak stworzyć ładny miecz świetlny w WebGL. Więc jeśli to wszystko połączysz, pamiętaj o wulkanizacji pierwiastków polimerowych przed wdrożeniem na serwerze produkcyjnym oraz Jeśli chcesz zachować zgodność z zasadami CSP, możesz liczyć na siebie.

Rozgrywka