סיכום
איך השתמשנו ב-Polymer כדי ליצור חרב (Lightsaber) מודולרית שמאפשרת לשלוט במכשירים ניידים ברמת ביצועים גבוהה. אנחנו בודקים כמה פרטים חשובים מהפרויקט שלנו, https://lightsaber.withgoogle.com/, כדי לעזור לכם לחסוך זמן כשאתם יוצרים את הפרויקט שלכם בפעם הבאה שתיתקלו בחבורת סערות כועסת.
סקירה כללית
אם אתם תוהים לגבי השימוש ב-Polymer או ב-WebComponents (רכיבי WebComponent), חשבנו שעדיף להתחיל בשיתוף תמצית מפרויקט קיים. הנה דוגמה שנלקחה מדף הנחיתה של הפרויקט שלנו https://lightsaber.withgoogle.com. זהו קובץ HTML רגיל, אבל יש בו קסם:
<!-- 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>
כיום יש הרבה אפשרויות ליצירת אפליקציות שמבוססות על HTML5. ממשקי API, Frameworks, ספריות, מנועי משחקים וכו'. למרות כל האפשרויות, קשה להשיג הגדרה שתשלב בין שליטה על ביצועים גבוהים של גרפיקה לבין מבנה מודולרי וגמיש שניתן להתאים. גילינו ש-Polymer יכול לעזור לנו לשמור על הארגון של הפרויקט ועדיין לבצע אופטימיזציה ברמה נמוכה, ויצרנו בקפידה את הדרך שבה פירקנו את הפרויקט לרכיבים כדי למנף בצורה הטובה ביותר את היכולות של Polymer.
מודולריות עם פולימר
ספרייה בשם Polymer שמאפשרת לשפר מאוד את כוחו של הפרויקט, על בסיס אלמנטים מותאמים אישית שניתן לעשות בהם שימוש חוזר. הוא מאפשר להשתמש במודולים עצמאיים עם פונקציונליות מלאה בקובץ HTML יחיד. הם מכילים לא רק את המבנה (סימון HTML) אלא גם סגנונות ולוגיקה מוטבעים.
לדוגמה:
<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>
אבל בפרויקט גדול יותר, כדאי להפריד בין שלושת הרכיבים הלוגיים האלה (HTML, CSS, JS) ולמזג אותם רק בזמן ההידור. אחד מהדברים שעשינו הוא לתת לכל רכיב בפרויקט תיקייה נפרדת משלו:
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
לכל תיקייה של רכיב יש מבנה פנימי זהה, עם ספריות וקבצים נפרדים ללוגיקה (קובצי קפה), לסגנונות (קובצי scss) ולתבנית (קובץ ירקן).
הנה רכיב sw-ui-logo
לדוגמה:
sw-ui-logo/
|-- bower.json
|-- scripts
| `-- sw-ui-logo.coffee
|-- styles
| `-- sw-ui-logo.scss
`-- sw-ui-logo.jade
ואם תבדקו את הקובץ .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')
כדי לראות איך הדברים מאורגנים בצורה נקייה, אפשר לכלול סגנונות ולוגיקה מקבצים נפרדים. כדי לכלול את הסגנונות שלנו ברכיבי הפולימר, אנחנו משתמשים בהצהרה include
של ירקן כך שיש תוכן CSS בתוך השורה עצמה לאחר הידור. רכיב הסקריפט sw-ui-logo.js
יפעל בזמן הריצה.
סוגי תלות מודולריים עם באוור
בדרך כלל אנחנו שומרים על הספריות ויחסי תלות אחרים ברמת הפרויקט.
עם זאת, בהגדרות שלמעלה יופיע bower.json
שנמצא בתיקייה של הרכיב: תלות ברמת הרכיב. הרעיון שעומד מאחורי הגישה הזו הוא שבמצב שבו יש הרבה אלמנטים עם יחסי תלות שונים, אנחנו יכולים לוודא שנטענו רק את יחסי התלות שבאמת נמצאים בשימוש. אם מסירים רכיב, אין צורך לזכור
להסיר את התלות שלו כי מסירים גם את קובץ bower.json
שמצהיר על יחסי התלות האלה. כל רכיב טוען בנפרד את יחסי התלות הקשורים אליו.
עם זאת, כדי למנוע כפילות של יחסי תלות, אנחנו כוללים גם קובץ .bowerrc
בתיקייה של כל רכיב. כך נקבע לבאור איפה לאחסן יחסי תלות, כדי שנוכל לוודא שיש רק סוג אחד בסוף של אותה ספרייה:
{
"directory" : "../../../../../bower_components"
}
כך, אם כמה רכיבים מצהירים על THREE.js
כתלויות, ברגע שבאולר יתקין אותו עבור הרכיב הראשון ויתחיל לנתח את הרכיב השני, הוא יבין שהתלות הזו כבר מותקנת ולא תוריד אותה מחדש ולא תשכפל אותה. באופן דומה, קובצי התלות יישמרו כל עוד יש לפחות רכיב אחד שמגדיר אותם בקובץ bower.json
.
סקריפט bash מוצא את כל קובצי bower.json
במבנה הרכיבים המקונן.
לאחר מכן הוא מזין את הספריות האלה בזו אחר זו ומבצע bower install
בכל אחת מהן:
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
תבנית במהירות של רכיב חדש
לוקח זמן בכל פעם שרוצים ליצור רכיב חדש: יצירת התיקייה ומבנה הקובץ הבסיסי עם השמות הנכונים. אנחנו משתמשים ב-Slush כדי לכתוב מחולל רכיבים פשוט.
אפשר לקרוא לסקריפט משורת הפקודה:
$ slush element path/to/your/element-name
בנוסף, נוצר הרכיב החדש, כולל כל מבנה הקובץ והתוכן שלו.
הגדרנו תבניות לקובצי הרכיב. למשל, תבנית הקובץ .jade
נראית כך:
// 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')
מחולל ה-Slush מחליף את המשתנים בשמות ובנתיבים של הרכיבים בפועל.
שימוש ב-Gulp לבניית רכיבים
Gulp מאפשר לכם לשלוט בתהליך ה-build. ובמבנה שלנו, כדי ליצור את הרכיבים שנדרשים ב-Gulp, צריך לבצע את השלבים הבאים:
- הידור קובצי
.coffee
של הרכיבים ל-.js
- הידור קובצי
.scss
של הרכיבים ל-.css
- עריכת קובצי
.jade
של הרכיבים ב-.html
, תוך הטמעת הקבצים של.css
.
פרטים נוספים:
המערכת מעבדת את קובצי .coffee
של הרכיבים אל .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')));
});
בשלבים 2 ו-3 אנחנו משתמשים בפלאגין gulp ומצפן כדי להדר את scss
אל
.css
ו-.jade
אל .html
, באותה גישה ל-2 שלמעלה.
כולל אלמנטים פולימריים
כדי לכלול בפועל את רכיבי הפולימר, אנחנו משתמשים בייבוא 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">
אופטימיזציה של אלמנטים פולימריים לייצור
בפרויקט גדול יכולים להיות הרבה אלמנטים של פולימר. בפרויקט שלנו, יש לנו יותר מחמישים. אם מתייחסים לכל רכיב
לקובץ .js
נפרד ולחלק מהן יש הפניות לספריות, הוא הופך ליותר
100 קבצים נפרדים. זאת אומרת הרבה בקשות שהדפדפן צריך לשלוח,
וכתוצאה מכך גורמות לפגיעה בביצועים. בדומה לתהליך שרשור והקטנה שנוכל להחיל על build של Angular, אנחנו "מגייסים" את פרויקט הפולימר שבסוף הייצור שלו.
Vulcanize הוא כלי פולימר שמשטח את עץ התלות בקובץ HTML אחד, ומפחית את מספר הבקשות. זה שימושי במיוחד לדפדפנים שלא תומכים ברכיבי אינטרנט במקור.
CSP (Content Security Policy) ופולימר
כשאתם מפתחים אפליקציות אינטרנט מאובטחות, צריך להטמיע CSP. CSP היא קבוצת כללים שמונעים מתקפות של סקריפטים חוצי אתרים (XSS): הפעלה של סקריפטים ממקורות לא בטוחים או הפעלת סקריפטים מוטבעים מקובצי HTML.
עכשיו, קוד ה-JavaScript בקובץ .html
שעבר אופטימיזציה, משורשר ומוקטן שנוצר על ידי Vulcanize מכיל את כל קוד ה-JavaScript בפורמט שלא תואם ל-CSP. כדי לפתור את הבעיה אנחנו משתמשים בכלי שנקרא Crisper.
Crisper מפצל סקריפטים מוטבעים מקובץ HTML ומציב אותם לקובץ JavaScript חיצוני אחד, כדי לשמור על התאימות ל-CSP. אנחנו מעבירים את קובץ ה-HTML המגוחך דרך Crisper ובסוף מקבלים שני קבצים: elements.html
ו-elements.js
. ב-elements.html
אפשר גם לטעון את ה-elements.js
שנוצר.
מבנה לוגי של אפליקציות
בפולימר, האלמנטים יכולים להיות כל דבר, מכלי עזר לא חזותי, מרכיבי ממשק משתמש קטנים ועצמאיים ומיועדים לשימוש חוזר (כמו לחצנים) ועד למודולים גדולים יותר כמו "דפים", ואפילו חיבור של אפליקציות מלאות.
עיבוד לאחר עיבוד עם ארכיטקטורת הורים-ילדים
בכל צינור עיבוד נתונים של גרפיקה בתלת-ממד, תמיד יש שלב אחרון שבו נוספים אפקטים על התמונה כולה, כסוג של שכבת-על. זהו השלב שאחרי העיבוד והוא כולל אפקטים כמו זוהר, קרניים בסגנון זוהר, עומק שדה, בוקה, טשטוש וכו'. האפקטים משולבים ומחילים על אלמנטים שונים בהתאם לאופן שבו הסצנה בנויה. ב-THREE.js נוכל ליצור תוכנת הצללה מותאמת אישית לתהליך העיבוד ב-JavaScript, או לעשות זאת באמצעות Polymer, הודות למבנה ההורה-צאצא שלו.
אם תסתכלו על קוד ה-HTML של האלמנט של המעבד שלאחר המעבד:
<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>
אנחנו מגדירים את האפקטים כרכיבי פולימר מקוננים במחלקה משותפת. לאחר מכן, ב-sw-experience-postprocessor.js
אנחנו עושים את זה:
effects = @querySelectorAll '.effect'
@composer.addPass effect.getPass() for effect in effects
אנחנו משתמשים בתכונת ה-HTML וב-querySelectorAll
של JavaScript כדי למצוא את כל האפקטים שמוצבים כאלמנטים של HTML במעבד הפוסט, לפי הסדר שבו הם צוינו. לאחר מכן, אנחנו עוברים עליהם באופן קבוע ומוסיפים אותם למלחין.
עכשיו נניח שאנחנו רוצים להסיר את אפקט DOF (עומק שדה) ולשנות את סדר הפריחה ואפקטים של דהייה בשוליים. כל מה שאנחנו צריכים לעשות הוא לערוך את ההגדרה של המעבד המידע למשהו כזה:
<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>
והסצנה פשוט תפעל, בלי לשנות שורה אחת של קוד בפועל.
עיבוד לולאה ולולאת עדכון בפולימר
עם Polymer אנחנו יכולים גם לגשת לעיבוד ולעדכוני המנוע בצורה אלגנטית.
יצרנו רכיב timer
שמשתמש ב-requestAnimationFrame
ומחשב ערכים כמו הזמן הנוכחי (t
) וזמן דלתא - הזמן שחלף מהפריים האחרון (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()
לאחר מכן אנחנו משתמשים בקישור נתונים כדי לקשור את המאפיינים t
ו-dt
למנוע שלנו (experience.jade
):
sw-timer(
t='{ % templatetag openvariable % }t}}',
dt='{ % templatetag openvariable % }dt}}'
)
sw-experience-engine(
t='[t]',
dt='[dt]'
)
ואנחנו מקשיבים לשינויים של t
ו-dt
במנוע ובכל פעם שהערכים משתנים, הפונקציה _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
אבל אם רוצים לדעת יותר על FPS, כדאי להסיר את קישור הנתונים של Polymer בלולאת עיבוד כדי לחסוך כמה אלפיות השנייה שנדרשות כדי להודיע לרכיבים על השינויים. אנחנו מטמיעים את התצפית בהתאמה אישית באופן הבא:
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
# ...
הפונקציה addUpdateListener
מקבלת קריאה חוזרת ושומרת אותה במערך הקריאות החוזרות שלה. לאחר מכן, בלולאת העדכון, אנחנו מבצעים חזרה על כל קריאה חוזרת (callback) ומפעילים אותה ישירות באמצעות ארגומנטים מסוג dt
ו-t
, תוך עקיפת קישור הנתונים או הפעלת האירוע. הוספנו פונקציית removeUpdateListener
שמאפשרת לבטל את הקריאה החוזרת (callback) שכבר השתמשתם בה בעבר.
חרב אור ב-THREE.js
THREE.js מקצר את הפירוט ברמה הנמוכה של WebGL ומאפשר לנו להתמקד בבעיה. הבעיה שלנו היא להילחם בחיילי סער ואנחנו זקוקים לכלי נשק. בואו נבנה חרב אור.
הלהב הזוהר הוא שמבדיל בין חרב האור מכל כלי נשק דו-ידיים ישן. הוא מורכב בעיקר משני חלקים: הקורה והשביל שנראה כשמזיזים אותו. יצרנו אותו בצורת גליל בהירה ומסלול דינמי שעוקב אחריו בזמן שהשחקן זז.
הלהב
הלהב מורכב משני להבי משנה. אינטרקציה פנימית וחיצונית. שתיהן הן רשת THREE.js עם החומרים התואמים שלהן.
הלהב הפנימי
ללהב הפנימי השתמשנו בחומר בהתאמה אישית עם מנגנון הצללה בהתאמה אישית. אנחנו לוקחים קו שנוצר על ידי שתי נקודות ומקרינים את הקו שבין שתי הנקודות האלה על מישור. המטוס הזה הוא בעצם מה ששולטים בו כשנלחמים עם הנייד, הוא נותן תחושה של עומק וכיוון של החרב.
כדי ליצור את התחושה של אובייקט זוהר, אנחנו מסתכלים על המרחק האורתוגונלי של כל נקודה במישור מהקו הראשי ומחברים את שתי הנקודות A ו-B כפי שמתואר בהמשך. ככל שהנקודה קרובה יותר לציר הראשי, היא בהירה יותר.
במקור שבהמשך מוסבר איך אנחנו מחשבים vFactor
כדי לשלוט בעוצמת ההצללה של קודקוד הקוד, ואז להשתמש בו כדי למזג עם הסצנה
בתוכנת ההצללה בקטע.
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" )
};
הלהב החיצוני זוהר
כדי ליצור את הזוהר החיצוני, אנחנו מבצעים רינדור למאגר אחסון נפרד ומשתמשים באפקט הפריחה לאחר העיבוד ומתמזגים עם התמונה הסופית כדי לקבל את הזוהר הרצוי. התמונה הבאה מציגה את שלושת האזורים השונים שצריך להשתמש בהם כדי לשמור על חרב הגונה. כלומר, הליבה הלבנה, הזוהר הכחול האמצעי והזוהר החיצוני.
מסלול חרב האור
השובל של חרב האור הוא המפתח לאפקט המלא, כמו המקור שמוצג בסדרת מלחמת הכוכבים. השלמנו את השביל עם מאוורר של משולשים שנוצר באופן דינמי בהתאם לתנועה של חרב האור. לאחר מכן, המאווררים מועברים אל המעבד לעיבוד המידע לשיפור חזותי נוסף. כדי ליצור את הגיאומטריה של האוורור, יש לנו מקטע קו, ועל סמך הטרנספורמציה הקודמת שלו והטרנספורמציה הנוכחית, אנחנו יוצרים משולש חדש ברשת ומסירים את חלק הזנב אחרי אורך מסוים.
כשיש לנו רשת, אנחנו מקצים לה חומר פשוט ומעבירים אותו לאחר מכן לעיבוד כדי ליצור אפקט חלק. אנחנו משתמשים באותו אפקט פריחה שהחלנו על הזוהר החיצוני של הלהב ויוצרים שובל חלק כמו שאפשר לראות:
זוהרים סביב השביל
כדי שהיצירה הסופית תהיה מלאה, היינו צריכים להתמודד עם הזוהר מסביב לשביל האמיתי, שאותו ניתן ליצור בכמה דרכים. לכן, מסיבות שקשורות לביצועים, לא נפרט את הפתרון שלנו, הוא ליצור מארגן בהתאמה אישית למאגר הזה שיוצר קצה חלק מסביב למהדק של מאגר הנתונים הזמני. לאחר מכן נשלב את הפלט הזה בעיבוד הסופי וכאן תוכלו לראות את הזוהר שמקיף את השביל:
סיכום
Polymer הוא ספרייה וקונספט רבי-עוצמה (כמו WebComponents באופן כללי). ההחלטה שלך תלויה רק בך. זה יכול להיות כל דבר, מלחצן ממשק משתמש פשוט ועד אפליקציית WebGL בגודל מלא. בפרקים הקודמים הצגנו כמה טיפים וטריקים לשימוש יעיל בפולימר בסביבת הייצור, ואיך לבנות מודולים מורכבים יותר שמניבים ביצועים טובים. הראינו לך גם כיצד להשיג חרב אור נעימה ב-WebGL. לכן אם משלבים את כל הגורמים האלה, חשוב לזכור לפרוץ את רכיבי הפולימר לפני הפריסה לשרת הייצור. אם רוצים להמשיך להשתמש ב-Crisper כדי לעמוד בתאימות ל-CSP, ייתכן שהכוח יהיה איתכם!