Membuat Lightsaber dengan Polimer

Screenshot Lightsaber

Ringkasan

Cara kami menggunakan Polymer untuk membuat Lightsaber yang dikontrol seluler WebGL berperforma tinggi yang modular dan dapat dikonfigurasi. Kami meninjau beberapa detail penting dari project kami https://lightsaber.withgoogle.com/ untuk membantu Anda menghemat waktu saat membuat video sendiri saat nanti Anda dihadapkan dengan sekelompok Stormtroopers yang marah.

Ringkasan

Jika Anda ingin tahu apa Polymer atau WebComponents, sebaiknya mulai dengan membagikan ekstrak dari proyek kerja sebenarnya. Berikut ini contoh yang diambil dari halaman landing project kami https://lightsaber.withgoogle.com. Ini adalah file HTML biasa, tetapi memiliki keajaiban di dalamnya:

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

Jadi saat ini ada banyak pilihan di luar sana jika Anda ingin membuat aplikasi berbasis HTML5. API, Framework, Library, Game Engine, dll. Terlepas dari semua pilihan, sulit untuk mendapatkan penyiapan yang merupakan perpaduan yang baik antara kontrol atas performa tinggi grafis serta struktur dan skalabilitas modular yang bersih. Kami mendapati bahwa Polymer dapat membantu kami menjaga project tetap teratur sembari tetap memungkinkan pengoptimalan performa tingkat rendah, dan kami dengan cermat menyusun cara kami memecah project menjadi beberapa komponen untuk memanfaatkan kemampuan Polymer dengan sebaik-baiknya.

Modularitas dengan Polimer

Polymer adalah library yang memungkinkan banyak kendali terkait cara pembuatan project dari elemen kustom yang dapat digunakan kembali. Hal ini memungkinkan Anda untuk menggunakan modul mandiri yang berfungsi penuh dan terdapat dalam satu file HTML. Class ini tidak hanya berisi struktur (markup HTML), tetapi juga gaya dan logika inline.

Lihat contoh di bawah ini:

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

Namun pada project yang lebih besar, sebaiknya pisahkan ketiga komponen logis ini (HTML, CSS, JS) dan hanya menggabungkannya pada waktu kompilasi. Jadi, satu hal yang kami lakukan adalah memberikan folder terpisah untuk setiap elemen dalam project:

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

Dan setiap folder elemen memiliki struktur internal yang sama dengan direktori dan file terpisah untuk logika (file kopi), gaya (file scss), dan template (file giok).

Berikut adalah contoh elemen sw-ui-logo:

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

Dan jika Anda melihat 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')

Anda dapat melihat bagaimana segala sesuatunya diatur secara rapi dengan menyertakan gaya dan logika dari file terpisah. Untuk menyertakan gaya dalam elemen Polymer, kita menggunakan pernyataan include Jade, sehingga kita memiliki konten file CSS inline yang sebenarnya setelah kompilasi. Elemen skrip sw-ui-logo.js akan dijalankan saat runtime.

Dependensi Modular dengan Bower

Biasanya kita mempertahankan library dan dependensi lainnya di level project. Namun dalam penyiapan di atas, Anda akan melihat bower.json yang ada di folder elemen: dependensi tingkat elemen. Ide di balik pendekatan ini adalah bahwa jika Anda memiliki banyak elemen dengan dependensi yang berbeda, kita dapat memastikan untuk hanya memuat dependensi yang benar-benar digunakan. Jika menghapus elemen, Anda tidak perlu ingat untuk menghapus dependensinya karena Anda juga akan menghapus file bower.json yang mendeklarasikan dependensi ini. Setiap elemen secara independen memuat dependensi yang terkait dengannya.

Namun, untuk menghindari duplikasi dependensi, kami juga menyertakan file .bowerrc di folder setiap elemen. Hal ini memberi tahu bower tempat untuk menyimpan dependensi, sehingga kita dapat memastikan bahwa hanya ada satu dependensi di bagian akhir dalam direktori yang sama:

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

Dengan cara ini, jika beberapa elemen mendeklarasikan THREE.js sebagai dependensi, setelah bower menginstalnya untuk elemen pertama dan mulai menguraikan elemen kedua, dependensi ini akan menyadari bahwa dependensi ini sudah diinstal dan tidak akan mendownload ulang atau menduplikasinya. Demikian pula, dependensi tersebut akan menyimpan file dependensi tersebut selama setidaknya ada satu elemen yang masih menentukannya dalam bower.json-nya.

Skrip bash menemukan semua file bower.json dalam struktur elemen bertingkat. Kemudian, kode ini memasuki direktori ini satu per satu dan menjalankan bower install di setiap direktori tersebut:

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

Template Elemen Baru Cepat

Setiap kali Anda ingin membuat elemen baru, diperlukan sedikit waktu: membuat folder dan struktur file dasar dengan nama yang benar. Jadi, kita menggunakan Slush untuk menulis generator elemen sederhana.

Anda dapat memanggil skrip dari command line:

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

Dan elemen baru dibuat, termasuk semua struktur dan konten file.

Kami menentukan template untuk file elemen, misalnya, template file .jade terlihat seperti berikut:

// 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 mengganti variabel dengan nama dan jalur elemen sebenarnya.

Menggunakan Gulp untuk Membangun Elemen

Gulp menjaga agar proses build tetap terkendali. Dan dalam struktur kita, untuk membangun elemen, kita perlu Gulp mengikuti langkah-langkah berikut:

  1. Mengompilasi file .coffee elemen ke .js
  2. Mengompilasi file .scss elemen ke .css
  3. Kompilasi file .jade elemen ke .html, yang menyematkan file .css.

Secara lebih detail:

Mengompilasi file .coffee elemen ke .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')));
});

Untuk langkah 2 dan 3, kita menggunakan gulp dan plugin kompas untuk mengompilasi scss ke .css dan .jade ke .html, dengan pendekatan yang sama seperti 2 di atas.

Termasuk Elemen Polimer

Untuk menyertakan elemen Polymer, kita menggunakan impor 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">

Mengoptimalkan elemen Polimer untuk produksi

Sebuah project besar pada akhirnya dapat memiliki banyak elemen Polimer. Dalam proyek ini, ada lebih dari lima puluh. Jika Anda menganggap setiap elemen memiliki file .js terpisah dan beberapa elemen memiliki referensi, file tersebut akan menjadi lebih dari 100 file terpisah. Artinya, browser perlu membuat banyak permintaan, dengan penurunan performa. Demikian pula untuk proses penggabungan dan minifikasi yang akan kami terapkan pada build Angular, kami melakukan "vulkanisasi" project Polymer di akhir untuk tahap produksi.

Vulcanize adalah alat Polymer yang meratakan hierarki dependensi menjadi satu file html, sehingga mengurangi jumlah permintaan. Cara ini sangat bagus untuk browser yang tidak mendukung komponen web secara native.

CSP (Kebijakan Keamanan Konten) dan Polymer

Saat mengembangkan aplikasi web yang aman, Anda perlu mengimplementasikan CSP. CSP adalah kumpulan aturan yang mencegah serangan pembuatan skrip lintas situs (XSS): eksekusi skrip dari sumber yang tidak aman atau menjalankan skrip inline dari file HTML.

Sekarang, file .html yang dioptimalkan, digabungkan, dan diminifikasi yang dihasilkan oleh Vulcanize memiliki semua kode JavaScript inline dalam format yang tidak sesuai dengan CSP. Untuk mengatasinya, kami menggunakan alat yang disebut Crisper.

Crisper membagi skrip inline dari file HTML dan menempatkannya ke dalam satu file JavaScript eksternal untuk kepatuhan CSP. Jadi, kita meneruskan file HTML hasil vulkanisasi melalui Crisper dan berakhir dengan dua file: elements.html dan elements.js. Di dalam elements.html, kode ini juga menangani pemuatan elements.js yang dihasilkan.

Struktur Logika Aplikasi

Di Polymer, elemen dapat berupa apa saja, mulai dari utilitas non-visual hingga elemen UI kecil, mandiri, dan dapat digunakan kembali (seperti tombol) hingga modul yang lebih besar seperti "halaman" dan bahkan menulis aplikasi lengkap.

Struktur logis tingkat atas aplikasi
Struktur logika tingkat atas dari aplikasi kita yang direpresentasikan dengan elemen Polymer.

Pascapemrosesan dengan Polimer dan Arsitektur Parent-child

Dalam pipeline grafis 3D, selalu ada langkah terakhir ketika efek ditambahkan di atas seluruh gambar sebagai semacam overlay. Ini adalah langkah pascapemrosesan, dan melibatkan efek seperti cahaya, sinar dewa, kedalaman bidang, bokeh, blur, dll. Efek-efeknya digabungkan dan diterapkan ke elemen yang berbeda sesuai dengan cara scene dibuat. Di THREE.js, kita dapat membuat shader kustom untuk pascapemrosesan di JavaScript, atau kita dapat melakukannya dengan Polymer, berkat struktur induknya-turunannya.

Jika Anda melihat kode HTML elemen pascaprosesor kami:

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

Kita menentukan efek sebagai elemen Polymer bertingkat dalam class umum. Kemudian, dalam sw-experience-postprocessor.js, kita melakukan ini:

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

Kita menggunakan fitur HTML dan querySelectorAll JavaScript untuk menemukan semua efek yang disusun bertingkat sebagai elemen HTML dalam pemroses postingan, sesuai urutan yang ditentukan. Kemudian kami mengulanginya dan menambahkannya ke komposer.

Sekarang, misalkan kita ingin menghapus efek DOF (Depth of Field), serta mengubah urutan efek bloom dan vignette. Yang perlu kita lakukan adalah mengedit definisi pasca-prosesor menjadi sesuatu seperti:

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

dan adegan itu akan berjalan begitu saja, tanpa mengubah satu baris kode pun yang sebenarnya.

Merender loop dan loop update di Polymer

Dengan Polymer, kita juga bisa melakukan rendering dan update mesin dengan elegan. Kami membuat elemen timer yang menggunakan requestAnimationFrame dan menghitung nilai seperti waktu saat ini (t) dan waktu delta - waktu yang berlalu dari frame terakhir (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()

Kemudian, kita menggunakan data binding untuk mengikat properti t dan dt ke mesin kita (experience.jade):

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

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

Dan kita memproses perubahan t dan dt dalam mesin dan setiap kali nilai berubah, fungsi _update akan dipanggil:

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

Jika menginginkan FPS, Anda dapat menghapus binding data Polymer dalam render loop untuk menghemat beberapa milidetik yang diperlukan untuk memberi tahu elemen tentang perubahan. Kami menerapkan observer kustom sebagai berikut:

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

Fungsi addUpdateListener menerima callback dan menyimpannya dalam array callback-nya. Kemudian, pada loop update, kita melakukan iterasi pada setiap callback dan menjalankannya dengan argumen dt dan t secara langsung, dengan mengabaikan data binding atau pengaktifan peristiwa. Setelah callback tidak lagi dimaksudkan untuk aktif, kami menambahkan fungsi removeUpdateListener yang memungkinkan Anda menghapus callback yang ditambahkan sebelumnya.

Lightsaber di THREE.js

THREE.js memisahkan detail tingkat rendah dari WebGL dan memungkinkan kita untuk fokus pada masalah tersebut. Masalah kita adalah melawan Stormtroopers dan kita butuh senjata. Jadi mari kita buat Lightsaber.

Pisau bersinar adalah yang membedakan lightsaber dengan senjata dua tangan lama. Bagian ini terutama terdiri dari dua bagian: balok dan jalan setapak yang terlihat saat menggerakkannya. Kami membangunnya dengan bentuk silinder yang cerah dan jejak dinamis yang mengikutinya saat pemain bergerak.

Pisau

Pisau ini terdiri dari dua sub bilah. Bagian dalam dan luar. Keduanya merupakan THREE.js mesh dengan bahannya masing-masing.

Pisau Dalam

Untuk blade dalam, kami menggunakan material kustom dengan shader kustom. Kita mengambil garis yang dibuat oleh dua titik dan memproyeksikan garis antara dua titik tersebut pada bidang. Bidang ini pada dasarnya adalah yang Anda kontrol saat bertarung dengan ponsel, fitur ini memberikan kesan kedalaman dan orientasi pada pedang.

Untuk menciptakan kesan objek bulat bercahaya, kita melihat jarak titik ortogonal titik mana pun pada bidang dari garis utama yang menghubungkan dua titik A dan B seperti di bawah ini. Semakin dekat titik ke sumbu utama, semakin terang titik tersebut.

Glow bilah dalam

Sumber di bawah menunjukkan cara kami menghitung vFactor untuk mengontrol intensitas pada shader vertex, kemudian menggunakannya untuk menyatu dengan scene di shader fragmen.

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

};

Kilau Pisau Luar

Untuk glow luar, kita merender ke renderbuffer terpisah dan menggunakan efek mekar pascapemrosesan dan berpadu dengan gambar akhir untuk mendapatkan glow yang diinginkan. Gambar di bawah menunjukkan tiga wilayah berbeda yang harus Anda miliki jika menginginkan pedang yang lumayan. Yaitu inti putih, cahaya kebiruan tengah, dan cahaya luar.

Pisau luar

Jalur Lightsaber

Jejak lightsaber adalah kunci untuk mendapatkan efek penuh seperti aslinya yang terlihat dalam seri Star Wars. Kami membuat jalan setapak dengan penggemar segitiga yang dihasilkan secara dinamis berdasarkan gerakan lightsaber. Kipas ini kemudian diteruskan ke {i>postprocessor<i} untuk peningkatan visual lebih lanjut. Untuk membuat geometri kipas, kita memiliki segmen garis dan berdasarkan transformasi sebelumnya serta transformasi saat ini, kita membuat segitiga baru dalam mesh, yang turun dari bagian ekor setelah panjang tertentu.

Jalan setapak Lightsaber di kiri
Jalur Lightsaber ke kanan

Setelah memiliki mesh, kami menetapkan material sederhana ke mesh tersebut, dan meneruskannya ke postprocessor untuk membuat efek yang lancar. Kita menggunakan efek mekar yang sama seperti yang kami terapkan pada glow bilah luar dan mendapatkan jalur yang mulus seperti yang dapat Anda lihat:

Jalur lengkap

Bersinar di sekitar jalan setapak

Agar bagian terakhir menjadi lengkap, kita harus menangani cahaya di sekitar jejak yang sebenarnya, yang dapat dibuat dengan beberapa cara. Solusi kami yang tidak dijelaskan secara mendetail di sini, karena alasan performa, adalah membuat shader kustom untuk buffer ini, yang menciptakan tepi yang halus di sekitar klem renderbuffer. Kemudian, kita menggabungkan output ini dalam render akhir, di sini Anda dapat melihat cahaya yang mengelilingi jejak:

Jalan setapak dengan cahaya

Kesimpulan

Polymer adalah library dan konsep yang canggih (sama seperti WebComponents pada umumnya). Terserah pada Anda apa yang akan membuatnya. Dapat berupa apa saja, mulai dari tombol UI sederhana hingga aplikasi WebGL berukuran penuh. Dalam bab sebelumnya, kami telah menunjukkan beberapa tips dan trik tentang cara menggunakan Polymer dalam produksi secara efisien dan cara membuat struktur modul yang lebih kompleks dan berperforma baik. Kami juga telah menunjukkan cara mendapatkan lightsaber yang terlihat bagus di WebGL. Jadi, jika Anda menggabungkan semua itu, ingatlah untuk Vulcanize elemen Polymer sebelum men-deploy ke server produksi, dan jika Anda tidak lupa menggunakan Crisper jika ingin tetap mematuhi CSP, mungkin force akan menyertai Anda.

Gameplay