ปล่อยของเล่นใหม่ไปเมื่อวานนี้อย่างเว็บ 360runaround.com ที่เอาไว้แปลงรูปภาพให้กลายเป็นภาพแบบ 360 องศาสำหรับโพสต์ลงเฟสบุ๊คด้วยวิธีง่ายๆ สำหรับรายละเอียดลองอ่านจากบล็อกที่แล้วได้ >> แปลงรูปเป็นภาพแบบ 360° ลงเฟสบุ๊คในคลิกเดียวด้วยเว็บ 360runaround.com
แต่เบื้องหลังความง่ายนั้นจริงๆแล้วการพัฒนานั้นซับซ้อนและวุ่นวายมาก บล็อกนี้เลยมาขอเขียนบันทึกเบื้องหลังว่าเทคนิคที่ใช้ทำยังไงเผื่อใครอยากจะลองเล่นอะไรแปลกๆกับเทรนด์เก๋ๆเทรนด์นี้ครับ
เข้าใจภาพแบบ Equirectangular
สำหรับเรื่องที่เป็น Fundamental ของภาพ 360 องศาที่ทุกคนต้องทำความเข้าใจก่อนจะไปทำอย่างอื่นต่อคือ "รูปแบบของภาพ 360 องศา" ซึ่งถ้าใครเคยใช้กล้อง 360 Camera อย่างพวก Ricoh Theta ก็จะรู้ว่าภาพที่ได้ออกมาจะหน้าตาแบบนี้
ก็จะเห็นความบิดเบี้ยว แต่จริงๆแล้วความบิดเบี้ยวนี้เป็นไปอย่างมีแบบแผน และเราเรียกภาพในรูปแบบนี้ว่า Equirectangular ครับ และนี่แหละคือ Fundamental ที่เราต้องเข้าใจมันก่อน
ก่อนอื่นเลย ถามว่าการสามารถมองภาพไปรอบๆ 360 องศาได้จริงๆแล้วมันคืออะไร? จริงๆมันก็คือการเอาตัวเองไปอยู่ในทรงกลมนั่นเอง ก็เลยสามารถหมุนไปดูรอบๆได้อย่างเนียนกิ๊ก อันนี้เป็นภาพทรงกลมที่ว่าเมื่อมองจากภายนอก
แต่ตอนที่เราเอามาดูจริงๆเราจะมองจากภายใน เหมือนเราไปยืนอยู่ตรงกลางทรงกลมนี้ ซึ่งภาพที่ได้มาจะครอปออกมาเพียงส่วนเดียวตามมุมมองของสายตา ก็เลยจะได้ออกมาเป็นแบบนี้
สรุปแล้วภาพ 360 องศาคือ Texture ของทรงกลมในโลกสามมิตินั่นเอง เพียงแต่เราต้องทำการแปลงภาพนั้นให้กลายเป็น 2 มิติเพื่อให้อยู่ในรูปแบบไฟล์ภาพ JPG ทั่วไป คำถามคือเราจะแปลงกันยังไง?
ก่อนอื่นต้องดูโครงสร้างทางเรขาคณิตของทรงกลมก่อน โครงสร้างของมันหน้าตาประมาณนี้ครับ มันคือการเอาสี่เหลี่ยมโค้งๆมาประกอบกัน แต่ละอันเล็กใหญ่ไม่เท่ากันตามตำแหน่งของชิ้นนั้นๆ
หรือถ้าเล่นกับทรงกลมที่คลาสสิคและทุกคนคุ้นตาหน่อยก็คือ ลูกโลก
โจทย์คือเราต้องแปลงภาพพื้นผิวของทรงกลมนี้ให้กลายเป็นภาพ 2 มิติ วิธีคือเราต้อง "กาง" กริดสี่เหลี่ยมแต่ละชิ้นบนลูกโลกนั้นออกมาให้กลายเป็นสี่เหลี่ยมจัตุรัส ผลที่ได้ออกมาจึงเป็นภาพอัตราส่วน 2:1 แบบนี้
ก็จะเห็นว่าตรงกลางๆจะค่อนข้างคล้ายเดิม แต่บริเวณด้านบนและด้านล่างจะถูกยืดออกเยอะมาก และนี่แหละครับภาพ Equirectangular หละ
ซึ่ง Pattern ภาพแบบนี้เป็นรูปแบบมาตรฐานที่ใช้ในทุกรูป Equirectangular ครับ อันนี้ลองเอาภาพด้านบนมาใส่ Grid ให้ดู
ก็จะเห็นว่ายืดตรง Grid บนๆกับล่างๆจะยืดออกและบิดเบี้ยว ส่วนตรงกลางค่อนข้างตรงอัตราส่วน แบบเดียวกับลูกโลกทุกประการ ถ้าเอาภาพด้านบนนี้มาแปะลงทรงกลมก็จะได้เป็นทรงกลมแบบนี้
และนี่แหละคือทรงกลมที่มองได้รอบและเป็นหลักการ Projection Mapping ที่เอาไปใช้แสดงผลบนเว็บต่างๆเช่น Facebook นั่นเอง ซึ่งเว็บเหล่านั้นจะใช้การมองจากข้างใน ได้ออกมาเป็นแบบนี้
สวัสดีภาพ 360 องศา ! แค่นี้แหละ ไม่ได้ยากอะไรเลยใช่ม้า แต่เนื่องจากมันเป็นอะไรที่ไม่ Linear ทำให้เราไม่สามารถสร้างภาพ Equirectangular ด้วย Photoshop ได้ แต่ต้องอาศัยการแปลงตามหลักคณิตศาสตร์
คณิตศาสตร์เบื้องหลังภาพ Equirectangular
ภาพ Equirectangular มันคือการแปลง Texture Map ของทรงกลมให้กลายเป็นภาพ 2 มิติ แต่ในชีวิตจริงวัตถุต่างๆไม่ได้วางเป็นพื้นผิวของทรงกลมเหมือนลูกโลก แล้วเราจะสร้างภาพ Equirectangular ขึ้นมาได้ยังไง?
คำตอบคือให้ลองเปลี่ยนมุมมองว่าเราไม่สนใจเลยว่าวัตถุจะวางยังไง ให้สนใจแค่ว่า "เรามองเห็นยังไง" และเพื่อให้มองได้รอบ มุมมองที่เราสนใจจึงมี 6 ด้านด้วยกันได้แก่ บน-ล่าง-ซ้าย-ขวา-หน้า-หลัง
โดยภาพแบบนี้เราเรียกมันว่า Cubemap
และให้คิดซะว่าเราไปยืนอยู่ตรงกลางทรงกลมและภาพที่เห็นรอบๆนั้นเป็นภาพของพื้นผิวทรงกลมรอบตัวเรา อันนี้เป็นภาพโครงสร้าง
และนี่เป็นภาพมุมมองทั้ง 6 ที่ Map ลงพื้นผิวทรงกลมแล้ว
ดังนั้นถ้ามองจากด้านในก็จะเห็นว่าภาพ 6 มุมมองที่เห็นด้านบนจริงๆแล้วเป็นโครงสร้างตามนี้
และเมื่อมันเป็นทรงกลมแล้ว เราก็จะสามารถแปลงเป็นภาพแบบ Equirectangular ได้ทันที และด้วยวิธีคิดแบบนี้ทำให้เราสามารถแปลงภาพใดๆที่มีมุมมองครบ 6 ด้านให้กลายเป็นภาพ 360 องศาได้เสมอ
แต่โจทย์ที่ได้จะเปลี่ยนไปเล็กน้อยเนื่องจากภาพแต่ละด้านมันไม่ใช่ Linear แต่เป็นภาพทรงโค้ง เราเลยต้องแก้ Distortion โดยใช้สมการการแปลงความบิดเบี้ยวตามรูปแบบด้านล่างนี้
ซึ่งถ้าเอา Mapping ด้านบนไป Project ลงบนทรงกลมก็จะได้ด้านทั้ง 6 ในรูปแบบทรงกลมอย่างพอดิบพอดี
สุดท้ายภาพด้านบนที่เป็น Cubemap จึงถูกแปลงเป็น Equirectangular ได้ดังนี้
เป็นอันเสร็จพิธีครับ นี่คือวิธีที่กล้องต่างๆสร้างภาพแบบนี้ขึ้นมา หากอยากรู้พวกสมการต่างๆ (ซึ่งส่วนใหญ่เป็น sin/cos) สามารถไปอ่าน Paper เรื่อง Spherical Projections (Stereographic and Cylindrical) ในส่วนของ Converting to and from 6 cubic environment maps and a spherical map ได้ครับ
ภาพ Equirectangular ที่โพสต์ลงเฟสบุ๊คได้
ความรู้อีกอย่างนึงที่ต้องรู้คือ แล้ว Facebook รู้ได้ไงว่าภาพของเราเ็น Equirectangular?
จริงๆไม่มีอะไรมาก Facebook ดูจาก Exif ครับ โดยมีเงื่อนไขสองอย่าง
1) ภาพต้องเป็นอัตราส่วน 2:1
2) ภาพต้องเป็น JPEG (เพื่อใส่ Exif ได้)
2) ข้อมูล Exif ต้องระบุว่าถ่ายมาจากกล้อง 360 องศาที่ Facebook สนับสนุน ซึ่งได้แก่ Ricoh Theta S, Giroptic 360 Cam, Samsung Gear 360, LG 360 Cam, C Realtech ALLie, 360Fly หรือ Panono
หากอยากลองเล่นอะไรแปลกๆ ให้ลองสร้างภาพ JPEG อัตราส่วน 2:1 ขึ้นมาสักภาพนึงแล้วแก้ Exif เข้าไปให้เป็น Maker = RICOH และ Model = RICOH THETA S ดูครับ
ภาพที่เห็นจะสามารถอัปขึ้น Facebook และกลายเป็นภาพ 360 องศาทันที แต่ถ้าภาพนั้นเป็นภาพถ่าย Linear ธรรมดา มันก็จะเหมือนกับเอาภาพนั้นไปใส่เป็น Texture ทรงกลม ผลคือด้านบนและด้านล่างจะยู่ๆ ส่วนตรงกลางจะป่องๆหน่อยดูไม่เป็นอัตราส่วนนั่นเอง ใช้งานจริงไม่ค่อยดีเท่าไหร่แต่หากอยากลองเล่นก็ทำได้ครับ
เทคนิคฝั่ง Server
ก็รู้พื้นฐานของภาพ 360 องศาแล้ว ก็แค่หาวิธีสร้างภาพ Equirectangular ขึ้นมาแล้วใส่ Exif หลอกว่าถ่ายจากกล้อง 360 ก็เป็นอันเรียบร้อย แต่นั่นแหละก็จะเห็นว่ามันมีความยากและไม่ได้ตรงไปตรงมาเท่าไหร่ ต้องทำภาพขึ้นมาจากมุมมองต่างๆและแก้สมการออกมาเป็นภาพๆเดียวให้ได้
สำหรับเว็บ 360runaround.com ใช้เทคนิคเหล่านี้ในการสร้างภาพขึ้นมาจากฝั่ง Server ครับ
1) ระบบแปลงภาพบน Server เขียนขึ้นมาด้วย Node.js 100% ทุกอย่างรันบน Node.js ไม่มี Dependency อื่นใดที่ต้องลงเพิ่ม ซึ่งอันนี้เป็นการเลือกเทคโนโลยีตามความเหมาะสมเพราะ Node.js สามารถทำงานที่ต้องการอย่างงานด้านการเรนเดอร์ภาพ 3 มิติได้และทำงานได้เร็วมาก นอกจากนั้นยังมีไลบรารี่ที่เกี่ยวข้องอย่างครบถ้วน สรุปคือดีทั้งเรื่องความเป็นไปได้และประสิทธิภาพนั่นเอง
2) การสร้างภาพ Equirectangular มีขั้นตอนดังนี้
- เรนเดอร์ภาพ 3 มิติจริงๆด้วย OpenGL โดยใช้ headless-gl ซึ่งไว้สร้าง GL Context โดยที่ไม่ต้องสร้าง Window ขึ้นมา ทำให้เราสามารถทำงานกับ OpenGL บน Node.js ได้โดย Syntax ที่ใช้จะเป็นของ WebGL เพราะ headless-gl เป็นการพอร์ต WebGL ไปใช้บน Node.js นั่นเอง
- เรนเดอร์ทั้งหมด 6 มุมมอง ได้แก่ บน-ล่าง-ซ้าย-ขวา-หน้า-หลัง ด้วยมุมมอง Field of View Angle 90 องศา ซึ่งสุดท้ายจะได้ภาพ Cubemap ออกมานั่นเอง
- แปลงภาพ Cubemap ให้เป็น Equirectangular ด้วย GLSL
- บันทึกภาพที่ได้เป็นไฟล์ JPEG และยัด Exif ไปว่าถ่ายจาก RICOH THETA S
ก็เป็นอันจบโปรเซส ได้ผลลัพธ์ออกมาพร้อมโพสต์ลงเฟสบุ๊คแล้ว สรุปรวมแล้วการสร้างภาพขึ้นมา 1 ภาพ จะต้องผ่านการเรนเดอร์ทั้งหมด 7 ครั้ง 1280x1280 พิกเซล 6 ภาพ และ 4096x2048 อีก 1 ภาพ จึงไม่ต้องแปลกใจที่การแปลงภาพจะกิน CPU 100% ยาวไปเลย 10-20 วินาที ฝั่ง Server ทำงานหนักมากจริงๆ แต่ก็เพื่อให้ได้ภาพที่สมบูรณ์ออกมาและสามารถปรับเปลี่ยนได้อย่างอิสระ ถ้าอยากได้รูปทรงใหม่ก็แค่เพิ่มโมเดล 3 มิติขึ้นมาแล้วใช้อัลกอริทึมเดิมในการแปลงภาพ
3) เพื่อความง่ายในการ Deploy ก็เลยยัดใส่ยัดระบบแปลงภาพใส่ไว้ใน Docker ตอนนี้สามารถ Deploy ระบบได้ด้วยบรรทัดเดียว
4) แต่เนื่องจาก headless-gl จำเป็นต้องใช้หน้าจอในการสร้าง Context แต่ Environment ใน Docker Container ไม่มีหน้าจออยู่ ก็เลยจำลองหน้าจอด้วย Xvfb (Virtual Framebuffer for X) ขึ้นมาเพื่อให้ใช้งานได้ใน Container โดยที่ไม่จำเป็นต้องมีหน้าจอ
5) ระบบวางอยู่บน Server 4 Cores และใช้ Node.js Cluster Fork โปรเซสมาทั้งหมด 4 โปรเซสเพื่อ Utilize CPU ทั้ง 4 Cores
6) เนื่องจาก Node.js ทำงานหนักพอสมควร Response Time เลยต่ำ ก็เลยทำเว็บไว้เป็น Static HTML และติดต่อกับระบบแปลงภาพผ่าน AJAX ส่วนไฟล์เว็บและ Static Files อื่นๆอย่างไฟล์ภาพก็ Deliver ด้วย nginx ทำให้เข้าเว็บได้เร็วถึงแม้ Server จะประมวลผลหนักตลอดเวลา
และหลังจากปรับจูน Server ตลอดเวลาเมื่อวานนี้ ตอนนี้เว็บเสถียรและไม่ล่มแล้ว สามารถเข้าใช้งานได้สมบูรณ์ครับ =)
7) ใช้ three.js เป็น Engine การแสดงพรีวิวภาพ 360 บนหน้าเว็บ โดยปรับจูนค่าต่างๆให้ตรงกับเฟสบุ๊ค เช่น อัตราส่วน 1:1 และมี Field of View Angle ที่ 65 องศา
ทั้งหมดก็ใช้เวลา 2 วันในการพัฒนา และอีก 1 วันในการตบตีกับ Designer จนทำเป็นหน้าเว็บออกมาเสร็จ ... แหะๆ
คร่าวๆก็ประมาณนี้ครับ ก็หวังว่าข้อมูลในบล็อกนี้จะมีประโยชน์ เผื่ออยากจะพลิกแพลงทำอะไรแผลงๆครับ ^_^