ประสบการณ์ Hobbit

เติมชีวิตชีวาให้มิดเดิลเอิร์ธด้วย WebGL บนมือถือ

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

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

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

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

ความท้าทายของ WebGL บนอุปกรณ์เคลื่อนที่

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

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

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

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

ใช้โมเดล Low-poly

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

หนึ่งในพวกโทรลล์ในป่าโทรลล์ชอว์
หนึ่งในฝูงโทรลล์ในป่าโทรว์ชอว์

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

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

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

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

พื้นผิวของหนึ่งในพวกโทรลล์ในป่าโทรลล์ชอว์
พื้นผิวของหนึ่งในยักษ์โทรลล์ในป่าโทรลล์ชอว์
(ขนาดดั้งเดิม 512x512 พิกเซล)

ลดความซับซ้อนของวัสดุและการจัดแสง

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

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

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

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

เมื่อสร้างเกมสำหรับอุปกรณ์เคลื่อนที่ GPU อาจไม่ใช่อุปสรรคที่ใหญ่ที่สุดเสมอไป CPU มักจะใช้เวลามากมาย โดยเฉพาะฟิสิกส์และภาพเคลื่อนไหวโครงร่าง เคล็ดลับอย่างหนึ่งที่ช่วยได้ในบางครั้งคือการใช้เฉพาะการคำนวณราคาแพงเหล่านี้กับเฟรมอื่นๆ เท่านั้น ทั้งนี้ขึ้นอยู่กับการจำลอง คุณสามารถใช้เทคนิคการเพิ่มประสิทธิภาพ 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 อัปเดตพื้นที่งาน

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

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

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

ฉากเดียวกันที่ไม่มีการปรับอัตราส่วนแคนวาสบน Nexus 10 (16 FPS) และปรับขนาดเป็น 50% (33 FPS)
ฉากเดียวกันที่ไม่มีการปรับขนาดผืนผ้าใบบน Nexus 10 (16 FPS) และปรับขนาดเป็น 50% (33 FPS)

ใช้วัตถุเป็นองค์ประกอบที่ใช้สร้างสรรค์

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

บล็อกสร้างวัตถุ 3 มิติในเขาวงกตของโดลกูลดูร์
บล็อกประกอบวัตถุ 3 มิติในเขาวงกตของโดลกูลดูร์

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

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

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

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

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

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

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

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

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

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

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

สรุป

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

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

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

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