วิธีวาดเส้นขอบในวิดีโอเกม
(ameye.dev)- วิธีหลัก ๆ ใน Unity สำหรับวาด เส้นขอบ รอบวัตถุ ได้แก่ rim effect, vertex expansion, blur buffer, Jump Flood algorithm และ edge detection ซึ่งแต่ละวิธีมีคุณภาพ ประสิทธิภาพ และต้นทุนการตั้งค่าที่แตกต่างกัน
- Rim effect ใช้ dot product ระหว่าง normal กับทิศทางการมองเพื่อประมาณ Fresnel และเน้นบริเวณด้านในของขอบ แต่ผลลัพธ์จะแตกต่างกันมากระหว่างโมเดลทรงกลมกับโมเดลที่มีมุมคม
- Vertex expansion สร้างเส้นรอบนอกโดยขยาย mesh ที่คัดลอกมาไว้ด้านหลัง และถ้าทำใน clip space จะช่วยให้ความกว้างบนหน้าจอสม่ำเสมอกว่า
- Blur buffer และ Jump Flood algorithm เป็นแนวทางที่วาด silhouette ลงใน buffer แล้วขยายออก โดยมีจุดเด่นตามลำดับคือเส้นที่นุ่มนวล หรือเส้นขอบที่กว้างมาก
- Edge detection ค้นหาความไม่ต่อเนื่องของ depth, normal และ color buffer จากทั้งหน้าจอเพื่อสร้างเส้น แต่ต้องปรับ threshold และ modulation อย่างละเอียดเพื่อลดเส้นที่ไม่ต้องการ
เหตุผลที่ใช้การเรนเดอร์เส้นขอบ
- เส้นขอบถูกใช้ในเกมเพื่อสร้าง สไตล์ภาพ หรือเป็น ตัวช่วยด้าน gameplay เช่น การเน้นวัตถุหรือการเลือกวัตถุ
- Sable ใช้เส้นขอบเพื่อให้ได้สไตล์เหมือนหนังสือการ์ตูน
- The Last of Us ใช้เส้นขอบเพื่อเน้นศัตรูเมื่อผู้เล่นเข้าสู่โหมดลอบเร้น
- เทคนิคทั้งห้าวิธีมี trade-off ที่แตกต่างกันระหว่างประสิทธิภาพ คุณภาพของภาพ และปริมาณการตั้งค่าด้วยมือ
Rim effect: เน้นขอบด้านในด้วยการประมาณ Fresnel
- Rim effect ประมาณ Fresnel effect เพื่อเรนเดอร์เส้นที่ rim/ขอบ ของวัตถุ
- การคำนวณอิงกับ dot product ของ normal vector
Nที่ normalize แล้ว และทิศทางการมองVที่ normalize แล้ว- นำค่า
1.0 - saturate(dot(N, V))ไปยกกำลังด้วยPเพื่อสร้างความเข้มของเอฟเฟกต์ - ใกล้เคียงกับการประมาณสำหรับเส้นขอบมากกว่า Fresnel ที่ถูกต้องตามฟิสิกส์
- นำค่า
- ในการใช้งานบน Unity custom shader จะควบคุม ความกว้าง, power, ความนุ่มนวล และสี ของเส้นขอบ
- ตัวอย่างการใช้งานใช้
smoothstep,lerp,_OutlineWidth,_OutlineSoftness,_OutlinePower,_OutlineColor
- ตัวอย่างการใช้งานใช้
- วิธีนี้จะแสดงเป็น เส้นด้านใน เสมอ และไม่เห็นบริเวณขอบนอกของวัตถุ
- เหมาะกับวัตถุที่กลมและเรียบ แต่สำหรับ cube หรือโมเดลที่ซับซ้อน ความกว้างของเส้นอาจไม่สม่ำเสมอหรือดูไม่เหมือนเส้นขอบ
Vertex expansion: ขยาย mesh ที่คัดลอกเพื่อสร้างเส้นด้านนอก
- Vertex expansion เรนเดอร์ สำเนา ของวัตถุหรือ mesh ต้นฉบับอีกครั้ง แล้วขยาย vertex จากด้านหลังของต้นฉบับเพื่อสร้างรูปทรงที่ใหญ่ขึ้น จากนั้นเรนเดอร์เป็นสีเดียว
-
การเลือกทิศทางการขยาย
- คุณภาพของเส้นขอบจะแตกต่างกันมากตามทิศทางที่ย้าย vertex
- หากย้ายตาม ทิศทางตำแหน่งของ vertex mesh จะมีลักษณะพองออก
- ใน local space ตำแหน่งของ vertex สามารถใช้เหมือนเวกเตอร์จากศูนย์กลางวัตถุไปยัง vertex ได้
- ถ้าไม่ normalize vertex ที่อยู่ไกลจากศูนย์กลางกว่าจะถูกย้ายมากกว่า
- การใช้
normalize(positionOS) * widthช่วยให้การย้ายใน object space สม่ำเสมอกว่า - หากย้ายตาม ทิศทาง normal vector จะได้ผลดีในวัตถุที่มีขอบเรียบ เช่น sphere หรือ capsule
- ในวัตถุที่มีมุมคมอย่าง cube อาจเกิดช่องว่างในเส้นขอบได้
- สามารถใช้ vertex color เป็นทิศทางการขยายได้เช่นกัน
- วิธีนี้คือการสร้าง custom normal แล้วเก็บไว้ในช่อง vertex color ของ mesh
- หาก bake normal แบบทรงกลมที่นุ่มนวลลงใน vertex color ของ cube จะได้เส้นขอบที่ดีกว่า
- ข้อเสียคือเป็นการตั้งค่าด้วยมือที่ต้องสร้าง custom normal สำหรับแต่ละ mesh แต่สามารถทำให้เป็นอัตโนมัติด้วยสคริปต์ได้
-
การเลือก space สำหรับการขยาย
- ในขั้น vertex ของ shader พิกัด vertex เริ่มจาก object space แล้วถูกแปลงผ่านเมทริกซ์ MVP ไปเป็น clip space
- ลำดับคือ object/model/local space → world space → camera/view space → clip space → screen space → viewport/window space
- หากขยายใน object space อาจทำให้เส้นขอบบิดเบี้ยว เพราะยังไม่ได้ใช้การแปลง MVP
- เมื่อใช้ model matrix อาจเกิดปัญหาเรื่อง scaling
- เมื่อไปจาก clip space เป็น screen space จะเกิด perspective shrinking จาก perspective division
- การเคลื่อนที่บางส่วนใน 3D space จะเคลื่อนไปตามทิศทางกล้อง จึงไม่ช่วยเพิ่มความกว้างของเส้นขอบบนหน้าจอ
- หากขยายใน clip space จะสร้างเส้นขอบที่สะอาดและมีความกว้างบนหน้าจอใกล้เคียงกันได้
- แปลงตำแหน่ง vertex และ normal ไปเป็น clip space แล้วแก้เฉพาะพิกัด
x,y - หารด้วยความกว้างและความสูงของหน้าจอเพื่อชดเชยอัตราส่วนภาพ
- คูณด้วย
wของ clip space เพื่อไม่ให้ perspective division หลังจากนั้นส่งผลสุทธิต่อความกว้างของเส้นขอบ - คูณด้วย
width * 2เพื่อให้หน่วยความกว้าง 1 ตรงกับ 1 พิกเซลบนหน้าจอ - วิธี clip space ก็ยังขึ้นกับ normal ของ mesh
- หากไม่ใช้ custom normal อาจเกิดช่องว่างที่มุมคมได้
- ถ้า normal ตั้งค่าไม่ถูกและชี้ไปทิศทางตรงข้าม vertex ของเส้นขอบก็จะย้ายไปทางตรงข้าม ทำให้เกิดช่องว่าง
- ดูคำอธิบายเพิ่มเติมได้ที่ creating an outline in clip space
-
การทำ masking
- ใน mesh ที่คัดลอกมา ควรแสดงเฉพาะ ส่วนเส้นขอบที่ยื่นออกมา เท่านั้น
- วิธีทั่วไปคือ cull front-facing geometry ของ mesh ที่คัดลอก แล้วใช้ backface สร้างเส้นขอบ
- depth test ใช้ less than or equal to เพื่อให้ backface เห็นเฉพาะตำแหน่งเส้นขอบ
- อีกวิธีคือใช้ stencil mask เพื่อไม่ให้ mesh ที่คัดลอกมาแสดงอยู่ด้านหน้าต้นฉบับ
- ในกรณีนี้ไม่จำเป็นต้อง cull
- จะไม่เกิดเส้นด้านในของวัตถุเลย
- หากวัตถุสองชิ้นซ้อนกัน จะเห็นเส้นขอบเฉพาะรอบวัตถุทั้งสองเท่านั้น
Blur buffer: ขยาย silhouette ด้วยการเบลอ
- วิธี blur buffer เรนเดอร์ silhouette ของวัตถุลงใน buffer แล้วขยายด้วย blur จากนั้นใช้ผลลัพธ์นั้นในการเรนเดอร์เส้นขอบ
-
Silhouette buffer
- ขั้นแรกคือการสร้าง silhouette buffer
- เรนเดอร์แต่ละวัตถุลงใน texture ด้วย shader ที่ส่งออกเป็นสีเดียว
- หากเรนเดอร์ silhouette ทั้งหมดเป็นสีขาว สามารถคูณด้วยสีเส้นขอบที่ต้องการในตอนท้ายเพื่อให้เป็นสีเดียวได้
- หากต้องการเส้นขอบคนละสีสำหรับแต่ละวัตถุ ก็สามารถเรนเดอร์ silhouette แต่ละอันด้วยสีเฉพาะได้
-
Blur pass
- Blur pass ใช้สำหรับขยาย silhouette buffer
- โดยปกติใช้ box blur หรือ Gaussian blur
- เพื่อปรับปรุงประสิทธิภาพ สามารถลดขนาด silhouette buffer ก่อน blur ได้
- Blur pass มีค่าใช้จ่ายสูง เพราะต้องคำนวณค่าเฉลี่ยหรือค่าเฉลี่ยถ่วงน้ำหนักของหลายพิกเซลรอบ ๆ แต่ละพิกเซล
- ควรทำ blur แบบ 2 pass
- สำหรับ box blur และ Gaussian blur ซึ่งเป็น separable filter สามารถลดความซับซ้อนของ algorithm จาก
O(N²)เป็นO(2N)ได้ - เริ่มจาก blur ในแนวตั้งก่อน แล้วนำผลลัพธ์ไป blur อีกครั้งในแนวนอนเพื่อให้ได้ผลลัพธ์สุดท้าย
- ความกว้างของเส้นขอบควบคุมด้วยพารามิเตอร์
_KernelSizeของ blur shader
-
Outline pass และ masking
- หลังจาก blur pass ให้นำ silhouette ที่ถูกเบลอไป composite กับฉากต้นฉบับเพื่อสร้างเส้นขอบ
- Blur buffer เหมาะกับ เส้นขอบที่นุ่มนวลหรือเรืองแสง
- หากใช้ step processing กับผล blur ก็สามารถเรนเดอร์เส้นขอบแบบแข็งได้เช่นกัน
- สามารถใช้ stencil mask เหมือนวิธี vertex expansion เพื่อให้เส้นขอบถูกเรนเดอร์เฉพาะด้านหลัง geometry
- อาจมีผลกระทบต่อประสิทธิภาพมากกว่าวิธีอื่น
Jump Flood algorithm: จัดการเส้นขอบที่กว้างมาก
- วิธีที่สี่คือการเรนเดอร์เส้นขอบด้วย Jump Flood algorithm
- ข้อดีคือสามารถเรนเดอร์เส้นขอบที่กว้างมากได้ด้วยต้นทุนด้านประสิทธิภาพที่สมเหตุสมผล
- รายละเอียดต่อไปอยู่ในบทความของ Ben Golus เรื่อง The Quest for Very Wide Outlines
Edge detection: ค้นหาความไม่ต่อเนื่องจากทั้งหน้าจอ
- วิธี edge detection วาดเส้นโดยค้นหา ความไม่ต่อเนื่อง ของฉากใน full-screen pass
- ความไม่ต่อเนื่องสามารถตรวจจับได้จากค่า depth buffer, normal vector, albedo color หรือข้อมูลอื่น ๆ ที่ใช้ได้ระหว่างกระบวนการเรนเดอร์
-
Roberts cross
- Roberts cross operator เป็น differential operator ที่คำนวณผลรวมกำลังสองของความต่างระหว่างพิกเซลแนวทแยง
- ในการใช้งานจริง จะตรวจจับ edge โดย convolution kernel กับภาพต้นฉบับ
- ใช้ kernel 2 ชุดสำหรับทิศทาง
xและทิศทางy - ขนาด kernel คือ
2 x 2 - ต้องใช้ sample เพียง 4 จุดรอบหนึ่งพิกเซล
- เป็น operator ที่เรียบง่ายแต่ให้ผลลัพธ์ที่ดีได้
-
Sobel operator
- Sobel operator ก็ใช้ kernel 2 ชุดสำหรับทิศทาง
xและทิศทางy - ขนาด Sobel kernel คือ
3 x 3และใช้ sample 9 จุดรอบหนึ่งพิกเซล - ดูการทำงานของ Sobel filter เพิ่มเติมได้ที่ blog post on Sobel filters
- Sobel operator ก็ใช้ kernel 2 ชุดสำหรับทิศทาง
-
แหล่งที่มาของความไม่ต่อเนื่อง
- วิธีทั่วไปคือค้นหาความไม่ต่อเนื่องจาก depth texture, normal texture และ color texture ที่ render pipeline สร้างสำหรับฉาก
- Edge detection pass จะ sample texture เหล่านี้และตรวจจับความไม่ต่อเนื่องด้วย operator ที่กล่าวถึงข้างต้น
- edge ที่สร้างขึ้นแบบนี้อาจถูกวาดเพราะพบความไม่ต่อเนื่องในหนึ่งในสาม buffer
- วิธีนี้มีการควบคุมรายวัตถุต่ำ เพราะเส้นขอบจะถูกใช้กับวัตถุทั้งหมดที่เขียนลงใน buffer นั้น
- การอนุญาตให้มีแหล่งความไม่ต่อเนื่องหลายแหล่งช่วยสร้างระบบเส้นขอบที่ robust กว่า
- edge บางเส้นตรวจพบจากทั้งสามแหล่ง
- edge จำนวนมากตรวจพบจากการมีส่วนร่วมของแหล่งใดแหล่งหนึ่งเท่านั้น
- สามารถกำหนด weight และ threshold ต่างกันให้แต่ละแหล่งได้ เพื่อควบคุมผลลัพธ์ทางภาพของเส้นขอบ
-
การ modulate edge detection
- หากใช้เพียง edge detection operator กับ buffer ความไม่ต่อเนื่อง อาจได้ผลลัพธ์ที่ปราศจาก artifact ได้ยาก
- ใน render pipeline จำนวนมาก depth buffer ถูกใช้งานแบบ non-linear
- เมื่อวัตถุสองชิ้นที่อยู่ใกล้กล้องห่างกัน 1m ความต่างของ depth จะมากกว่าวัตถุสองชิ้นที่อยู่ไกลและห่างกัน 1m
- เพื่อชดเชยเรื่องนี้ สามารถ modulate threshold สำหรับการตรวจจับความไม่ต่อเนื่องของ depth ด้วย depth buffer เองได้
- geometry ที่อยู่ใกล้ต้องมีความไม่ต่อเนื่องของค่า depth มากขึ้นก่อนจะถูกตรวจจับเป็น edge
- อาจเกิด edge ที่ไม่ต้องการเมื่อมี grazing angle เล็ก ๆ ได้เช่นกัน
- สามารถ modulate ด้วย Fresnel mask ที่สร้างจาก dot product ของ normal vector
NกับทิศทางการมองV - mask นี้เป็นชนิดเดียวกับที่ใช้ในวิธี rim effect
- สามารถใช้เทคนิค modulation อื่นได้เช่นกัน แต่การเลือกขึ้นอยู่กับ visual effect ที่ต้องการ
-
แหล่งความไม่ต่อเนื่องแบบ custom
- สามารถส่ง แหล่งความไม่ต่อเนื่องแบบ custom ให้ outline shader ได้ด้วย
- สิ่งนี้คือ render texture ที่สร้างขึ้นโดยตรงระหว่างกระบวนการเรนเดอร์ และบรรจุข้อมูล custom สำหรับใช้สร้างเส้นขอบ
- เพราะสามารถควบคุมได้โดยตรงว่าวัตถุใดถูกเขียนลงใน custom buffer จึงควบคุมได้ด้วยว่าวัตถุใดจะได้รับเส้นขอบ
- ตัวอย่างเช่น เรนเดอร์ vertex color ของ mesh ลงใน texture เพื่อสร้างแหล่งความไม่ต่อเนื่อง
- อีกวิธีคือการระบายสีพื้นผิวตาม world position หรือสร้าง custom buffer ที่รวมข้อมูลจาก depth buffer และ normal buffer
- ดูข้อมูลเพิ่มเติมได้ที่ Linework section map
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
บทความ Jump Flood Algorithm ที่ลิงก์ไว้นั้นยอดเยี่ยมมาก: https://bgolus.medium.com/the-quest-for-very-wide-outlines-b...
น่าสนใจที่ได้คิดถึงหลายแนวทางซึ่งใช้ได้ในระดับพิกเซล/เท็กเซล และตรงนี้ Signed Distance Field (SDF) ก็ถูกใช้ในวิธีที่ชาญฉลาดเพื่อช่วยทำงานหลายอย่างแทน
ผลลัพธ์ที่สร้างเส้นขอบตามความหนาที่ต้องการได้ในเวลาเชิงเส้นนั้นน่าทึ่งเมื่อเทียบกับวิธีคำนวณแบบ brute force สำหรับเส้นขอบหนา ๆ
ขอแนะนำ SDF อย่างยิ่ง ไม่ว่าจะเป็นแบบอิงเวกเตอร์ แบบอิงฟังก์ชันเหมือนงานของ Inigo Quilez หรือแบบแรสเตอร์อิงเท็กเซล/ว็อกเซลเหมือนในบทความ
Houdini ก็รองรับ raster SDF ได้ดีและมีชุดเครื่องมือ SDF ที่สุกงอมแล้ว ดังนั้นแม้แต่เวอร์ชันฟรีก็น่าลองดู
ข้อเสียคือถ้าจะรันบน GPU ต้องมีการเขียนแบบสุ่มเข้าถึง หรือก็คือ compute shader
ถ้าใช้ CPU ได้ ก็มี implementation อยู่หลายตัว
JavaScript: https://parmanoir.com/distance/
C: https://github.com/983/df
C++: https://github.com/opencv/opencv/blob/4.x/modules/imgproc/sr...
Python: https://github.com/pymatting/pymatting/blob/afd2dec073cb08b8...
เพราะคุณภาพดี และสามารถเรนเดอร์เอฟเฟกต์ตามระยะทางได้ เช่น เส้นขอบที่เต้นเป็นจังหวะ
เครื่องมือวาดเส้น 3D ตัวนี้ก็เก็บ SDF ไว้ในเท็กซ์เจอร์ขนาดเล็กแล้ว sample ตอนรันไทม์: https://x.com/alexanderameye/status/1663523972485357569
SDF ทรงพลังจริง ๆ
ถ้าขยายแนวทางเดียวกันไปถึง radiance cascades ก็อาจเร็วขึ้นได้อีก จึงค่อนข้างน่าสนใจ
สักวันหนึ่งอยากเจาะลึก กราฟิก 3D แบบมีสไตล์เฉพาะ ในฐานะโปรเจกต์วิจัยและพัฒนา
ช่วงหลังมีความคืบหน้าไปพอสมควร แต่ยังดูเหมือนมีผลไม้ที่เก็บได้ง่ายเหลืออยู่อีกมาก
สงสัยว่าเมื่อกล้องถอยห่างออกไป จะลดรายละเอียดของโมเดล 3D แบบ toon rendering อย่างไร และจะเปลี่ยนผ่านระหว่างรูปลักษณ์ที่มีสไตล์จัดกับที่มีสไตล์น้อยกว่าให้เป็นธรรมชาติได้อย่างไร
อีกปัญหาคือจะเรนเดอร์ฉากหลังสีน้ำของแอนิเมชัน 2D วาดมือให้ดูน่าเชื่อในฉาก 3D ได้ไหม และจะทำให้รอยพู่กันกับเท็กซ์เจอร์กระดาษใน screen space เคลื่อนไหวอย่างนุ่มนวลได้อย่างไร
ยังเป็นคำถามเปิดอยู่ว่าควรแสดงองค์ประกอบอย่างควัน เปลวไฟ ต้นไม้ หญ้า โคลน ฝน ขน และน้ำ ในเกม 3D แบบมีสไตล์เฉพาะอย่างไร
ยังสงสัยด้วยว่าจะทำให้การดัดแปลงโมเดลเล็กน้อยเพื่อให้ดูดีขึ้นจากมุมกล้องปัจจุบันแบบแอนิเมชันวาดมือ เป็นระบบอัตโนมัติในเกมที่มีกล้องอิสระได้ไหม
คำถามใหญ่คือ mesh editor และ background editor ในอุดมคติสำหรับ renderer แบบมีสไตล์ควรเป็นอย่างไร และจำเป็นต้องมีพื้นผิว 3D กับ rig ที่ถูกต้องตามฟิสิกส์จริง ๆ ไหม หรือจะนิยามให้เป็นนามธรรมกว่านั้นได้ไหม
ก็น่าสนใจเช่นกันว่าจะเรนเดอร์ pixel art ย้อนยุคจากโมเดล 3D แบบเรียบง่ายเพื่อสร้างเกม 2D เชิง procedural ได้ไหม และเมื่อ mesh สองชิ้นบังเอิญตัดกัน ก็อาจใช้การทำสไตล์ให้จุดตัดนั้นสะดุดตาน้อยลงได้
แค่คำถามเหล่านี้ก็น่าจะเติมเต็มอาชีพของคนสิบคนได้แล้ว แต่ผมว่ากลับเป็นเรื่องดีเสียอีก
ฟิลเตอร์ Kuwahara ก็ดูน่าเชื่อถือพอสำหรับคนส่วนใหญ่
เครื่องมือแก้ไขสำหรับ renderer แบบมีสไตล์ แค่ Blender + Rigify + shape keys + เวทมนตร์จาก drivers อีกนิดหน่อยก็เพียงพอกับความต้องการของผมแล้ว
การทำ texture ใน Blender น่ารำคาญ แต่ถ้าระดับงานอดิเรกก็พอทนได้ และถ้าต้องการการควบคุม non-photorealistic rendering มากขึ้น fork ของ DillonGoo Studio อาจเหมาะกว่า: https://www.dillongoostudios.com/gooengine
การทำ pixel art จากโมเดล 3D ผมเคยลองด้วยวิธีเรนเดอร์แอนิเมชัน/โมเดลที่ความละเอียดต่ำ ผลลัพธ์โอเค แต่ต้องลองผิดลองถูก
จำได้ว่าเคยมีตัวอย่างที่ทำ post-processing ซับซ้อนขึ้นเพื่อกำจัดสิ่งอย่าง pixel flicker ด้วย
ความละเอียดต่ำ: https://x.com/Navy_Green/status/1525564342975995904
การทำให้เสถียร: https://x.com/Navy_Green/status/1693820282245431540
เริ่มอาชีพด้วยแอป VR แต่ไม่นานก็ย้ายไปทำเว็บเพราะตลาดดีกว่า
พอเห็นบทความแบบนี้แล้วก็คิดถึงสายงานนั้น
งานที่เกี่ยวกับ กราฟิก 3D, collision, shader มีความรู้สึกเหมือนเวทมนตร์บางอย่างที่หาได้ยากในสายอื่น
จริง ๆ แล้วมันเหมือนกับการสร้างโลกและจำลองฟิสิกส์ขึ้นมา และคณิตศาสตร์ก็ใช้งานได้จริงและโผล่มาบ่อยกว่าสาขาโปรแกรมมิงอื่นมาก
ความพึงพอใจในการทำงานสูงกว่ามาก และหัวข้อก็มีความลึกกับเพดานให้ไปต่อสูงกว่า
ทำมา 4 ปีแล้ว ยังไม่เคยชนกำแพงความเบื่อแบบตอนทำเว็บดีเวลลอปเลย
ไม่มีอะไรดีไปกว่าการมาทำงานวันจันทร์ เปิด engine editor แล้วมองโลกเล็ก ๆ ที่กำลังทำอยู่ถูกเรนเดอร์ขึ้นมา พร้อมคิดว่าจะใส่ฟีเจอร์เจ๋ง ๆ อะไรต่อไป
แต่ในทางกลับกัน ในสาย technical art ถ้าดูดผลงานไปป้อน neural network ส่วนที่น่าสนใจและให้ความรู้สึกคุ้มค่าจะหายไป และสำหรับงานมืออาชีพก็จะเหลือแต่งานน่าเบื่อเดิม ๆ หรืออาจเพิ่มขึ้นด้วยซ้ำ
มูลค่าของชุดทักษะทั้งหมดก็ลดลง และคนในวงการเทคทำท่าเหมือนรู้เรื่องงานนั้นดีกว่าคนที่ทำจริง ๆ พร้อมตำหนิว่าเราไม่ยินดีที่บริษัท Silicon Valley เอาผลงานราคา C- ออกมาขายในราคา F โดยไม่จ่ายค่าตอบแทนให้คนที่สร้างข้อมูลต้นทาง และทำลายตลาด
ชั่งข้อดีข้อเสียแล้ว ผมคิดว่าการย้ายไปทำเว็บดีเวลลอปเป็นทางเลือกที่ถูกต้อง
ในเกม Astral Divide ของผม ผมสร้างเทคนิคที่ไม่ได้อยู่ในบทความนี้
คล้ายกับ Blurred Buffer แต่ไม่ทำพาสเบลอ และใช้เส้นขอบที่เกิดจาก anti-aliasing แทน
วาดวัตถุเป็นสีขาวทึบบนพื้นหลังสีดำโปร่งใส แล้วใน fragment shader ก็กรองเฉพาะพิกเซลที่ช่อง alpha ไม่ได้ทึบสนิทหรือโปร่งใสสนิท โดยใช้ค่า threshold ที่ hardcode ไว้
ผลลัพธ์ออกมาดีพอสมควร ต้นทุนด้านประสิทธิภาพก็ถูก และการ implement ก็ง่ายมาก
เมื่อก่อนตอน implement เครื่องมือครอป ในซอฟต์แวร์ตัดต่อวิดีโอ ระหว่างแก้ไขต้องทำให้พื้นที่นอกส่วนที่ถูกครอปดูเบลอชั่วคราว
ไม่อยากใส่พาสเบลอแพง ๆ อีก เลยแค่เปลี่ยน mipmap bias ให้เรนเดอร์ texture ความละเอียดต่ำลง แล้ว texture filtering ก็ทำงานให้ฟรีทั้งหมด
ลองเทียบกับเอฟเฟกต์เบลอเวลาครอปที่คล้ายกันใน PowerPoint แล้วแทบเหมือนกัน แถม PowerPoint ยังมี color banding นิดหน่อย แต่ implementation ของผมไม่มี
ในทำนองเดียวกัน ตอนที่ไม่มี anti-aliasing แบบเต็มความละเอียด พื้นที่ส่วนใหญ่ของ canvas เป็นสี่เหลี่ยม 2D ที่หมุนได้ เลยเห็นขอบเป็นขั้นบันได
แทนที่จะเปิด anti-aliasing เต็มหน้าจอ ผมขยายสี่เหลี่ยมทุกอันออกเล็กน้อยและย่อพิกัด UV ลงตามสัดส่วน ทำให้ขอบที่มองเห็นอยู่ด้านในของสี่เหลี่ยม 3D จริง ๆ แล้วครั้งนี้ texture filtering ก็แก้ให้ฟรีอีกเช่นกัน
หรือเป็นวิธีที่คงค่า alpha ไว้แล้วตั้งแค่สี ทำให้เส้นขอบดูจางแต่ไม่เป็นขั้นบันได?
โน้ตยอดเยี่ยมมาก
ช่วงนี้ผมกำลังหาแนวทาง edge detection อยู่ แล้วเจอวิธีดี ๆ จากผู้พัฒนา Mars First Logistics: https://www.reddit.com/r/Unity3D/comments/taq2ou/improving_e...
บทความก็ดีมาก และผลลัพธ์ก็ดูดีมาก: https://ameye.dev/notes/rendering-outlines/edge-detection/co...
ดูเหมือนฉากหนึ่งจากการ์ตูนเนเธอร์แลนด์ Franka
เป็นบทความที่ยอดเยี่ยมจริง ๆ และ ประสบการณ์การอ่าน ก็ยอดเยี่ยมด้วย
อธิบายแนวคิดยาก ๆ ด้วยภาษาที่ใครก็เข้าใจได้ แผนภาพและตัวอย่างก็ดี อีกทั้งระยะห่างและ typography ยังทำให้อ่านง่ายในระดับสูงสุด
อยากรู้ว่าอะไรเป็นแรงบันดาลใจให้สร้างธีมปัจจุบัน และเคยคิดจะทำแพลตฟอร์มเผยแพร่คอนเทนต์ที่เน้นวิศวกรบ้างไหม
ในซอฟต์แวร์ technical art คือรักแรกของผมอย่างแท้จริง
อยากให้ Godot มี pipeline สำหรับ compute shader ของเอฟเฟกต์ post-processing ที่ใช้ง่ายกว่านี้
การตั้งค่า compositor plugin ตอนนี้มี boilerplate ค่อนข้างเยอะ
repository นี้เป็นตัวอย่างที่ดีของ post-processing ใน Godot: https://github.com/sphynx-owner/JFA_driven_motion_blur_demo
ผมจำได้ว่าเห็นเอฟเฟกต์นี้ครั้งแรกใน Wacky Races บน Dreamcast
ตอนนั้นมีการโปรโมตเยอะว่าเป็นเกมแรกที่นำเอฟเฟกต์นี้มาใช้ และนักพัฒนาแทบจะเป็นผู้คิดค้นมันขึ้นมา
ไม่รู้ว่านั่นเป็นเรื่องจริงหรือเป็นการพูดเกินจริงเพื่อการตลาด แต่ในฐานะเกมเมอร์ ผมมั่นใจว่าประสบการณ์แรกของผมคือตอนนั้น
พูดตรง ๆ Dreamcast เป็นฮาร์ดแวร์แรกที่สามารถทำเอฟเฟกต์นี้แบบเรียลไทม์ด้วยคุณภาพสูงได้
ผมพัฒนา เอฟเฟกต์ cel shading ให้เกม Looney Tunes: Space Race บน Dreamcast และทำได้ทันทีในสัปดาห์แรกที่ได้รับ dev kit ของ Dreamcast
Infogrames Sheffield ผู้สร้าง Wacky Racers เห็น implementation เวอร์ชันแรก ๆ ของเรา แล้วเพิ่มเอฟเฟกต์ที่คล้ายกันเข้าไปในเกมของตัวเอง
มันดูดี แต่ถูกใส่เข้ามาช่วงท้ายการผลิต จึงไม่ได้ถูก optimize ให้เข้ากับเอฟเฟกต์นั้นเหมือนเกมของเรา
ทีม Jet Grind Radio ก็สร้างเอฟเฟกต์เดียวกันขึ้นเอง และวางจำหน่ายก่อนเรา
algorithm เหมือนกันเป๊ะ แต่การใช้งานต่างกัน ฝั่งนั้นยอมรับเส้นขอบที่ไม่สม่ำเสมอ หนา และขรุขระอย่างเต็มที่ ส่วน Sheffield กับเราพยายามสู้กับลักษณะนั้นเพื่อให้เข้ากับสไตล์งานศิลป์ที่สม่ำเสมอและดั้งเดิมกว่า
ประมาณหนึ่งปีต่อมา ใน Dragons Lair 3D บน Xbox ดูเหมือนมีใครบางคนหาวิธีรัน cel shading แบบ edge detection แบบเรียลไทม์ได้
ผมเคยทำ implementation ทดลองของแนวทางนั้นบน Dreamcast แต่ประสิทธิภาพไม่พอเลยสำหรับใช้กับตัวละครหลายตัวพร้อมกันขณะรันเกม
ไม่รู้ว่าเป็นเพราะ Xbox แรงกว่า หรือ algorithm ฉลาดกว่า แต่ผลลัพธ์นั้นปฏิเสธไม่ได้
ถ้าอยากทำเกมที่ดูเหมือนการ์ตูนวาดมือจริง ๆ ส่วนตัวผมยังคิดว่าวิธีนั้นเป็นวิธีที่ให้คุณภาพดีที่สุด
สักวันอยากหาข้ออ้างมาทำมันใหม่อีกครั้ง และตอนนี้ประสิทธิภาพคงไม่ใช่ปัญหาแล้ว
เป็นความบังเอิญที่น่าทึ่งทีเดียว
ผมต้องเริ่ม shader programming และการเรนเดอร์ 3D ให้ได้แล้ว
บทความแบบนี้ดีจริง ๆ และอยากให้ตัวเองใช้ shader ได้จริง