
สรุป
วิธีที่เราใช้ Polymer สร้าง WebGL ขั้นสูงที่ควบคุมในอุปกรณ์เคลื่อนที่ ไลท์เซเบอร์ที่สามารถปรับแต่งได้ เรามาดูรายละเอียดสำคัญกัน ของโปรเจ็กต์ของเรา https://lightsaber.withgoogle.com/ เพื่อช่วยประหยัดเวลาในการสร้างผลงานของคุณเอง ในครั้งต่อไปที่คุณประสบปัญหาบางอย่าง สตอร์มทรูปเปอร์โกรธ
ภาพรวม
หากคุณสงสัยว่า Polymer หรือ WebComponents คืออะไร ควรจะเริ่มต้นโดยแชร์การดึงข้อมูลจากโครงการที่ใช้งานอยู่จริง นี่คือตัวอย่างที่ได้มาจากหน้า Landing Page ของโครงการของเรา 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, เฟรมเวิร์ก, ไลบรารี, เครื่องมือเกม ฯลฯ แม้ว่าจะมีทางเลือกอื่นๆ มากมาย แต่ก็ยากที่จะหาการตั้งค่าที่ผสมผสานกันอย่างลงตัว ระหว่างการควบคุมกราฟิกประสิทธิภาพสูงและโมดูลที่สะอาดตา และความสามารถในการปรับขนาด เราพบว่า 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>
แต่ในโปรเจ็กต์ที่ใหญ่กว่า คุณควรแยกทั้ง 3 องค์ประกอบนี้ออกจากกัน คอมโพเนนต์ (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
และโฟลเดอร์ของแต่ละองค์ประกอบมีโครงสร้างภายในเหมือนกัน ไดเรกทอรีและไฟล์สำหรับตรรกะ (ไฟล์กาแฟ) รูปแบบ (ไฟล์ SMS) และ (ไฟล์ Jade)
ต่อไปนี้คือตัวอย่างเอลิเมนต์ 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
ของ Jade เราจึงมี CSS ในบรรทัดจริง
เนื้อหาไฟล์หลังการคอมไพล์ องค์ประกอบสคริปต์ sw-ui-logo.js
จะ
ดำเนินการที่รันไทม์
การขึ้นต่อกันแบบโมดูลพร้อมเครื่องวัด
โดยปกติแล้วเราจะเก็บไลบรารีและทรัพยากร Dependency อื่นๆ ไว้ที่ระดับโปรเจ็กต์
แต่ในการตั้งค่าข้างต้น คุณจะเห็น bower.json
ที่
โฟลเดอร์ขององค์ประกอบ: ทรัพยากร Dependency ระดับองค์ประกอบ แนวคิดเบื้องหลังวิธีการนี้
คือในสถานการณ์ที่คุณมีองค์ประกอบต่างๆ ที่มี
เราสามารถตรวจสอบได้ เพื่อโหลดเฉพาะทรัพยากร Dependency ที่
ใช้งานจริง หากนำองค์ประกอบออก คุณก็ไม่ต้องจำว่า
นำการอ้างอิงออก เนื่องจากคุณจะนำไฟล์ bower.json
ออกด้วย
ที่ประกาศทรัพยากร Dependency เหล่านี้ องค์ประกอบแต่ละรายการจะโหลด
ทรัพยากร Dependency ที่เกี่ยวข้อง
อย่างไรก็ตาม เราได้รวมไฟล์ .bowerrc
ไว้ด้วยเพื่อหลีกเลี่ยงความซ้ำซ้อนของทรัพยากร Dependency
ในโฟลเดอร์ขององค์ประกอบแต่ละรายการด้วย ข้อมูลนี้จะบอกตำแหน่งที่ควรจัดเก็บ
ทรัพยากร Dependency ต่างๆ เพื่อให้เรามั่นใจได้ว่ามีเพียงตัวเดียวในตอนท้าย
ไดเรกทอรี:
{
"directory" : "../../../../../bower_components"
}
ด้วยวิธีนี้ หากองค์ประกอบหลายรายการประกาศ THREE.js
เป็นทรัพยากร Dependency 1 ครั้ง
Bower จะติดตั้งสำหรับองค์ประกอบแรก และเริ่มแยกวิเคราะห์องค์ประกอบที่ 2
ระบบจะทราบว่ามีการติดตั้งทรัพยากร Dependency นี้ไว้แล้วและจะไม่
โปรดดาวน์โหลดซ้ำหรือทำซ้ำ ในทำนองเดียวกัน ก็จะคงทรัพยากร Dependency นั้นไว้
ตราบใดที่ยังมีองค์ประกอบอย่างน้อย 1 อย่างที่ยังคงกำหนดไฟล์
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 ทำให้กระบวนการบิลด์อยู่ภายใต้การควบคุม และในโครงสร้าง การสร้าง องค์ประกอบที่เราจำเป็นต้องใช้ 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 เราใช้ "เอว" และปลั๊กอินเข็มทิศในการคอมไพล์ scss
เพื่อ
.css
และ .jade
กับ .html
ในลักษณะเดียวกับ 2 ด้านบน
รวมถึงองค์ประกอบโพลิเมอร์
ในการรวมองค์ประกอบ Polymer เราใช้การนำเข้า 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">
กำลังเพิ่มประสิทธิภาพองค์ประกอบ Polymer เพื่อการผลิต
โปรเจ็กต์ขนาดใหญ่อาจมีองค์ประกอบโพลิเมอร์จำนวนมาก ใน
เรามีมากกว่า 50 โปรเจ็กต์ ถ้าคุณพิจารณาว่าแต่ละองค์ประกอบมี
แยก .js
ไฟล์และบางไฟล์มีการอ้างอิงไลบรารี ไฟล์มีขนาดมากกว่า
100 ไฟล์แยกกัน ซึ่งมีคำขอมากมายที่เบราว์เซอร์ต้องทำ
โดยสูญเสียประสิทธิภาพการทำงาน ในลักษณะเดียวกันกับกระบวนการเชื่อมต่อและลดขนาด
จะนำมาใช้กับงานสร้าง Angular ได้ เราจึงทำการ "แยกตัว" โครงการ Polymer ที่
สำหรับเวอร์ชันที่ใช้งานจริง
Vulcanize คือเครื่องมือพอลิเมอร์ที่ รวมต้นไม้ทรัพยากร Dependency ไว้เป็นไฟล์ HTML ไฟล์เดียว จำนวนคำขอ วิธีนี้เหมาะอย่างยิ่งสำหรับเบราว์เซอร์ที่ไม่ รองรับคอมโพเนนต์เว็บในตัว
CSP (นโยบายรักษาความปลอดภัยเนื้อหา) และ Polymer
เมื่อพัฒนาเว็บแอปพลิเคชันที่ปลอดภัย คุณจำเป็นต้องนำ CSP มาใช้ CSP เป็นชุดของกฎที่ป้องกันการโจมตีแบบ Cross-site Scripting (XSS) ดังนี้ การดำเนินการกับสคริปต์จากแหล่งที่มาที่ไม่ปลอดภัย หรือการเรียกใช้สคริปต์แบบอินไลน์ จากไฟล์ HTML
ตอนนี้สร้างไฟล์ .html
ที่มีการเพิ่มประสิทธิภาพ เชื่อมต่อ และลดขนาดแล้ว
โดย Vulcanize มีโค้ด JavaScript ทั้งหมดในหน้าที่ไม่สอดคล้องกับ CSP
ในการแก้ปัญหานี้ เราใช้เครื่องมือชื่อ
Crisper
Crisper แยกสคริปต์แบบในหน้าจากไฟล์ HTML แล้วแบ่งออกเป็นไฟล์เดียว
ไฟล์ JavaScript ภายนอกสำหรับการปฏิบัติตามข้อกำหนดของ CSP เราจึงส่งผ่านวัลคาไนซ์
HTML ผ่าน Crisper ได้ และลงท้ายด้วยไฟล์ 2 ไฟล์ ได้แก่ elements.html
และ
elements.js
ใน elements.html
ยังช่วยโหลด
สร้างรายได้ elements.js
โครงสร้างตรรกะแอปพลิเคชัน
ในพอลิเมอร์ องค์ประกอบต่างๆ อาจเป็นอะไรก็ได้ ตั้งแต่ยูทิลิตีที่ไม่มีภาพไปจนถึงขนาดเล็ก องค์ประกอบ UI แบบสแตนด์อโลนและใช้ซ้ำได้ (เช่น ปุ่ม) ไปจนถึงโมดูลที่ใหญ่กว่า เช่น "หน้า" และแม้แต่การเขียนแอปพลิเคชันเต็มรูปแบบ

การประมวลผลข้อมูลหลังการประมวลผลด้วยพอลิเมอร์และสถาปัตยกรรมหลัก-ย่อย
ในไปป์ไลน์กราฟิก 3 มิติ จะมีขั้นตอนสุดท้ายที่เอฟเฟกต์ จะปรากฏอยู่ด้านบนของทั้งภาพในรูปแบบของการวางซ้อน นี่คือ หลังการประมวลผล รวมถึงผลกระทบต่างๆ เช่น การเรืองแสง แสงสะท้อน ระยะชัดลึก โบเก้ เบลอ ฯลฯ เอฟเฟ็กต์จะถูกรวมเข้าด้วยกันและนำไปใช้กับ องค์ประกอบต่างๆ ตามวิธีการสร้างฉาก ใน 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>
เราระบุเอฟเฟกต์เป็นองค์ประกอบ Polymer ที่ฝังอยู่ใต้คลาสทั่วไป จากนั้นให้ทำดังนี้
ใน 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
นอกจากนี้ 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 ออก การเชื่อมโยงในการแสดงผลลูปเพื่อประหยัดเวลา 2-3 มิลลิวินาทีที่จำเป็นต่อการแจ้งเตือน องค์ประกอบเกี่ยวกับการเปลี่ยนแปลง เราติดตั้งใช้งานผู้สังเกตการณ์ที่กำหนดเองดังนี้
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 และบันทึกไว้ใน
อาร์เรย์ Callback จากนั้น ในลูปการอัปเดต เราจะปรับปรุง
ทุกๆ Callback และ
เราจะดำเนินการด้วยอาร์กิวเมนต์ dt
และ t
โดยตรง เป็นการข้ามการเชื่อมโยงข้อมูลหรือ
การเริ่มทำงานของเหตุการณ์ เมื่อ Callback ไม่ทำงานอีกต่อไป เราจึงเพิ่ม
removeUpdateListener
ที่ช่วยให้คุณนำ Callback ที่เพิ่มไว้ก่อนหน้านี้ออกได้
ไลท์เซเบอร์ใน THREE.js
THREE.js ตัดรายละเอียดระดับต่ำของ WebGL และช่วยให้เราโฟกัส เกี่ยวกับปัญหาดังกล่าว ปัญหาของเราคือการต่อสู้กับสตอร์มทรูปเปอร์ และเราต้องการ อาวุธ มาสร้างไลท์เซเบอร์กัน
คมด้านที่เรืองแสงเป็นสิ่งที่ทำให้ดาบไลท์เซเบอร์แตกต่างจากดาบโบราณ อาวุธ 2 มือ ซึ่งประกอบด้วย 2 ส่วนเป็นหลัก ได้แก่ ลำแสงและเส้นทางเดิน ซึ่งมองเห็นได้เมื่อย้ายตำแหน่ง เราสร้างเมืองนี้ขึ้นมาโดยมีรูปทรงกระบอกสีสดใส และเส้นทางแบบไดนามิกที่ตามมาเมื่อผู้เล่นเคลื่อนที่
เดอะ เบลด
ใบมีดประกอบด้วยใบมีดย่อย 2 ใบ ทั้งภายในและภายนอก ทั้งคู่เป็น THREE.js ที่รวมเข้าด้วยกันโดยใช้วัสดุที่เกี่ยวข้อง
ใบมีดด้านใน
สำหรับใบมีดด้านใน เราใช้วัสดุที่ออกแบบเองพร้อมตัวปรับเฉดสีแบบกำหนดเอง พ นำเส้นที่สร้างขึ้นจากจุด 2 จุดมาแสดงเส้นแบ่งระหว่าง 2 จุดนี้ คะแนนเครื่องบิน โดยพื้นฐานแล้ว เครื่องบินนี้คือสิ่งที่คุณควบคุมได้ ต่อสู้ด้วยอุปกรณ์เคลื่อนที่ ซึ่งให้ความรู้สึกของความลึกและการวางแนว แทนดาบ
ในการสร้างความรู้สึกของวัตถุเรืองแสงทรงกลมที่เรามองที่ ระยะห่างของจุดตั้งฉากของจุดใดก็ตามบนระนาบจากแกนหลัก เส้นที่เชื่อมกับจุด 2 จุด A และ B ตามด้านล่าง ยิ่งจุดอยู่ใกล้ แกนหลักจะยิ่งสว่างมาก

แหล่งที่มาด้านล่างแสดงวิธีที่เราประมวลผล vFactor
เพื่อควบคุมความเข้ม
ในตัวปรับแสงเงา Vertex เพื่อใช้กลืนไปกับฉากใน
ตัวปรับแสงเงา Fragment
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" )
};
แสงด้านนอกใบมีดเรืองแสง
สำหรับการเรืองแสงด้านนอก เราแสดงผลไปยัง Renderbuffer แยกต่างหากและใช้ เอฟเฟกต์ดอกไม้หลังการประมวลผล และผสานกับภาพสุดท้ายเพื่อให้ได้ เรืองแสงที่ต้องการ รูปภาพด้านล่างจะแสดง 3 ภูมิภาคที่แตกต่างกัน ถ้าคุณอยากได้ดาบที่ดูดี ได้แก่ แกนสีขาว ตรงกลาง เปล่งประกายสีฟ้าและแสงด้านนอก

เส้นทางไลท์เซเบอร์
เส้นทางของไลท์เซเบอร์คือกุญแจสำคัญในการสร้างผลลัพธ์เต็มรูปแบบเช่นเดียวกับต้นฉบับที่เห็น ในซีรีส์ Star Wars เราสร้างเส้นทางเดินนี้โดยใช้รูปสามเหลี่ยมต่างๆ ที่สร้างสรรค์ขึ้น แบบไดนามิกตามการเคลื่อนที่ของไลท์เซเบอร์ แฟนๆ เหล่านี้จะ ไปยังโพสต์โปรเซสเซอร์เพื่อปรับปรุงภาพให้ดีขึ้น วิธีสร้าง เรขาคณิตของพัดลม เรามีส่วนของเส้นตรง และขึ้นอยู่กับการแปลงก่อนหน้านี้ และการเปลี่ยนรูปแบบปัจจุบันสร้างรูปสามเหลี่ยมใหม่ใน Mesh แล้วลดลง ออกจากส่วนหางหลังจากความยาวที่กำหนด


เมื่อเรามีตาข่ายแล้ว เราจะกำหนดวัสดุง่ายๆ ให้และส่งต่อไปยัง เพื่อสร้างเอฟเฟ็กต์ที่ราบรื่น เราใช้เอฟเฟกต์ดอกไม้แบบเดียวกับที่ ที่ใช้กับใบมีดด้านนอกที่เรืองแสงและดูเส้นทางที่ราบรื่นอย่างที่เห็น ดังนี้

เรืองแสงรอบๆ ทางเดิน
เพื่อให้งานชิ้นสุดท้ายเสร็จสมบูรณ์ เราต้องจัดการกับการเรืองแสง เส้นทาง ซึ่งอาจสร้างได้หลายวิธี วิธีแก้ปัญหาที่เรา ไม่ได้ลงรายละเอียดในส่วนนี้ เนื่องจากเหตุผลด้านประสิทธิภาพคือการสร้าง ตัวปรับแสงเงาสำหรับบัฟเฟอร์นี้ ซึ่งสร้างขอบที่เรียบเนียนบริเวณที่หนีบของ Renderbuffer จากนั้นเราจะรวมเอาต์พุตนี้ในการแสดงภาพสุดท้าย ซึ่ง ดูแสงรอบๆ เส้นทางเดิน

บทสรุป
Polymer คือไลบรารีและแนวคิดที่มีประสิทธิภาพ (เช่นเดียวกับ WebComponents ทั่วไป) ก็ขึ้นอยู่กับคุณว่าจะทำอะไรบ้าง อาจเป็นอะไรก็ได้ตั้งแต่ ปุ่ม UI ง่ายๆ ไปยังแอปพลิเคชัน WebGL ขนาดเต็ม ในบทก่อนหน้า เราได้แสดงกลเม็ดเคล็ดลับในการใช้ Polymer อย่างมีประสิทธิภาพ จริงอยู่ที่วิธีจัดโครงสร้างโมดูล ที่ซับซ้อนมากขึ้นซึ่งมี เรายังได้บอกวิธีเล่นไลท์เซเบอร์ที่ดูดีใน WebGL ด้วย ดังนั้นหากรวมทั้งหมดแล้ว ก็อย่าลืมวัลคาไนองค์ประกอบโพลิเมอร์ ก่อนทำให้ใช้งานได้ในเซิร์ฟเวอร์ที่ใช้งานจริง และหากคุณไม่ลืมใช้ Crisper หากต้องการปฏิบัติตามข้อกำหนดของ CSP ขอพลังกับคุณได้เลย
