ประสบการณ์ Hobbit

การสร้าง Middle-Earth ให้มีชีวิตชีวาด้วย Mobile WebGL

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

เมื่อต้นปีที่ผ่านมา เราเริ่มโปรเจ็กต์ร่วมกับเพื่อนๆ จาก Google และ Warner Bros. เพื่อสร้างประสบการณ์การใช้งานเว็บเพื่ออุปกรณ์เคลื่อนที่เป็นอันดับแรกสำหรับภาพยนตร์เรื่องใหม่ของฮอบบิทอย่าง The Hobbit: The Desolation of Smaug การสร้างเวอร์ชันทดลองของ Chrome บนอุปกรณ์เคลื่อนที่ซึ่งมีมัลติมีเดียจำนวนมากเป็นงานที่ท้าทายและสร้างแรงบันดาลใจอย่างมาก

ประสบการณ์การใช้งานนี้ได้รับการเพิ่มประสิทธิภาพสำหรับ Chrome สำหรับ Android ในอุปกรณ์ Nexus รุ่นใหม่ที่เรามีสิทธิ์เข้าถึง WebGL และ Web Audio แล้ว อย่างไรก็ตาม ประสบการณ์การใช้งานส่วนใหญ่จะเข้าถึงได้ในอุปกรณ์และเบราว์เซอร์ที่ไม่ใช่ WebGL ได้ด้วย เนื่องจากการคอมโพสิตที่เร่งด้วยฮาร์ดแวร์และภาพเคลื่อนไหว CSS

ประสบการณ์ทั้งหมดนี้อิงตามแผนที่ของมัชฌิมโลก รวมถึงสถานที่และตัวละครจากภาพยนตร์ฮอบบิท การใช้ WebGL ช่วยให้เราสามารถถ่ายทอดและสำรวจโลกอันกว้างใหญ่ของไตรภาคฮอบบิท และช่วยให้ผู้ใช้ควบคุมประสบการณ์การใช้งานได้

ปัญหาของ WebGL ในอุปกรณ์เคลื่อนที่

ประการแรกคือคำว่า "อุปกรณ์เคลื่อนที่" มีความกว้างมาก ข้อมูลจำเพาะของอุปกรณ์มีความแตกต่างกันมาก ดังนั้นในฐานะนักพัฒนาแอป คุณต้องตัดสินใจว่าต้องการรองรับอุปกรณ์จำนวนมากขึ้นด้วยประสบการณ์การใช้งานที่ซับซ้อนน้อยลง หรือจำกัดอุปกรณ์ที่รองรับไว้เฉพาะอุปกรณ์ที่แสดงโลก 3 มิติได้อย่างสมจริงมากขึ้น ดังที่เราทำในกรณีนี้ สำหรับ "การเดินทางผ่านมิดเดิ้ลเอิร์ธ" เรามุ่งเน้นที่อุปกรณ์ Nexus และสมาร์ทโฟน Android ยอดนิยม 5 รุ่น

ในการทดสอบ เราใช้ three.js เช่นเดียวกับที่ใช้ในโปรเจ็กต์ WebGL ก่อนหน้านี้ เราเริ่มใช้งานด้วยการสร้างเกม Trollshaw เวอร์ชันแรกที่จะทำงานได้ดีในแท็บเล็ต Nexus 10 หลังจากการทดสอบเบื้องต้นในอุปกรณ์ เราพบรายการการเพิ่มประสิทธิภาพที่คล้ายกับสิ่งที่เรามักจะใช้กับแล็ปท็อปที่มีสเปคต่ำ ดังนี้

  • ใช้โมเดลแบบ Low-Poly
  • ใช้พื้นผิวที่มีความละเอียดต่ำ
  • ลดจำนวนการเรียกใช้การวาดให้น้อยที่สุดโดยการผสานเรขาคณิต
  • ใช้วัสดุและแสงอย่างง่าย
  • นำเอฟเฟกต์หลังการประมวลผลออกและปิดการปรับเกลี่ย
  • เพิ่มประสิทธิภาพ JavaScript
  • แสดงผลแคนวาส WebGL เป็นครึ่งขนาดและปรับขนาดด้วย CSS

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

ใช้โมเดลแบบ Low-Poly

มาเริ่มกันที่โมเดลกัน การใช้โมเดลแบบ Low Poly จะช่วยประหยัดเวลาในการดาวน์โหลดและเวลาในการเริ่มต้นฉากได้อย่างแน่นอน เราพบว่าสามารถเพิ่มความซับซ้อนได้ค่อนข้างมากโดยไม่ส่งผลกระทบต่อประสิทธิภาพมากนัก โมเดลโทรลล์ที่เราใช้ในเกมนี้มีใบหน้าประมาณ 5,000 ใบหน้า และฉากมีใบหน้าประมาณ 40,000 ใบหน้า ซึ่งก็ทำงานได้ดี

โทรลล์ตัวหนึ่งในป่า Trollshaw
หนึ่งในโทรลล์ของป่าทรอลชอว์

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

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

ใช้พื้นผิวที่มีความละเอียดต่ำ

เราเลือกโหลดพื้นผิวต่างๆ ที่มีขนาดครึ่งหนึ่งของพื้นผิวในเดสก์ท็อปเพื่อลดเวลาในการโหลดในอุปกรณ์เคลื่อนที่ ปรากฏว่าอุปกรณ์ทั้งหมดสามารถรองรับขนาดพื้นผิวได้สูงสุด 2048x2048 พิกเซล และส่วนใหญ่รองรับ 4096x4096 พิกเซล การค้นหาพื้นผิวในพื้นผิวแต่ละรายการไม่น่าจะมีปัญหาเมื่ออัปโหลดไปยัง GPU ขนาดรวมของพื้นผิวต้องพอดีกับหน่วยความจำ GPU เพื่อหลีกเลี่ยงการอัปโหลดและดาวน์โหลดพื้นผิวอย่างต่อเนื่อง แต่นี่อาจไม่ใช่ปัญหาใหญ่สำหรับประสบการณ์การใช้งานเว็บส่วนใหญ่ อย่างไรก็ตาม การรวมพื้นผิวไว้ใน Sprite Sheet น้อยที่สุดเท่าที่จะเป็นไปได้เป็นสิ่งสําคัญในการลดจํานวนการเรียกใช้การวาด ซึ่งเป็นสิ่งที่ส่งผลต่อประสิทธิภาพในอุปกรณ์เคลื่อนที่อย่างมาก

พื้นผิวสำหรับหนึ่งในโทรลล์ของป่าทรอลชอว์
พื้นผิวสำหรับหนึ่งในโทรลล์ของป่าทรอลชอว์
(ขนาดต้นฉบับ 512x512 พิกเซล)

ใช้วัสดุและแสงอย่างง่าย

การเลือกวัสดุยังส่งผลต่อประสิทธิภาพอย่างมากและต้องจัดการอย่างชาญฉลาดบนอุปกรณ์เคลื่อนที่ การใช้ MeshLambertMaterial (การคำนวณแสงต่อจุดยอด) ใน three.js แทน MeshPhongMaterial (การคำนวณแสงต่อ texel) คือสิ่งที่เราใช้เพื่อเพิ่มประสิทธิภาพ โดยพื้นฐานแล้ว เราพยายามใช้เชดเดอร์ที่เรียบง่ายและคำนวณแสงให้น้อยที่สุด

หากต้องการดูว่าวัสดุที่คุณใช้ส่งผลต่อประสิทธิภาพของฉากอย่างไร คุณสามารถลบล้างวัสดุของฉากด้วย MeshBasicMaterial ซึ่งจะช่วยให้คุณเห็นภาพการเปรียบเทียบที่ดี

scene.overrideMaterial = new THREE.MeshBasicMaterial({color:0x333333, wireframe:true});

เพิ่มประสิทธิภาพ JavaScript

เมื่อสร้างเกมสำหรับอุปกรณ์เคลื่อนที่ GPU อาจไม่ใช่อุปสรรคที่ใหญ่ที่สุดเสมอไป ใช้เวลากับ CPU นาน โดยเฉพาะฟิสิกส์และภาพเคลื่อนไหวโครงกระดูก เคล็ดลับหนึ่งที่บางครั้งอาจช่วยได้ (ขึ้นอยู่กับการจําลอง) คือให้เรียกใช้การคํานวณที่ใช้เวลานานเหล่านี้ทุกๆ 2 เฟรม นอกจากนี้ คุณยังใช้เทคนิคการเพิ่มประสิทธิภาพ JavaScript ที่มีให้สำหรับพูลออบเจ็กต์ การเก็บขยะและการสร้างออบเจ็กต์ได้ด้วย

การอัปเดตออบเจ็กต์ที่จัดสรรไว้ล่วงหน้าในลูปแทนการสร้างออบเจ็กต์ใหม่เป็นขั้นตอนสำคัญในการหลีกเลี่ยง "ปัญหาขัดข้อง" ในการรวบรวมขยะระหว่างเกม

ตัวอย่างเช่น ลองดูโค้ดต่อไปนี้

var currentPos = new THREE.Vector3();

function gameLoop() {
  currentPos = new THREE.Vector3(0+offsetX,100,0);
}

ลูปเวอร์ชันปรับปรุงนี้จะช่วยหลีกเลี่ยงการสร้างออบเจ็กต์ใหม่ที่ต้องเก็บขยะ

var originPos = new THREE.Vector3(0,100,0);
var currentPos = new THREE.Vector3();
function gameLoop() {
  currentPos.copy(originPos).x += offsetX;
  //or
  currentPos.set(originPos.x+offsetX,originPos.y,originPos.z);
}

ตัวแฮนเดิลเหตุการณ์ควรอัปเดตเฉพาะพร็อพเพอร์ตี้ และปล่อยให้ requestAnimationFrame เรนเดอร์ลูปจัดการการอัปเดตระยะ

อีกเคล็ดลับหนึ่งคือการเพิ่มประสิทธิภาพและ/หรือคํานวณการทํางานของเรย์แคสติ้งล่วงหน้า ตัวอย่างเช่น หากต้องการแนบวัตถุกับเมชในระหว่างการเคลื่อนไหวตามเส้นทางแบบคงที่ คุณสามารถ "บันทึก" ตำแหน่งระหว่างลูป 1 รอบ แล้วอ่านจากข้อมูลนี้แทนการแคสต์เรย์กับเมช หรือจะใช้วิธีที่เราใช้ในประสบการณ์การใช้งาน Rivendell ก็ได้ ซึ่งก็คือใช้เรย์แคสต์เพื่อมองหาการโต้ตอบของเม้าส์กับเมชที่มองไม่เห็นแบบ Low Poly ที่เรียบง่ายกว่า การค้นหาการชนในเมชที่มีความละเอียดสูงนั้นช้ามากและควรหลีกเลี่ยงในลูปเกมโดยทั่วไป

แสดงผลแคนวาส WebGL เป็นครึ่งขนาดและปรับขนาดด้วย CSS

ขนาดของผืนผ้าใบ WebGL น่าจะเป็นพารามิเตอร์ที่มีประสิทธิภาพมากที่สุดที่คุณปรับเพื่อเพิ่มประสิทธิภาพได้ ยิ่งภาพพิมพ์แคนวาสที่คุณใช้วาดฉาก 3 มิติมีขนาดใหญ่เท่าใด ก็ยิ่งต้องวาดพิกเซลในเฟรมแต่ละเฟรมมากขึ้นเท่านั้น ซึ่งแน่นอนว่าส่งผลต่อประสิทธิภาพ Nexus 10 ที่มีจอแสดงผลความละเอียดสูง 2560x1600 พิกเซลต้องแสดงผลพิกเซลมากกว่าแท็บเล็ตที่มีความละเอียดต่ำถึง 4 เท่า เราใช้เคล็ดลับในการเพิ่มประสิทธิภาพนี้สำหรับอุปกรณ์เคลื่อนที่ โดยตั้งค่าแคนวาสให้มีขนาดครึ่งหนึ่ง (50%) แล้วปรับขนาดให้ใหญ่ขึ้นเป็นขนาดที่ต้องการ (100%) ด้วยการเปลี่ยนรูปแบบ 3 มิติของ CSS ที่เร่งด้วยฮาร์ดแวร์ ข้อเสียของวิธีนี้คือรูปภาพจะแตกเป็นพิกเซลเมื่อใช้เส้นบางๆ แต่บนหน้าจอความละเอียดสูง ผลลัพธ์ที่ได้จะไม่แย่มาก คุ้มค่ากับประสิทธิภาพที่เพิ่มขึ้นอย่างแน่นอน

ฉากเดียวกันนี้โดยไม่มีการปรับขนาดภาพพิมพ์แคนวาสใน Nexus 10 (16 FPS) และปรับขนาดเป็น 50% (33 FPS)
ฉากเดียวกันนี้โดยไม่มีการปรับขนาดภาพพิมพ์แคนวาสใน Nexus 10 (16 FPS) และปรับขนาดเป็น 50% (33 FPS)

ออบเจ็กต์เป็นองค์ประกอบพื้นฐาน

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

องค์ประกอบพื้นฐานของวัตถุ 3 มิติที่ใช้ในเขาวงกตของดอลกุลดูร์
องค์ประกอบพื้นฐานของวัตถุ 3 มิติที่ใช้ในเขาวงกตของดอล กูลดูร์

ใน Rivendell เรามีพื้นบางส่วนที่เราจัดตำแหน่งใหม่ในเชิงลึก Z อยู่ตลอดเมื่อเส้นทางของผู้ใช้ดำเนินไป เมื่อผู้ใช้ผ่านส่วนต่างๆ สิ่งเหล่านี้จะเปลี่ยนตำแหน่งให้อยู่ไกลออกไป

สำหรับปราสาท Dol Guldur เราต้องการให้เขาวงกตสร้างขึ้นใหม่ทุกครั้งที่เล่น ด้วยเหตุนี้ เราจึงสร้างสคริปต์ที่สร้างเขาวงกตขึ้นมาใหม่

การผสานโครงสร้างทั้งหมดเป็นเมชขนาดใหญ่ตั้งแต่ต้นจะทำให้ฉากมีขนาดใหญ่มากและมีประสิทธิภาพต่ำ ในการแก้ปัญหานี้ เราจึงตัดสินใจซ่อนและแสดงองค์ประกอบพื้นฐานโดยขึ้นอยู่กับว่าองค์ประกอบนั้นอยู่ในมุมมองหรือไม่ ตั้งแต่เริ่มต้น เรามีความคิดว่าจะใช้สคริปต์เรย์แคสเตอร์ 2 มิติ แต่สุดท้ายเราใช้Frustum Culling ของ three.js ในตัว เราใช้สคริปต์ Raycaster ซ้ำเพื่อซูมเข้า "อันตราย" ที่ผู้เล่นกำลังเผชิญ

สิ่งสําคัญอีกอย่างหนึ่งที่ต้องจัดการคือการโต้ตอบของผู้ใช้ ในเดสก์ท็อป คุณจะป้อนข้อมูลด้วยเมาส์และแป้นพิมพ์ ส่วนในอุปกรณ์เคลื่อนที่ ผู้ใช้จะโต้ตอบด้วยการสัมผัส ปัด บีบนิ้ว การวางแนวอุปกรณ์ ฯลฯ

การใช้การโต้ตอบด้วยการสัมผัสในประสบการณ์การใช้งานเว็บบนอุปกรณ์เคลื่อนที่

การเพิ่มการรองรับการสัมผัสนั้นไม่ยาก มีบทความดีๆ เกี่ยวกับหัวข้อนี้ให้อ่าน แต่ก็มีบางสิ่งเล็กๆ น้อยๆ ที่ทำให้กระบวนการนี้ซับซ้อนขึ้น

คุณใช้ทั้งการสัมผัสและเมาส์ได้ทั้ง 2 อย่าง Chromebook Pixel และแล็ปท็อปอื่นๆ ที่เปิดใช้การสัมผัสจะรองรับทั้งเมาส์และการสัมผัส ข้อผิดพลาดที่พบบ่อยอย่างหนึ่งคือ การตรวจสอบว่าอุปกรณ์เปิดใช้การสัมผัสหรือไม่ จากนั้นเพิ่ม Listener เหตุการณ์การสัมผัสเท่านั้นและไม่มีสำหรับเมาส์

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

โปรดทราบว่านี่เป็นการสัมผัสแบบหลายจุด: event.touches คืออาร์เรย์ของการสัมผัสทั้งหมด ในบางกรณี การดู event.targetTouches หรือ event.changedTouches แทนและตอบสนองต่อการแตะที่คุณสนใจจะน่าสนใจกว่า เราใช้การหน่วงเวลาก่อนที่จะตรวจสอบว่ามีการสัมผัสที่เคลื่อนไหว (ปัด) หรือยังคงอยู่ (แตะ) เพื่อแยกการแตะกับการปัด หากต้องการจับการบีบ เราจะวัดระยะห่างระหว่างการสัมผัสครั้งแรก 2 ครั้งและการเปลี่ยนแปลงของระยะห่างนั้นเมื่อเวลาผ่านไป

ในโลก 3 มิติ คุณต้องตัดสินใจว่ากล้องจะตอบสนองต่อการกระทำด้วยเมาส์หรือด้วยการปัดอย่างไร วิธีทั่วไปในการเพิ่มการเคลื่อนไหวของกล้องคือการติดตามการเคลื่อนไหวของเมาส์ ซึ่งทำได้ด้วยการควบคุมโดยตรงโดยใช้ตำแหน่งเมาส์หรือการเคลื่อนไหวแบบเดลต้า (การเปลี่ยนแปลงตำแหน่ง) คุณอาจไม่ต้องการให้อุปกรณ์เคลื่อนที่ทำงานแบบเดียวกับเบราว์เซอร์ในเดสก์ท็อปเสมอไป เราได้ทดสอบอย่างละเอียดเพื่อตัดสินใจว่าเวอร์ชันใดเหมาะกับแต่ละเวอร์ชัน

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

สรุป

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

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

การทดสอบได้เริ่มต้นขึ้นแล้วและถือเป็นการเดินทางที่ยอดเยี่ยม หวังว่าคุณจะชอบ

หากอยากลองใช้ ออกเดินทางสู่มิดเดิลเอิร์ธด้วยตัวคุณเอง