5 คะแนน โดย GN⁺ 2024-07-29 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • เมื่อโปรเจกต์ฉากและเลเวล 3D ที่รันในเบราว์เซอร์ใหญ่ขึ้น ก็มีการสะสมเครื่องมือที่นำกลับมาใช้ซ้ำได้สำหรับใช้ เทคนิคเชิงกระบวนวิธีและเชิงกำเนิด กับองค์ประกอบเฉพาะของเลเวล มากกว่าการสร้างโลกทั้งใบ
  • งานเท็กซ์เจอร์มีแกนหลักอยู่ที่ Triplanar Mapping และ Hex Tiling ซึ่งใช้สำหรับใส่เท็กซ์เจอร์โดยไม่ต้องมี UV หรือซ่อนแพตเทิร์นที่เกิดจากการทำซ้ำ
  • ทั้งสองเทคนิคใช้ pow() กับค่าน้ำหนักการอินเทอร์โพเลตเพื่อเพิ่มสัดส่วนของแกนหลักหรือผลลัพธ์จากการ lookup ที่เด่นกว่า และลดภาระด้านประสิทธิภาพด้วยการ ข้ามการ lookup เท็กซ์เจอร์ บางส่วน
  • fragment shader ที่มีต้นทุนสูงสามารถบรรเทาได้ด้วย Depth Pre-Pass และในฉากที่มี overdraw มาก ประสิทธิภาพอาจดีขึ้นได้มากกว่า 30%
  • ในฝั่ง mesh และ geometry มีแนวโน้มที่จะขยายการแสดงผลของการตกแต่ง ฉากหลัง และความเสียหาย ผ่าน LoD terrain, runtime mesh processing pipeline และในอนาคตคือ Constructive Solid Geometry

เครื่องมือเชิงกระบวนวิธีที่สะสมมาจากฉาก 3D บนเบราว์เซอร์

  • ตลอดหลายปีของการสร้าง 3D scenes and levels ที่รันในเบราว์เซอร์ เดโมอิสระที่เน้น custom shader ได้เติบโตเป็นรูปแบบคล้ายเกมที่เชื่อมโยงกัน
  • แนวทางร่วมคือการใช้ เทคนิคเชิงกระบวนวิธีและเชิงกำเนิด กับบางส่วนของเลเวล แทนที่จะสร้างโลกทั้งใบแบบ procedural generation
  • เครื่องมือและเอฟเฟกต์แบบ procedural และกึ่ง procedural ที่ใช้ซ้ำในหลายเลเวลจึงค่อย ๆ สะสมขึ้นเอง

Shader และเท็กซ์เจอร์

  • เท็กซ์เจอร์ส่วนใหญ่เป็น seamless texture ที่ tile ได้ต่อเนื่องทั้งสองแกน และเมื่อปูบนพื้นที่กว้าง แพตเทิร์นที่ซ้ำกันอาจเห็นได้ชัด
  • เพิ่มหลายฟีเจอร์ให้ custom shader ที่ต่อยอดจาก MeshPhysicalMaterial ของ Three.JS เพื่อปรับปรุงการรองรับ seamless texturing
  • Triplanar Mapping

    • Triplanar Mapping เป็นเครื่องมือหลักด้าน texturing ที่ใช้ในแทบทุกเลเวล
    • สามารถใช้ seamless texture ใส่เท็กซ์เจอร์ให้ mesh ได้โดยไม่ต้องมี UV map ที่กำหนดไว้ล่วงหน้า จึงมีประโยชน์ในกรณีอย่าง terrain ที่สร้างแบบ procedural ซึ่งโมเดลเลอร์ไม่มีโอกาสกำหนด UV mapping
    • ทำงานได้ดีกับทั้ง mesh ที่สร้างขึ้นและ mesh ที่โมเดลด้วยมือ
    • การ implement เบาและเรียบง่าย โดยมีตัวอย่างอ้างอิงอยู่ที่ triplanarMapping.ts
  • การปรับปรุง Triplanar Mapping

    • Triplanar Mapping แบบทั่วไปจะ linearly blend การ lookup เท็กซ์เจอร์จากสามแกนโดยอิงจาก normal ของ fragment
    • ในบริเวณที่ normal ไม่ได้ใกล้กับแกนใดแกนหนึ่งอย่างชัดเจน เท็กซ์เจอร์อาจดูเป็นชั้น ๆ
    • หากใช้ pow() ที่มีเลขชี้กำลังสูงกับค่าน้ำหนัก แล้ว normalize อีกครั้ง จะทำให้สัดส่วนของ แกนหลัก มากขึ้นและพื้นที่เปลี่ยนผ่านเล็กลง
    • การแปลงนี้ทำให้บน mesh ส่วนใหญ่ ค่าน้ำหนักของแกนหนึ่งเข้าใกล้ 1 และค่าน้ำหนักของอีกสองแกนเข้าใกล้ 0
    • ด้วยการ ข้ามการ lookup เท็กซ์เจอร์ ของค่าน้ำหนักที่เล็กกว่า threshold สามารถลดภาระด้านประสิทธิภาพของ Triplanar Mapping ลงมาเหลือมากกว่าการทำ texturing แบบใช้ UV ปกติเพียงเล็กน้อย
    • การจัดการ normal map ต้องพิจารณาแยกใน shader code และใช้ แนวทางของ GPU Gems
    • รายละเอียดการ implement ดูได้ที่ Normal Mapping for a Triplanar Shader
  • Hex Tiling

    • Hex Tiling เป็นอัลกอริทึมสำหรับซ่อนการ tile และการซ้ำที่เห็นได้ชัดของ seamless texture
    • เพียงเพิ่ม option การตั้งค่าเดียวให้ material ก็อาจทำให้ฉากเปลี่ยนจากสภาพที่ดูเหมือน mockup คุณภาพต่ำไปใกล้เคียง semi-realistic ได้
    • implementation แรกอิงจาก Shadertoy ของ Fabrice Neyret และแปลงให้เข้ากับ material system ของ Three.JS ก่อนรวมเข้ากับ shader material หลักของโปรเจกต์
    • ต่อมาหลังได้รับอนุญาต จึง port เป็นไลบรารีอิสระ three-hex-tiling ที่สามารถเพิ่ม Hex Tiling ให้ built-in material ของโปรเจกต์ Three.JS ได้
    • ต่างจาก Triplanar Mapping ตรงที่ต้องมี UV mapping ที่กำหนดไว้ล่วงหน้า
    • หากใช้สองเทคนิคร่วมกัน จำนวน texture fetch สูงสุดต่อ fragment ต่อ map จะเพิ่มได้ถึง 27 ครั้ง จึงไม่เหมาะในทางปฏิบัติ
    • Hex Tiling ก็ linearly interpolate ผลจากการ lookup สามครั้งต่อ fragment เช่นกัน ดังนั้นเทคนิคค่าน้ำหนัก pow() ที่ใช้กับ Triplanar Mapping จึงช่วยปรับปรุงทั้งประสิทธิภาพและคุณภาพผลลัพธ์ได้
  • Depth Pre-Pass

    • เทคนิค texturing ขั้นสูงอาจทำให้เกิด fragment shader ที่มีต้นทุนสูงในฉากขนาดใหญ่
    • Depth Pre-Pass คือวิธีเรนเดอร์ทั้งฉากก่อนด้วย material ที่เรียบง่ายและราคาถูกมาก เพื่อบันทึก depth ของแต่ละพิกเซล
    • แม้มี overhead จากการเรนเดอร์ฉากสองรอบ แต่ในฉากที่มี overdraw มาก โดยทั่วไปผลได้มักมากกว่าต้นทุน
    • เมื่อมี overdraw มาก การเพิ่ม Depth Pre-Pass อาจทำให้ประสิทธิภาพดีขึ้นได้มากกว่า 30%
    • หากเปลี่ยนการตั้งค่า pre-pass จะสามารถเรนเดอร์เฉพาะ fragment ที่ถูกบดบัง เพื่อ visualise fragment ที่จะถูกข้ามเมื่อใช้ pre-pass
    • รายละเอียดการ implement และการตั้งค่าใน Three.JS ดูได้จาก dedicated article
  • การสังเคราะห์เท็กซ์เจอร์ PBR ด้วย AI

    • AI-generated texture ถูกใช้ในแทบทุกฉาก
    • หากใช้แบบพอดี ผลลัพธ์อาจดูค่อนข้างดี และเท็กซ์เจอร์ทั้งหมดในฉากตัวอย่างเป็น AI-generated
    • กระบวนการสร้างเท็กซ์เจอร์ สร้าง PBR map และรวมเป็น seamless 4K texture โดยไม่ต้อง upscale กล่าวถึงไว้ใน บทความแยก
    • เว็บไซต์ที่บทความนั้นกล่าวถึงสำหรับสร้าง PBR map ไม่สามารถใช้งานได้อีกแล้ว
    • ปัจจุบันใช้ DeepBump สำหรับสร้าง normal map และเมื่อจำเป็นก็ใช้เครื่องมือที่ไม่ใช่ AI อย่าง Materialize กับ map อื่น ๆ
  • Volumetric Fog/Clouds

    • volumetric rendering เป็นสาขาที่ได้รับความสนใจ เพราะสามารถเพิ่มเอฟเฟกต์เฉพาะตัวให้ฉากได้
    • สร้าง shader ที่ค่อนข้างใช้ได้ทั่วไป ซึ่งสามารถเพิ่ม cloud หรือ fog ให้กับฉาก Three.JS ใด ๆ ได้
    • ได้แรงบันดาลใจจาก Shadertoy ของ Inigo Quilez เพื่อสร้าง volumetric clouds shader พื้นฐานที่ใช้ LoD noise lookup คล้ายกัน แล้วภายหลังขยายให้เป็นรูปแบบที่ทั่วไปกว่าและตั้งค่าได้มากขึ้น
    • shader นี้มีประโยชน์ในการเติมพื้นที่ว่างของเลเวลที่ค่อนข้างโล่ง และเพิ่ม ความรู้สึกมีพลวัต ให้เลเวลที่นิ่ง ด้วย cloud หรือ fog ที่เคลื่อนไหว
    • ยังใช้โค้ดและแนวทางบางส่วนที่ n8programs พัฒนาในโปรเจกต์ three-good-godrays
    • three-good-godrays ก็ถูกใช้บ่อยเช่นกัน และเพิ่มบรรยากาศที่โดดเด่นมากให้เลเวล

Mesh และ Geometry

  • การสร้าง mesh ขณะ runtime เป็นพื้นที่ที่ถูกทำงานมากขึ้นเรื่อย ๆ
  • ชอบแนวคิดที่โลกงอกขึ้นจาก software seed แต่ต้องการหลีกเลี่ยงปรากฏการณ์ “ไม่มีที่สิ้นสุดแต่กลวงเปล่า” ของเกมบางเกมที่ชู procedural generation
  • ดังนั้นจึงมุ่งเน้นเป็นหลักที่การเพิ่ม การตกแต่ง ฉากหลัง และ procedural flourish ให้เลเวล มากกว่าการสร้างประสบการณ์หลักทั้งหมดแบบ procedural
  • LoD Terrain

    • terrain generation เป็นพื้นที่ตัวอย่างของการพัฒนาเกมเชิงกระบวนวิธี และตัว implementation เองก็ไม่ได้พิเศษ
    • เช่นเดียวกับวิธีส่วนใหญ่ ใช้ noise function สร้าง terrain heightmap แล้ว tessellate เป็น triangle เพื่อเรนเดอร์
    • การ texturing ใช้ Triplanar Mapping หรือ Hex Tiling
    • หัวใจสำคัญคือ ระบบ LoD ซึ่งสร้าง terrain เป็นหน่วย tile และสร้างแต่ละ tile ในหลายความละเอียด
    • จะสลับความละเอียดต่าง ๆ แบบไดนามิกตามระยะห่างระหว่าง tile กับ camera
    • terrain generation system นี้ถูกนำกลับมาใช้ซ้ำบ่อย และด้วยระบบที่ยืดหยุ่นและมีประสิทธิภาพ จึงนำไปใช้กับหลายเลเวลได้โดยใช้ความพยายามน้อย
  • Pipeline การประมวลผลและปรับแต่ง mesh แบบ procedural

    • ส่วนที่ทำงานมากที่สุดช่วงหลังคือ pipeline สำหรับ procedural mesh processing
    • เป้าหมายเริ่มต้นคือการ subdivide และ deform low-poly mesh แบบ procedural รวมถึง mesh ที่สร้างแบบไดนามิก
    • จุดประสงค์คือทำให้ mesh เรียบง่ายอย่าง platform, boulder หรือโครงสร้างขนาดใหญ่ ดูสมจริงหรือน่าสนใจขึ้นเมื่อใส่ในเลเวล
    • งานนี้นำไปสู่ software pipeline ที่รับ raw geometry data ใน runtime ของเบราว์เซอร์ แก้ไขได้ตามต้องการ แล้วส่งออกกลับเป็นรูปแบบที่เรนเดอร์ได้
    • กระบวนการนี้ต้องพิจารณารายละเอียดเป็นพิเศษ โดยเฉพาะในการจัดการ normal
    • รายละเอียดการ implement อยู่ในบทความ subdividing meshes for displacement

ตัวเลือกทดลองถัดไป: Constructive Solid Geometry

  • เครื่องมือส่วนใหญ่ที่ระบุไว้ที่นี่เดิมเริ่มจาก implementation ครั้งเดียวเพื่อ use case เฉพาะ แต่ถูกนำกลับมาใช้ซ้ำในเลเวลและบริบทอื่น ๆ
  • ไอเดียหลักถัดไปที่อยากลองคือ Constructive Solid Geometry
  • Constructive Solid Geometry เป็นระบบที่ใช้ boolean operator กับพื้นที่ 3D
    • สามารถรวม mesh สองชิ้นใด ๆ เข้าด้วยกันได้
    • สามารถตัด chunk ออกจาก mesh ได้
    • และทำการปรับแต่งลักษณะคล้ายกันอื่น ๆ ได้
  • csg.js implement CSG toolkit ที่มี mesh primitive, boolean operator และ API ที่สะอาดไว้ในไฟล์ JavaScript ไฟล์เดียวพร้อมคอมเมนต์ ความยาวประมาณ 500 LoC
  • วางแผนว่าสักวันจะ port ไลบรารีนี้ไปเป็น Rust เพื่อทำความเข้าใจวิธีทำงานให้ดีขึ้น
  • หากใช้ CSG ร่วมกับ mesh processing pipeline ที่มีอยู่ ก็น่าจะสร้างผลลัพธ์ที่น่าสนใจได้มาก
  • โดยเฉพาะอยากลองฟีเจอร์ที่ทำให้ mesh เกิด damage แบบ procedural
    • ตัด chunk ออกจาก building หรือ bridge เพื่อจำลอง decay หรือ weathering
    • สร้าง crack บน wall หรือ road

1 ความคิดเห็น

 
GN⁺ 2024-07-29
ความคิดเห็นบน Hacker News
  • เมื่อก่อนเคยลองเล่นกับ procedural generation อยู่บ้าง โดยเฉพาะพยายามทำต้นไม้ที่ดูสวย แต่ชิ้นส่วนที่ผมขาดไปคือวิธีเชื่อม geometry เข้าด้วยกันให้ง่าย
    การสร้างทรงกระบอกสองอันนั้นง่าย แต่การต่อสองอันเข้าด้วยกันให้ดูเป็นธรรมชาตินั้นยากมาก
    ในทางทฤษฎี CSG น่าจะเติมเต็มส่วนนี้ได้ แต่การคิดปัญหาในกรอบนั้นไม่ง่าย เพราะมองเป็นแค่ลูปเพิ่ม vertex ไปเรื่อย ๆ ไม่ได้ ต้องโมเดลทุกอย่างเป็นรูปทรง 3D
    ผมเคยลองทำ routine ที่รับ vertex loop สองชุดแล้วเพิ่ม face เพื่อเชื่อมด้วย heuristic แต่การเลือกว่าควรเชื่อม vertex ไหนเข้าด้วยกันยากกว่าที่คิดมาก และได้รอยเชื่อมที่ดูแย่ได้ง่าย
    สักวันหนึ่งอยากทำเกมที่ระบบ procedural generation แบบโมดูลาร์หลายระบบทำงานร่วมกันได้โดยยังคงความฉับพลันไว้ เช่น ให้ระบบต่าง ๆ “ยึดครอง” ส่วนหนึ่งของโลก แล้วมอบหมายส่วนนั้นต่อให้ระบบอื่น หรือเชื่อมเข้ากับองค์ประกอบรอบ ๆ อย่างเป็นธรรมชาติ

    • ทำต่อไปก็ดี แต่อย่าให้มันซับซ้อนเกินไป
      เมื่อเร็ว ๆ นี้มีวิดีโอ [0] เกี่ยวกับการสร้างต้นไม้แบบ generative ซึ่งวิธีแก้คือปล่อยให้ทรงกระบอกตัดกันไปเลย วิธี hacky แบบนี้ก็ใช้ได้ดีและให้ผลลัพธ์ที่โอเคได้
      อย่างที่บอก CSG ก็ทำได้ แต่อาจซับซ้อนเกินจำเป็น อีกวิธีคือสร้าง skeleton ของต้นไม้ก่อนแล้วทำ lofting และถ้าจำเป็นก็ค่อยผสานกับ CSG เพื่อสร้างลำต้นกับเปลือก
      ไลบรารีดี ๆ จะเปิดความเป็นไปได้ใหม่ ๆ ถ้าหาไลบรารี geometry 3D ที่ทำ boolean operation อย่างการรวมทรงกระบอกหรือการลบ geometry ได้ ก็จะลองไอเดียใหม่ ๆ ได้มากมาย ผมเคยลองมาหลายตัว แต่ตัวเดียวที่ค่อนข้างถูกใจคือ JSCAD [1]
      [0] https://youtu.be/8zMbJmuwEUc?si=KQclrVPeSrIRmsbA
      [1] https://github.com/jscad/OpenJSCAD.org
    • ลองดูเรื่อง signed distance field น่าจะดี มันใกล้เคียงกับวิธีแก้ CSG ที่สง่างามที่สุดสำหรับ procedural geometry
      คล้ายกับวิธีบรรยายและแปลงรูปทรง 3D แบบ functional
      ถ้าอยากดูว่าคนใน demoscene ทำอะไรกับสิ่งนี้ไว้บ้าง ขอแนะนำอย่างยิ่งให้ค้นหา Mercury Delight บน YouTube ใน Shadertoy ก็มีตัวอย่างเยอะ และมีหลายอย่างที่น่าทึ่งจริง ๆ
    • ถ้า unroll ลูปได้ ก็สามารถมองเป็นวิธีแบบลูปที่เพิ่ม vertex ได้เช่นกัน
      https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Condition...
      ตัวอย่างด้านล่างก็น่าดูเช่นกัน
      https://github.com/MaxBondABE/batteries/blob/master/src/geom...
  • การสร้างภูมิประเทศ ที่ดี ไม่ว่าจะในสเกลระดับโลกหรือระดับสายตาคน ไม่ใช่เรื่องเล็กน้อยเลย
    แนวทางง่าย ๆ ก็อย่างที่เห็นในบทความ คือสร้าง heightmap ที่ขรุขระ ซึ่งแทบไม่เหมือนภูมิประเทศจริง และก็ไม่น่าสนใจนักสำหรับการสำรวจ
    ตัวอย่างเช่น Dwarf Fortress เริ่มจาก midpoint displacement พื้นฐาน แต่หลังจากนั้นผ่าน post-processing แบบปรับแต่งเฉพาะจำนวนมาก

    • ผมมองว่านี่เป็นปัญหาของวงการเกมโดยรวม
      สิ่งที่อยู่ในบทความนี้ ไม่มีอะไรที่เรียกว่าเล็กน้อยได้เลยแม้จะพูดแบบกว้าง ๆ ผู้เขียน ในแง่ของการเรนเดอร์ด้วย GPU shader เพียงอย่างเดียว ถือว่าใกล้เคียงกับ นักพัฒนา 100x อย่างชัดเจนเมื่อเทียบกับแทบทุกคนบนโลก
      “แนวทางง่าย ๆ” นี่ง่ายจริงหรือ?
      จากคน 8 พันล้านคน จะมีสักกี่เปอร์เซ็นต์ที่ implement ได้แม้แต่ขั้น “Hello World” ที่จำเป็นต่อการเริ่มบทความนี้? แล้วจะมีสักกี่คนที่รู้ว่า shader คืออะไร? งาน OpenGL ก็ไม่ได้มีมากมายขนาดนั้น แถมสถานการณ์แบบ “OpenGL คืออะไร เราใช้แต่ Unity” ก็พบได้ทั่วไป
      แล้วเกมเมอร์ออนไลน์ล่ะ? ณ วันที่ 28 กรกฎาคม 2024 เวลา 13:22 น. EST จากผู้เล่น 1,021,282 คน [1] ที่กำลังออนไลน์ใน Counter Strike 2 จะมีสักกี่เปอร์เซ็นต์ที่ implement ได้แม้แต่ขั้นแรกของ shader ที่เกมที่พวกเขาเล่นต้องใช้?
      จะมีสักกี่เปอร์เซ็นต์ที่คอมไพล์โปรแกรม C++ บน command line แบบง่าย ๆ หรือเขียนสคริปต์ JavaScript ที่ง่ายกว่านั้นในเบราว์เซอร์ได้? จริง ๆ นี่เป็นคำถามดักนิดหน่อย เพราะคนส่วนใหญ่แค่ใช้อีเมลก็แทบไม่คล่องแล้ว
      [1] https://steamdb.info/app/730/charts/
    • เคยเห็นวิธีสร้างภูมิประเทศโดยทำ heightmap ขรุขระขึ้นมาก่อน แล้วใช้การจำลอง erosion
    • ผมมองบทความนี้ว่าเป็น จุดเริ่มต้นที่ดี มากกว่าจะเป็น “ระดับที่นำไปออกขายได้”
      เช่นเดียวกับ AI การสร้าง procedural content ที่ดีที่สุดก็น่าจะมี post-processing แบบปรับแต่งเฉพาะอยู่ในผลลัพธ์สุดท้าย
  • เป็นบทความที่สรุป เทคนิค procedural หลายอย่างที่ใช้บนเว็บได้ดีมาก จึงมีประโยชน์จริง ๆ
    ถ้าสนใจ procedural RPG level editor ที่กำลังทำอยู่ ดูได้ที่ https://github.com/gamedevgrunt/3D-Action-RPG-JavaScript

  • ประสบการณ์ของผมกับการใช้ depth pre-pass ค่อนข้างปนกัน
    จากที่ลองหลายครั้ง บน GPU เดสก์ท็อประดับกลางถึงสูง ผมไม่เห็นการเพิ่มประสิทธิภาพที่ชัดเจน
    ไม่แน่ใจเหตุผลที่แน่ชัด แต่คิดว่าอาจเป็นเพราะ early Z rejection ช่วยลดการเรียก pixel shader อยู่แล้ว โดยปกติจะเรนเดอร์ mesh ทึบแสงจากหน้าไปหลัง
    อย่างไรก็ดี การทดลองของผมอยู่ในบริบทของแอปพลิเคชัน CAD/CAM ไม่ใช่เกม ฉากก็ค่อนข้างต่างจากสภาพแวดล้อมเกมทั่วไปด้วย คือมี texture น้อย แต่ geometry มีจำนวน polygon สูงมาก

    • เทคนิคเหล่านี้ทั้งหมด ขึ้นกับบริบท ไม่เพียงแต่ขึ้นกับเกมและ engine ทั้งระบบ แต่ยังขึ้นกับ artwork และการจัดองค์ประกอบฉากเฉพาะด้วย
      depth pre-pass มักเป็นขั้นแรกของ occlusion culling ด้วย แต่เรื่องนี้ก็ขึ้นกับบริบทเช่นกัน มีแนวโน้มว่าจะมีประโยชน์กับทิวทัศน์เมืองที่ซับซ้อนมากกว่าโมเดล CAD
    • ถ้าคอขวดอยู่ที่การประมวลผล vertex แล้ว depth pre-pass จะไม่ช่วย
      มันมีประโยชน์ที่สุดเมื่อ depth complexity สูงและ fragment shader แพง พูดอย่างเป็นธรรม เกมส่วนใหญ่ก็เข้าข่ายนี้
      ใน deferred renderer โดยทั่วไปไม่จำเป็น แต่ใน forward+ มักได้ประโยชน์ค่อนข้างมาก