Dance Tonite ใน WebVR

ผมรู้สึกตื่นเต้นเมื่อทีม Google Data Arts ติดต่อ Moniker และตัวผมเองเกี่ยวกับการทำงานร่วมกันเพื่อสำรวจความเป็นไปได้ที่ WebVR จะเกิดขึ้น ผมได้ดูผลงานจากทีมงานตลอดหลายปีที่ผ่านมา และโครงการของพวกเขา โดนใจผมอยู่เสมอ การคอลแลบของเราส่งผลให้เกิด Dance Tonite ซึ่งเป็นประสบการณ์การเต้นแบบ VR ที่เปลี่ยนแปลงตลอดเวลากับ LCD Soundsystem และแฟนๆ นั่นเป็นวิธีที่เราทำ

แนวคิด

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

เรายึดมั่นในสิ่งนี้ อะไรก็ตามที่เราคิดขึ้นมาได้น่าจะใช้งานได้กับ VR ทุกประเภท ตั้งแต่ชุดหูฟัง VR ที่ทำงานร่วมกับโทรศัพท์มือถือ เช่น Daydream View ของ Google, Cardboard และ Gear VR ของ Samsung ไปจนถึงระบบการปรับขนาดห้องอย่าง HTC VIVE และ Oculus Rift ซึ่งสะท้อนให้เห็นการเคลื่อนไหวร่างกายของคุณในสภาพแวดล้อมเสมือนจริง บางทีที่สำคัญที่สุดคือเรารู้สึกว่ามันเป็นส่วนหนึ่งของเว็บ ในการสร้างสิ่งที่เหมาะกับทุกคนที่ไม่มีอุปกรณ์ VR

1. การจับภาพภาพเคลื่อนไหวแบบ DIY

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

มีคนบันทึกตัวเองใน Dance Tonite หน้าจอด้านหลังแสดงสิ่งที่เห็นในชุดหูฟัง

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

เมื่อใช้ WebVR นักพัฒนาซอฟต์แวร์จะเข้าถึงตำแหน่งศีรษะและการวางแนวของผู้ใช้ผ่านออบเจ็กต์ VRPose ได้ ค่านี้จะอัปเดตทุกเฟรมโดยฮาร์ดแวร์ VR เพื่อให้โค้ดสามารถแสดงผลเฟรมใหม่จากมุมมองที่ถูกต้องได้ นอกจากนี้ ผ่านทาง GamePad API ที่มี WebVR ทำให้เราเข้าถึงตำแหน่ง/การวางแนวของตัวควบคุมผู้ใช้ผ่านออบเจ็กต์ GamepadPose ได้ด้วย เราเพียงแค่จัดเก็บค่าตำแหน่งและการวางแนวเหล่านี้ทั้งหมดไว้ทุกๆ เฟรม เพื่อสร้าง "การบันทึก" การเคลื่อนที่ของผู้ใช้

2. มินิมอลลิสมและเครื่องแต่งกาย

อุปกรณ์ VR ขนาดห้องสำหรับวันนี้ช่วยให้เราติดตามร่างกายของผู้ใช้ได้ 3 จุด คือ ศีรษะและมือ 2 ข้าง ใน Dance Tonite เราอยากจะเน้น การเคลื่อนที่ของมนุษยชาติในการเคลื่อนที่ในอวกาศ เพื่อให้บรรลุเป้าหมายนี้ เรายกระดับความสวยงามให้น้อยที่สุดเท่าที่จะทำได้ เพื่อมุ่งเน้นที่การเคลื่อนไหว เราชอบไอเดียในการใช้สมองของผู้คน

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

จากตัวอย่างภาพ เราได้แรงบันดาลใจจากห้องหลากสีและเครื่องแต่งกายทรงเรขาคณิตในการบันทึกเสียงประกอบภาพยนตร์ Triadic Ballet ของ Margarete Hastings ในปี 1970 ของ Margarete Hastings

ขณะที่เหตุผลของ Schlemmer เลือกเครื่องแต่งกายทรงเรขาคณิตแนวนามธรรมคือการจำกัด การเคลื่อนไหวของนักเต้นเฉพาะหุ่นเชิดและหุ่นเชิด แต่เรามีเป้าหมายตรงกันข้ามกับ Dance Tonite

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

3. แป้นปั่นสำหรับการเคลื่อนไหว

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

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

4. ห้องที่เชื่อมถึงกัน

ห้องที่เชื่อมถึงกัน

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

การเพิ่มประสิทธิภาพ: อย่าวางเฟรม

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

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

1. เรขาคณิตของบัฟเฟอร์อินสแตนซ์

เนื่องจากโปรเจ็กต์ทั้งหมดของเราใช้วัตถุ 3 มิติเพียงเล็กน้อย เราจึงเพิ่มประสิทธิภาพได้อย่างมากโดยใช้ Instanced Buffer Geometry โดยพื้นฐานแล้ว ซอฟต์แวร์นี้จะช่วยให้คุณอัปโหลดออบเจ็กต์ไปยัง GPU เพียงครั้งเดียวและวาด "อินสแตนซ์" ของออบเจ็กต์ดังกล่าวได้มากเท่าที่ต้องการในการเรียกใช้การวาดครั้งเดียว ใน Dance Tonite เรามีวัตถุแค่ 3 อย่าง (กรวย กระบอก และห้องที่มีรู) แต่วัตถุเหล่านั้นอาจมีอีกหลายร้อยสำเนา Instance Buffer Geometry เป็นส่วนหนึ่งของ ThreeJS แต่เราใช้ Fork แบบทดลองและที่อยู่ระหว่างดำเนินการของ Dusan Bosnjak ซึ่งนำ THREE.InstanceMesh มาใช้ ซึ่งทำให้การทำงานกับ Instanced Buffer Geometry ง่ายขึ้นมาก

2. การหลีกเลี่ยงพนักงานเก็บขยะ

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

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

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

ตัวอย่างเช่น นี่คือโค้ดสำหรับแปลงเมทริกซ์ตำแหน่งของศีรษะและมือของผู้ใช้เป็นอาร์เรย์ของค่าตำแหน่ง/การหมุนที่เราจัดเก็บแต่ละเฟรม การนำ SERIALIZE_POSITION, SERIALIZE_ROTATION และ SERIALIZE_SCALE มาใช้ซ้ำช่วยให้เราหลีกเลี่ยงการจัดสรรหน่วยความจำและการรวบรวมขยะที่จะเกิดขึ้นหากเราสร้างออบเจ็กต์ใหม่ทุกครั้งที่มีการเรียกใช้ฟังก์ชัน

const SERIALIZE_POSITION = new THREE.Vector3();
const SERIALIZE_ROTATION = new THREE.Quaternion();
const SERIALIZE_SCALE = new THREE.Vector3();
export const serializeMatrix = (matrix) => {
    matrix.decompose(SERIALIZE_POSITION, SERIALIZE_ROTATION, SERIALIZE_SCALE);
    return SERIALIZE_POSITION.toArray()
    .concat(SERIALIZE_ROTATION.toArray())
    .map(compressNumber);
};

3. การทำให้การเคลื่อนไหวเป็นอนุกรมและการเล่นแบบโปรเกรสซีฟ

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

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

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

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

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

ส่วนหนึ่งของการบันทึกของเรามีลักษณะดังนี้

{"fps":15,"count":1,"loopIndex":"1","hideHead":false}
[-464,17111,-6568,-235,-315,-44,9992,-3509,7823,-7074, ... ]
[-583,17146,-6574,-215,-361,-38,9991,-3743,7821,-7092, ... ]
[-693,17158,-6580,-117,-341,64,9993,-3977,7874,-7171, ... ]
[-772,17134,-6591,-93,-273,205,9994,-4125,7889,-7319, ... ]
[-814,17135,-6620,-123,-248,408,9988,-4196,7882,-7376, ... ]
[-840,17125,-6644,-173,-227,530,9982,-4174,7815,-7356, ... ]
[-868,17120,-6670,-148,-183,564,9981,-4069,7732,-7366, ... ]
...

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

4. การเคลื่อนที่แบบสอดประสาน

เนื่องจากเราต้องการแสดงการทำงาน 30 ถึง 60 รายการในเวลาเดียวกัน เราจึงต้องลดอัตราการใช้ข้อมูลให้สูงกว่าที่เราเคยเห็น ทีมศิลปะข้อมูลได้จัดการกับปัญหาเดียวกันในโปรเจ็กต์เซสชันศิลปะเสมือน ซึ่งพวกเขาเล่นการบันทึกเสียงของศิลปินที่วาดภาพใน VR โดยใช้ Tilt Brush พวกเขาแก้ปัญหาได้ด้วยการสร้างเนื้อหาผู้ใช้เวอร์ชันกลางที่มีอัตราเฟรมต่ำและแลกเปลี่ยนระหว่างเฟรมขณะเล่น เราประหลาดใจที่พบว่าเราแทบจะมองไม่เห็นความแตกต่างระหว่างการบันทึกที่แทรกขณะทำงานที่ 15 FPS กับการบันทึกเดิมที่ 90 FPS

หากต้องการดูด้วยตาคุณเอง คุณสามารถบังคับให้ Dance Tonite เล่นข้อมูลในอัตราต่างๆ โดยใช้สตริงคำค้นหา ?dataRate= คุณสามารถใช้รายงานนี้เพื่อเปรียบเทียบการเคลื่อนไหวที่บันทึกไว้ที่ 90 เฟรมต่อวินาที, 45 เฟรมต่อวินาที หรือ 15 เฟรมต่อวินาที

สำหรับตำแหน่ง เราจะแทรกการประมาณเชิงเส้นระหว่างคีย์เฟรมก่อนหน้าและคีย์เฟรมถัดไป โดยอิงตามระยะห่างระหว่างคีย์เฟรม (อัตราส่วน) ของเรา

const { x: x1, y: y1, z: z1 } = getPosition(previous, performanceIndex, limbIndex);
const { x: x2, y: y2, z: z2 } = getPosition(next, performanceIndex, limbIndex);
interpolatedPosition = new THREE.Vector3();
interpolatedPosition.set(
    x1 + (x2 - x1) * ratio,
    y1 + (y2 - y1) * ratio,
    z1 + (z2 - z1) * ratio
    );

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

const quaternion = getQuaternion(previous, performanceIndex, limbIndex);
quaternion.slerp(
    getQuaternion(next, performanceIndex, limbIndex),
    ratio
    );

5. กำลังซิงค์การเคลื่อนไหวกับเพลง

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

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

6. มีหมอกลงบ้างและหมอกลง

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

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

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

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

// this is called every frame
// the FPS calculation is based on stats.js by @mrdoob
tick: (interval = 3000) => {
    frames++;
    const time = (performance || Date).now();
    if (prevTime == null) prevTime = time;
    if (time > prevTime + interval) {
    fps = Math.round((frames * 1000) / (time - prevTime));
    frames = 0;
    prevTime = time;
    const lastCullDistance = settings.cullDistance;

    // if the fps is lower than 52 reduce the cull distance
    if (fps <= 52) {
        settings.cullDistance = Math.max(
        settings.minCullDistance,
        settings.cullDistance - settings.roomDepth
        );
    }
    // if the FPS is higher than 56, increase the cull distance
    else if (fps > 56) {
        settings.cullDistance = Math.min(
        settings.maxCullDistance,
        settings.cullDistance + settings.roomDepth
        );
    }
    }

    // gradually increase the cull distance to the new setting
    cullDistance = cullDistance * 0.95 + settings.cullDistance * 0.05;

    // mask the edge of the cull distance with fog
    viewer.fog.near = cullDistance - settings.roomDepth;
    viewer.fog.far = cullDistance;
}

ตอบสนองความต้องการของทุกคน: การสร้าง VR สำหรับเว็บ

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

1. ลูกแก้วสีเหลือง

ดังนั้น ผู้ใช้ VR ขนาดห้องของเราก็จะทำการแสดง แต่ผู้ใช้อุปกรณ์ VR บนอุปกรณ์เคลื่อนที่ (เช่น Cardboard, Daydream View หรือ Samsung Gear) จะสัมผัสประสบการณ์ได้อย่างไร ด้วยเหตุนี้ เราได้เปิดตัวองค์ประกอบใหม่ในสภาพแวดล้อมของเรา นั่นคือลูกกลมสีเหลือง

ลูกแก้วสีเหลือง
ลูกแก้วสีเหลือง

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

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

2. มุมมองอื่น

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

3. เงา: ปลอมจนกว่าจะลงมือทำ

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

ให้แสงเงา

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

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

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

function createShadow() {
    const texture = new THREE.TextureLoader().load(shadowTextureUrl);
    const material = new THREE.MeshLambertMaterial({
        map: texture,
        transparent: true,
        side: THREE.BackSide,
        depthWrite: false,
        blending: THREE.SubtractiveBlending,
    });
    const geometry = new THREE.PlaneBufferGeometry(0.5, 0.5, 1, 1);
    const plane = new THREE.Mesh(geometry, material);
    return plane;
    }

4. การไปเยือน

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

5. กำลังแชร์ไฟล์บันทึกเสียง

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

กำลังแชร์ไฟล์บันทึกเสียง

เราหันมาใช้ GIF.js ซึ่งเป็นไลบรารี JavaScript ที่ทำให้คุณสามารถเข้ารหัส GIF แบบเคลื่อนไหวจากภายในเบราว์เซอร์ได้ ซึ่งจะลดภาระงานการเข้ารหัสเฟรมให้กับ Web Work ซึ่งสามารถทำงานในเบื้องหลังเป็นกระบวนการที่แยกต่างหาก ทำให้สามารถใช้ประโยชน์จากตัวประมวลผลหลายตัวที่ทำงานเคียงข้างกัน

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

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

6. ภาคพื้นดิน: Google Cloud และ Firebase

แบ็กเอนด์ของเว็บไซต์ "เนื้อหาที่ผู้ใช้สร้างขึ้น" มักเป็นเรื่องซับซ้อนและบอบบาง แต่เราได้สร้างระบบที่ใช้งานง่ายและมีประสิทธิภาพด้วย Google Cloud และ Firebase เมื่อนักแสดงอัปโหลดการเต้นใหม่ลงในระบบ ระบบจะตรวจสอบสิทธิ์แบบไม่ระบุชื่อโดยการตรวจสอบสิทธิ์ของ Firebase ครีเอเตอร์ได้รับอนุญาตให้อัปโหลดไฟล์บันทึกเสียงไปยังพื้นที่ชั่วคราวโดยใช้ Cloud Storage for Firebase เมื่อการอัปโหลดเสร็จสมบูรณ์ เครื่องไคลเอ็นต์จะเรียก HTTP ของ Cloud Functions for Firebase โดยใช้โทเค็น Firebase ซึ่งจะทริกเกอร์กระบวนการของเซิร์ฟเวอร์ที่ตรวจสอบการส่ง สร้างระเบียนฐานข้อมูล และย้ายการบันทึกไปยังไดเรกทอรีสาธารณะใน Google Cloud Storage

พื้นแข็ง

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

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

7. Service Worker

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

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

const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
config.plugins.push(
    new SWPrecacheWebpackPlugin({
    dontCacheBustUrlsMatching: /\.\w{8}\./,
    filename: 'service-worker.js',
    minify: true,
    navigateFallback: 'index.html',
    staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
    runtimeCaching: [{
        urlPattern: /playlist\.json$/,
        handler: 'networkFirst',
    }, {
        urlPattern: /\/recordings\//,
        handler: 'cacheFirst',
        options: {
        cache: {
            maxEntries: 120,
            name: 'recordings',
        },
        },
    }],
    })
);

ปัจจุบันปลั๊กอินไม่สามารถจัดการกับเนื้อหาสื่อที่โหลดแบบต่อเนื่อง เช่น ไฟล์เพลงของเรา เราจึงแก้ปัญหานี้โดยตั้งค่าส่วนหัว Cloud Storage Cache-Control ในไฟล์เหล่านี้เป็น public, max-age=31536000 เพื่อให้เบราว์เซอร์แคชไฟล์ได้นานถึง 1 ปี

บทสรุป

เราตื่นเต้นที่จะได้เห็นว่านักแสดงจะนำไปต่อยอดประสบการณ์นี้อย่างไร และใช้เป็นเครื่องมือในการแสดงออกอย่างสร้างสรรค์โดยใช้ภาพเคลื่อนไหว เราได้เปิดตัวโค้ดแบบโอเพนซอร์สทั้งหมด ซึ่งดูได้ที่ https://github.com/puckey/dance-tonite ในยุคแรกๆ ของ VR และโดยเฉพาะอย่างยิ่ง WebVR เราหวังเป็นอย่างยิ่งว่าจะได้รับชมครีเอทีฟโฆษณาใหม่ๆ และเส้นทางอันคาดไม่ถึงที่จะใช้สื่อใหม่นี้ เริ่มเต้น