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ć. Sprawdzamy kilka kluczowych szczegółów naszego projektu: https://lightsaber.withgoogle.com/ pomoże Ci zaoszczędzić czas, gdy następnym razem natrafisz na 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>

Obecnie istnieje wiele możliwości tworzenia aplikacji opartej na HTML5. 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łowość z polimerem

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, dołączamy plik .bowerrc w folderze poszczególnych elementów. 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

Zostanie utworzony nowy element z całą strukturą i zawartością pliku.

Zdefiniowaliśmy szablony 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 pozwala kontrolować proces 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 elementów .coffee plików do folderu .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 interfejsu gulp i wtyczki kompasu, by skompilować scss na .css i .jade do .html, w podobny sposób jak do punktu 2 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ć dużo elementów polimerowych. W naszym tych projektów jest ponad pięćdziesiąt. Jeśli okaże się, że każdy element ma oddzielny plik .js i niektóre z bibliotekami, do których odwołują się biblioteki, staje się on większy niż 100 oddzielnych 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 polimer

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 aplikacji elements.html zajmuje się również wczytywaniem wygenerował(a) 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

Używamy funkcji HTML i funkcji querySelectorAll JavaScriptu, aby znaleźć wszystkie efektów zagnieżdżonych jako elementy HTML w procesorze postów w kolejności, w których zostały określone. 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>

a scena po prostu się uruchomi, nie zmieniając ani jednego wiersza rzeczywistego kodu.

pętla renderowania i pętli aktualizacji w narzędziu Polymer;

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

Słuchamy zmian t i dt w wyszukiwarce i za każdym razem, wartości zmienią się, funkcja _update zostanie wywołana:

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. Zbudowaliśmy go w jasnym kształcie oraz dynamiczny ślad, który podąża za nim w miarę ruchu 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. Ten samolot to Ty sterujesz, gdy 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.

Poświata wewnętrznego ostrza

Poniższe źródło pokazuje, jak obliczamy vFactor, aby kontrolować intensywność w cieniowaniu wierzchołków, aby zmieszać je ze sceną program do cieniowania fragmentów

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ć zewnętrzną poświatę, renderujemy w osobnym buforze renderowania i używamy funkcji z efektem kwitnienia kwiatu i zmieszać go z końcowym zdjęciem, aby uzyskać pożądany blask. Na grafice poniżej widać 3 różne regiony, potrzebne, by mieć porządną sztylet. Biały, środkowy rdzeń niebieska poświata z zewnętrzną poświatą.

Ostrze zewnętrzne

Szlak mieczy świetlnych

Ślad miecza świetlnego jest kluczem do uzyskania pełnego efektu w takiej postaci, w jakiej widać oryginał. z 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 na zewnątrz łopatki poświatę i powstaliś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: z prostego przycisku 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