วิธีกึ่งทั่วไปคือการทำให้สิ่งที่ฉันเรียกส่วนประกอบ 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 ใช้งานวิธีใดดีที่สุดสำหรับคุณ