การออกแบบเครื่องมือเกม - Ubershader - การออกแบบการจัดการ Shader [ปิด]


18

ฉันต้องการใช้ระบบ Ubershader ที่ยืดหยุ่นด้วยการแรเงาที่เลื่อนออกไป ความคิดปัจจุบันของฉันคือการสร้างความแตกต่างจากโมดูลซึ่งจัดการกับคุณสมบัติบางอย่างเช่น FlatTexture, BumpTexture, Displacement Mapping เป็นต้นนอกจากนี้ยังมีโมดูลขนาดเล็กที่ถอดรหัสสีทำการแมปโทนและอื่น ๆ ซึ่งมีข้อดีที่ฉันสามารถ แทนที่โมดูลบางประเภทหาก GPU ไม่รองรับดังนั้นฉันสามารถปรับให้เข้ากับความสามารถของ GPU ในปัจจุบัน ฉันไม่แน่ใจว่าการออกแบบนี้ดีหรือไม่ ฉันกลัวว่าฉันจะเลือกได้ไม่ดีตอนนี้และจ่ายเงินในภายหลัง

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


3
ไม่นานพอสำหรับคำตอบที่แท้จริง: คุณจะทำได้ดีกับวิธีการนี้ถ้าคุณเริ่มต้นเล็ก ๆ และปล่อยให้มันเติบโตตามความต้องการของคุณแทนที่จะพยายามสร้าง MegaCity-One ที่มาพร้อมกับหน้า ประการแรกคุณลดความกังวลที่ใหญ่ที่สุดของคุณในการออกแบบวิธีที่มากเกินไปและจ่ายให้ในภายหลังหากมันไม่ได้ผลอันดับที่สองคุณหลีกเลี่ยงการทำงานพิเศษที่ไม่เคยใช้
Patrick Hughes

โชคไม่ดีที่เราไม่ยอมรับคำถาม "คำขอทรัพยากร" อีกต่อไป
Gnemlock

คำตอบ:


23

วิธีกึ่งทั่วไปคือการทำให้สิ่งที่ฉันเรียกส่วนประกอบ shaderคล้ายกับสิ่งที่ฉันคิดว่าคุณกำลังเรียกโมดูล

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

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

นี่ไม่ใช่วิธีการเดียวกันกับ ubershader Ubershaders เป็นที่ที่คุณวางรหัสทั้งหมดที่จำเป็นสำหรับทุกสิ่งไว้ในชุดเดียวของ shader อาจใช้ #ifdefs และชุดเครื่องแบบและชอบเปิดและปิดคุณสมบัติเมื่อรวบรวมหรือเรียกใช้ โดยส่วนตัวแล้วฉันเกลียดการใช้วิธี ubershader แต่เครื่องมือ AAA บางอันค่อนข้างน่าประทับใจใช้พวกเขา (โดยเฉพาะ Crytek เป็นที่สนใจ)

คุณสามารถจัดการชิ้นส่วน shader ได้หลายวิธี วิธีที่ทันสมัยที่สุด - และมีประโยชน์หากคุณวางแผนที่จะสนับสนุน GLSL, HLSL และคอนโซล - คือการเขียนโปรแกรมแยกวิเคราะห์สำหรับภาษา shader (อาจใกล้เคียงกับ HLSL / Cg หรือ GLSL มากที่สุดเท่าที่จะทำได้ "devs" ของคุณ ) ที่สามารถใช้สำหรับการแปลจากแหล่งสู่แหล่งที่มา อีกวิธีการหนึ่งคือห่อส่วนของ shader ในไฟล์ XML หรือสิ่งที่คล้ายกันเช่น

<shader name="example" type="pixel">
  <input name="color" type="float4" source="vertex" />
  <output name="color" type="float4" target="output" index="0" />
  <glsl><![CDATA[
     output.color = vec4(input.color.r, 0, 0, 1);
  ]]></glsl>
</shader>

หมายเหตุด้วยวิธีการดังกล่าวคุณสามารถสร้างส่วนรหัสได้หลายส่วนสำหรับ API ที่แตกต่างกันหรือแม้แต่รุ่นส่วนของรหัส (เพื่อให้คุณสามารถมีรุ่น GLSL 1.20 และรุ่น GLSL 3.20) กราฟของคุณสามารถแยกส่วนของ shader ที่ไม่มีส่วนของโค้ดที่เข้ากันได้โดยอัตโนมัติดังนั้นคุณสามารถรับการย่อยสลายแบบกึ่งสง่างามบนฮาร์ดแวร์รุ่นเก่า (ดังนั้นการทำแผนที่ปกติหรืออะไรก็ตามที่ยกเว้นฮาร์ดแวร์เก่าที่ไม่สามารถรองรับได้ ทำการตรวจสอบเครืออย่างชัดเจน)

ตัวอย่าง XMl นั้นสามารถสร้างสิ่งที่คล้ายกับ (ขอโทษถ้านี่คือ GLSL ที่ไม่ถูกต้องเป็นเวลานานแล้วที่ฉันต้องอยู่ภายใต้ API ตัวเอง):

layout (location=0) in vec4 input_color;
layout (location=0) out vec4 output_color;

struct Input {
  vec4 color;
};
struct Output {
  vec4 color;
}

void main() {
  Input input;
  input.color = input_color;
  Output output;

  // Source: example.shader
#line 5
  output.color = vec4(input.color.r, 0, 0, 1);

  output_color = output.color;
}

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

หากคุณมีชิ้นจำนวนมากอินพุตของพวกเขา (ซึ่งไม่ได้จัดเป็นเอาต์พุตโดยชิ้นส่วนบรรพบุรุษในทรี) จะถูกรวมเข้าในบล็อกอินพุตเช่นเดียวกับเอาต์พุตและโค้ดจะถูกต่อกัน มีการทำงานพิเศษเล็กน้อยเพื่อให้แน่ใจว่าขั้นตอนตรงกัน (จุดยอด vs ชิ้นส่วน) และรูปแบบการป้อนแอตทริบิวต์ของจุดสุดยอด "เพิ่งทำงาน" ข้อดีอีกอย่างของวิธีนี้คือคุณสามารถเขียนดัชนีการรวมและแอตทริบิวต์การป้อนข้อมูลที่ชัดเจนซึ่งไม่ได้รับการสนับสนุนใน GLSL เวอร์ชันเก่าและจัดการสิ่งเหล่านี้ในไลบรารีการสร้าง / การรวม shader ของคุณ ในทำนองเดียวกันคุณสามารถใช้ข้อมูลเมตาในการตั้งค่า VBOs และการglVertexAttribPointerโทรของคุณเพื่อให้มั่นใจในความเข้ากันได้และทุกอย่าง "ใช้งานได้"

น่าเสียดายที่ไม่มีไลบรารีข้าม API ที่ดีเช่นนี้อยู่แล้ว Cg ใกล้เคียงกันแล้ว แต่มีการรองรับอึสำหรับ OpenGL บนการ์ด AMD และอาจช้ามากหากคุณใช้คุณสมบัติการสร้างโค้ดพื้นฐานที่สุด เฟรมเวิร์กเอฟเฟ็กต์ DirectX ทำงานด้วยเช่นกัน แต่แน่นอนว่าไม่มีการรองรับภาษาใด ๆ นอกเหนือจาก HLSL มีไลบรารี่ที่ไม่สมบูรณ์ / buggy สำหรับ GLSL ที่เลียนแบบไลบรารี่ DirectX แต่ให้สถานะเป็นครั้งสุดท้ายที่ฉันตรวจสอบว่าฉันเพิ่งจะเขียนเอง

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

#if USE_NORMAL_MAPPING
  vec4 normal;
  // all your normal mapping code
#else
  vec4 normal = normalize(in_normal);
#endif

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

เพื่อความชัดเจนทั้งสองวิธี (รวมถึงการเขียนเฉดสีที่หลากหลายด้วยตนเอง) ล้วนถูกใช้ในพื้นที่ AAA ใช้งานวิธีใดดีที่สุดสำหรับคุณ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.