มีวิธีการส่งผ่านจำนวนสถานที่แสง (และสี) โดยพลการสำหรับชิ้นส่วน shader และวนรอบพวกเขาใน Shader?
ถ้าไม่เช่นนั้นไฟหลายดวงควรถูกจำลองอย่างไร? ตัวอย่างเช่นในแง่ของการกระจายแสงทิศทางคุณไม่สามารถส่งผ่านน้ำหนักรวมสำหรับ shader ได้
มีวิธีการส่งผ่านจำนวนสถานที่แสง (และสี) โดยพลการสำหรับชิ้นส่วน shader และวนรอบพวกเขาใน Shader?
ถ้าไม่เช่นนั้นไฟหลายดวงควรถูกจำลองอย่างไร? ตัวอย่างเช่นในแง่ของการกระจายแสงทิศทางคุณไม่สามารถส่งผ่านน้ำหนักรวมสำหรับ shader ได้
คำตอบ:
โดยทั่วไปมีสองวิธีในการจัดการกับสิ่งนี้ ทุกวันนี้พวกมันถูกเรียกว่าการเรนเดอร์เรนเดอร์และเรนเดอร์เรนเดอร์ มีการเปลี่ยนแปลงหนึ่งในสองสิ่งนี้ที่ฉันจะกล่าวถึงด้านล่าง
ให้แสงแต่ละวัตถุหนึ่งครั้งสำหรับทุกแสงที่มีผล ซึ่งรวมถึงแสงโดยรอบ คุณใช้โหมดการผสมผสานแบบเพิ่มเติม ( glBlendFunc(GL_ONE, GL_ONE)
) ดังนั้นการมีส่วนร่วมของแสงแต่ละครั้งจะถูกเพิ่มเข้าด้วยกัน เนื่องจากการมีส่วนร่วมของแสงที่แตกต่างกันเป็นสารเติมแต่งเฟรมเฟรมจึงได้รับคุณค่าในที่สุด
คุณสามารถรับ HDR ได้โดยการเรนเดอร์ไปที่เฟรมเฟรมจุดลอยตัว จากนั้นคุณจะผ่านฉากสุดท้ายเพื่อลดค่าแสง HDR ลงในช่วงที่มองเห็นได้ นี่จะเป็นจุดที่คุณใช้เบลล์และโพสต์เอฟเฟกต์อื่น ๆ ด้วย
การปรับปรุงประสิทธิภาพทั่วไปสำหรับเทคนิคนี้ (หากฉากมีวัตถุจำนวนมาก) คือการใช้ "pre-pass" ซึ่งคุณแสดงวัตถุทั้งหมดโดยไม่ต้องวาดอะไรไปที่ color framebuffer (ใช้glColorMask
เพื่อปิดการเขียนสี) นี่แค่เติมในบัฟเฟอร์ความลึก ด้วยวิธีนี้หากคุณแสดงวัตถุที่อยู่ด้านหลัง GPU จะสามารถข้ามชิ้นส่วนเหล่านั้นได้อย่างรวดเร็ว มันยังคงมีการเรียกใช้จุดสุดยอด Shader แต่มันสามารถข้ามการคำนวณส่วนที่มีราคาแพงกว่าโดยทั่วไป
นี่เป็นโค้ดที่ง่ายกว่าและง่ายต่อการมองเห็น และสำหรับฮาร์ดแวร์บางตัว (โดยส่วนใหญ่เป็นมือถือและ GPU ในตัว) จะมีประสิทธิภาพมากกว่าทางเลือกอื่น แต่สำหรับฮาร์ดแวร์ระดับไฮเอนด์ทางเลือกโดยทั่วไปจะชนะในฉากที่มีแสงจำนวนมาก
การเรนเดอร์ที่ถูกเลื่อนมีความซับซ้อนกว่าเล็กน้อย
สมการแสงที่คุณใช้ในการคำนวณแสงสำหรับจุดบนพื้นผิวใช้พารามิเตอร์พื้นผิวต่อไปนี้:
ในการเรนเดอร์เรนเดอร์พารามิเตอร์เหล่านี้ไปยังฟังก์ชั่นการส่องสว่างของชิ้นส่วนโดยการส่งโดยตรงจากจุดยอด shader ถูกดึงจากพื้นผิว (โดยปกติผ่านพิกัดพื้นผิว พารามิเตอร์อื่น ๆ สีกระจายอาจคำนวณได้จากการรวมสีต่อจุดสุดยอดกับพื้นผิวรวมพื้นผิวหลายสิ่ง
ในการเรนเดอร์แบบเลื่อนเราทำสิ่งนี้อย่างชัดเจนทั้งหมด ในการผ่านครั้งแรกเราแสดงผลวัตถุทั้งหมด แต่เราไม่ได้ทำให้สี แต่เราแสดงพารามิเตอร์พื้นผิวแทน ดังนั้นแต่ละพิกเซลบนหน้าจอจึงมีชุดของพารามิเตอร์พื้นผิว สิ่งนี้ทำได้ผ่านการเรนเดอร์ไปสู่พื้นผิวหน้าจอ พื้นผิวหนึ่งจะเก็บสีที่กระจายเป็น RGB ของมันและอาจเป็นไปได้ที่ความมันวาวแบบอัลฟ่า พื้นผิวอีกอันหนึ่งจะเก็บสีที่มีลักษณะเฉพาะ หนึ่งในสามจะเก็บปกติ และอื่น ๆ
ตำแหน่งมักจะไม่ถูกจัดเก็บ มันถูกสร้างขึ้นใหม่ในคณิตศาสตร์รอบที่สองซึ่งซับซ้อนเกินกว่าที่จะเข้าไปที่นี่ได้ พอจะพูดได้ว่าเราใช้บัฟเฟอร์ความลึกและตำแหน่งชิ้นส่วนพื้นที่หน้าจอเป็นอินพุตเพื่อหาตำแหน่งพื้นที่กล้องของจุดบนพื้นผิว
ดังนั้นตอนนี้พื้นผิวเหล่านี้จึงเก็บข้อมูลพื้นผิวทั้งหมดสำหรับทุกพิกเซลที่มองเห็นได้ในฉากเราเริ่มแสดงผลแบบเต็มหน้าจอ แสงแต่ละดวงจะได้รับการเรนเดอร์เต็มหน้าจอ เราสุ่มตัวอย่างจากพื้นผิวพารามิเตอร์พื้นผิว (และสร้างตำแหน่งใหม่) จากนั้นใช้เพื่อคำนวณการมีส่วนร่วมของแสงนั้น มีการเพิ่ม (อีกครั้งglBlendFunc(GL_ONE, GL_ONE)
) ในภาพ เราทำเช่นนี้ต่อไปจนกว่าไฟจะหมด
HDR อีกครั้งเป็นขั้นตอนหลังกระบวนการ
ข้อเสียที่ใหญ่ที่สุดในการเรนเดอร์คือการลดรอยหยัก มันต้องใช้การทำงานกับ antialias อีกเล็กน้อย
ข้อเสียที่ใหญ่ที่สุดถ้า GPU ของคุณมีแบนด์วิดธ์หน่วยความจำจำนวนมากก็คือประสิทธิภาพ เราแสดงรูปทรงเรขาคณิตที่แท้จริงเพียงครั้งเดียว (หรือ 1 + 1 ต่อแสงที่มีเงาหากเรากำลังทำแผนที่เงา) เราไม่เคยใช้เวลากับพิกเซลหรือเรขาคณิตที่ซ่อนอยู่ซึ่งไม่สามารถมองเห็นได้หลังจากนี้ เวลาแสงผ่านทั้งหมดจะถูกใช้ไปกับสิ่งที่มองเห็นได้จริง
หาก GPU ของคุณไม่มีแบนด์วิดท์หน่วยความจำจำนวนมากไฟสัญญาณผ่านอาจเริ่มเจ็บได้ การดึงจากพื้นผิว 3-5 จุดต่อพิกเซลหน้าจอไม่ใช่เรื่องสนุก
นี่เป็นความแตกต่างของการเรนเดอร์ที่มีการแลกเปลี่ยนที่น่าสนใจ
เช่นเดียวกับในการเรนเดอร์รอคุณแสดงพารามิเตอร์พื้นผิวของคุณไปยังชุดของบัฟเฟอร์ อย่างไรก็ตามคุณมีข้อมูลพื้นผิวแบบย่อ ข้อมูลพื้นผิวเพียงอย่างเดียวที่คุณสนใจในครั้งนี้คือค่าบัฟเฟอร์ความลึก (สำหรับการสร้างตำแหน่งใหม่), ปกติและความมันวาวแบบ specular
จากนั้นสำหรับแต่ละแสงคุณคำนวณเพียงแค่ผลของแสง ไม่มีการคูณด้วยสีผิวไม่มีอะไรเลย เพียงแค่จุด (N, L) และคำศัพท์เฉพาะอย่างสมบูรณ์โดยไม่มีสีผิว ควรระบุเงื่อนไขที่เป็นข้อมูลจำเพาะและกระจายในบัฟเฟอร์แยกต่างหาก คำศัพท์เฉพาะและแสงแบบกระจายสำหรับแสงแต่ละอันจะถูกนำมารวมกันภายในสองบัฟเฟอร์
จากนั้นคุณปรับรูปทรงเรขาคณิตอีกครั้งโดยใช้การคำนวณแบบ specular และการกระจายแสงเพื่อทำชุดค่าผสมสุดท้ายกับสีพื้นผิวซึ่งทำให้เกิดการสะท้อนแสงโดยรวม
อัพไซด์ที่นี่คือการที่คุณได้รับการตอบกลับหลายครั้ง (อย่างน้อยง่ายกว่าเมื่อรอการตัดบัญชี) คุณทำการเรนเดอร์วัตถุน้อยกว่าการเรนเดอร์ไปข้างหน้า แต่สิ่งสำคัญที่เลื่อนออกไปว่านี่เป็นเวลาที่ง่ายกว่าที่จะมีสมการแสงที่แตกต่างกันสำหรับพื้นผิวที่แตกต่างกัน
ด้วยการเรนเดอร์ที่เลื่อนออกไปคุณจะวาดฉากทั้งหมดด้วย shader ต่อแสงเดียวกัน ดังนั้นทุกวัตถุต้องใช้พารามิเตอร์วัสดุเดียวกัน ด้วยการส่องสว่างล่วงหน้าคุณสามารถทำให้วัตถุแต่ละชิ้นมี shader ที่แตกต่างกันดังนั้นจึงสามารถทำขั้นตอนการส่องสว่างขั้นสุดท้ายด้วยตนเอง
สิ่งนี้ไม่ได้ให้อิสระมากเท่ากับกรณีเรนเดอร์เรน แต่ก็ยังเร็วกว่านี้ถ้าคุณมีแบนด์วิธพื้นผิวว่างเปล่า
invariant
คำสำคัญสำหรับรับประกันในกรณีอื่น ๆ )
คุณต้องใช้การเรนเดอร์หรือการให้แสงล่วงหน้า ไพพ์ไลน์แบบเก่าที่มีฟังก์ชั่นคงที่บางส่วน (อ่านแล้ว: ไม่มีเฉดสี) รองรับไฟมากถึง 16 หรือ 24 ดวง - แต่นั่นก็คือ การเรนเดอร์แบบเลื่อนช่วยลดขีด จำกัด แสง แต่ด้วยราคาของระบบการเรนเดอร์ที่ซับซ้อนมากขึ้น
เห็นได้ชัดว่า WebGL รองรับ MRT ซึ่งเป็นสิ่งจำเป็นอย่างยิ่งสำหรับการเรนเดอร์รูปแบบใด ๆ - ดังนั้นจึงอาจทำได้ ฉันแค่ไม่แน่ใจว่ามันเป็นไปได้จริง
หรือคุณสามารถตรวจสอบความสามัคคี 5 - ซึ่งมีการเรนเดอร์ที่ถูกต้องอยู่นอกกรอบ
อีกวิธีที่ง่ายในการจัดการกับสิ่งนี้คือเพียงแค่จัดลำดับแสงไฟ (อาจขึ้นอยู่กับระยะห่างจากเครื่องเล่นและไม่ว่าจะอยู่ในกล้องถ่ายรูป frustum หรือไม่) และเปิดใช้อันดับ 8 เท่านั้น กับคุณภาพของเอาต์พุต (ตัวอย่างเช่น Far Cry 1)
คุณสามารถดูLightmaps ที่คำนวณล่วงหน้าได้ เกมอย่าง Quake 1 ได้รับไมล์สะสมจำนวนมากจากเกมเหล่านี้และอาจมีขนาดค่อนข้างเล็ก น่าเสียดายที่คำนวณล่วงหน้าไม่รวมความคิดของไฟแบบไดนามิก 100% แต่มันดูดีจริงๆ คุณสามารถรวมสิ่งนี้เข้ากับขีด จำกัด ของคุณได้ 8 แสงตัวอย่างเช่นจรวดหรือสิ่งนั้นจะมีแสงจริง - แต่ไฟบนผนังหรือเช่นนั้นจะเป็นแผนที่แสง
หมายเหตุด้านข้าง:คุณไม่ต้องการที่จะวนซ้ำพวกเขาใน Shader หรือไม่? บอกลาการแสดงของคุณ GPU ไม่ใช่ซีพียูและไม่ได้รับการออกแบบมาให้ทำงานเช่นเดียวกับที่ใช้ใน JavaScript โปรดจำไว้ว่าแต่ละพิกเซลที่คุณเรนเดอร์ (แม้ว่าจะถูกเขียนทับ) ต้องทำการวนซ้ำ - ดังนั้นถ้าคุณใช้ที่ 1920x1080 และลูปแบบธรรมดาที่รัน 16 ครั้งคุณจะทำงานทุกอย่างภายในลูป 33177600 ครั้งอย่างมีประสิทธิภาพ การ์ดกราฟิกของคุณจะทำงานชิ้นส่วนเหล่านั้นในแบบคู่ขนานจำนวนมากแต่ลูปเหล่านั้นยังคงกินฮาร์ดแวร์รุ่นเก่า
คุณสามารถใช้ Pixel Shader ที่รองรับแสงไฟ n (โดยที่ n คือจำนวนเล็ก ๆ เช่น 4 หรือ 8) และวาดฉากใหม่หลายครั้งผ่านชุดไฟใหม่ทุกครั้งและใช้การผสมเพิ่มเติมเพื่อรวมเข้าด้วยกัน
นั่นเป็นแนวคิดพื้นฐาน แน่นอนว่าต้องมีการปรับแต่งมากมายเพื่อให้เร็วพอสำหรับฉากที่มีขนาดพอสมควร อย่าวาดแสงทั้งหมดเพียง แต่แสงที่มองเห็นได้ (frustum และการบดเคี้ยว) อย่าวาดฉากทั้งหมดใหม่ในแต่ละรอบเพียงแค่วัตถุที่อยู่ในระยะของแสงในบัตรนั้น มี shader หลายรุ่นที่รองรับจำนวนไฟที่แตกต่างกัน (1, 2, 3, ... ) ดังนั้นคุณไม่ต้องเสียเวลาประเมินแสงมากกว่าที่คุณต้องการ
การเรนเดอร์ตามที่กล่าวไว้ในคำตอบอื่นเป็นทางเลือกที่ดีเมื่อคุณมีไฟเล็ก ๆ มากมาย แต่ไม่ใช่วิธีเดียว