แสงจำนวนมากที่มีเงาใน three.js ทำให้เกิดข้อผิดพลาด Fragment shader

สมมติว่ามีฉากหนึ่งที่มีไฟถนนหลายดวง (มากกว่า 20 ดวง) คุณเคลื่อนวัตถุเข้ามาใกล้และคาดว่าจะมีเงา

ไฟง่ายๆ

var light = new THREE.PointLight(0xffffff, 0.5, 6.0);

มีเพียงถนนเท่านั้นที่มี .receiveShadow = true และมีเพียงรถเท่านั้นที่มี .castShadow = true (นอกเหนือจากไฟในเวลาต่อมา)

ตัวอย่าง

ใน three.js การเพิ่ม .castShadow = true ให้กับไฟทั้งหมดทำให้เกิดข้อผิดพลาดดังต่อไปนี้

THREE.WebGLProgram: shader error:  0 gl.VALIDATE_STATUS false 
gl.getProgramInfoLog Fragment shader sampler count exceeds MAX_TEXTURE_IMAGE_UNITS (16).

โชคดีที่ฉากในชั่วโมงนี้เราต้องการเพียงไม่กี่ฉาก (สูงสุด 4 ภาพ) เพื่อสร้างเงา เนื่องจากแสงส่วนใหญ่อยู่ไม่ไกลเกินเอื้อม

ผมลองใช้ 2 วิธี

  1. วนผ่านไฟทั้งหมดและตั้งค่า .castShadow = true หรือ .castShadow = false แบบไดนามิก

  2. เพิ่มและถอดไฟออกให้หมดแต่ตั้งให้ไม่มีเงาหรือเงา

ฉันได้รับข้อผิดพลาดเดียวกันกับทั้งสองคน

แนวทางอื่นใดที่จะได้ผล?

อัปเดต

@neeh สร้าง Fiddle สำหรับมัน ที่นี่ (เพื่อให้ข้อผิดพลาดเปลี่ยน var numLightRows = 8; เป็นตัวเลขที่สูงกว่า) โปรดจับตาดูข้อผิดพลาด เพราะจะมีข้อผิดพลาดอื่นเกิดขึ้นเมื่อมีไฟมากเกินไปซึ่งไม่ได้เกิดจากปัญหาเดียวกัน

นอกจากนี้เขายังชี้ให้เห็นว่าเราเห็น ที่นี่ ที่ pointShadowMap ถูกสร้างขึ้นแม้ว่าจะไม่ได้ใช้งานก็ตาม สิ่งนี้อธิบายได้ว่าเหตุใดจึงไม่มีการเปลี่ยนแปลงด้วยแนวทางที่ "ชาญฉลาดกว่า" ขณะนี้อยู่ภายในโค้ด GLSL

ดังนั้นเราจึงถูกจำกัดโดย GPU ซึ่งในกรณีของฉันมี 16 IMAGE_UNITS แต่นั่นไม่ใช่กรณีของ GPU ทั้งหมด (CPU ของฉันทำงานได้ดีกับมากกว่านั้น) คุณสามารถตรวจสอบระบบของคุณด้วย renderer.capabilities.maxTextures แต่ดังที่กล่าวไปแล้วเราต้องการเพียง 4 เท่านั้น

ปัญหายังคงอยู่


person arc    schedule 18.03.2017    source แหล่งที่มา
comment
ฉันจะไม่ใช้ scene.remove( light ) และ scene.add( light ) แต่สำรวจฉากเพื่อหาแสงสว่าง ลบเงาออก และในส่วนที่อยู่ใกล้รถจะเปิดไฟ   -  person gaitat    schedule 19.03.2017
comment
@gaitat ฉันลองทั้งสองอย่างและน่าเสียดายที่ทางเลือกทั้งสองล้มเหลว   -  person arc    schedule 19.03.2017


คำตอบ (1)


ปัญหา

ใช่ a href="https://github.com/mrdoob/three.js/blob/dev/src/renderers/webgl/WebGLShadowMap.js#L190" rel="nofollow noreferrer">ใหม่ แผนที่เงาจะถูกสร้างขึ้นสำหรับทุกแสงที่มี castShadow = true (จริงๆ แล้ว ไม่เป็นเช่นนั้น ให้ตรวจสอบ ปัญหานี้) แผนที่เงาคือการวาดโดยใช้เงาเพื่อนำมาผสมลงบนพื้นผิวในภายหลัง

gl.getProgramInfoLog จำนวนตัวอย่างตัวเชเดอร์ของแฟรกเมนต์เกิน MAX_TEXTURE_IMAGE_UNITS (16)

หมายความว่าอุปกรณ์ของคุณสามารถส่งได้ไม่เกิน 16 พื้นผิวต่อการดึงสาย โดยปกติแล้ว รถ (ถนน?) ที่คุณต้องการวางเงาไว้คือ 1 Draw Call

หากต้องการวาดวัตถุที่ได้รับเงา แผนที่เงาทั้งหมดควรผสมร่วมกับแผนที่แบบกระจาย ดังนั้นสิ่งนี้จึงจำเป็นต้องใช้หน่วยพื้นผิว N+1 สำหรับการเรียกแบบดึงครั้งเดียว (N คือจำนวนไฟที่ทำให้เกิดเงาได้)

หากคุณเจาะลึกเข้าไปใน Three.js shaders คุณจะพบ สิ่งนี้ :

#ifdef USE_SHADOWMAP

    #if NUM_DIR_LIGHTS > 0

        // Reserving NUM_DIR_LIGHTS texture units
        uniform sampler2D directionalShadowMap[ NUM_DIR_LIGHTS ];
        varying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];

    #endif

    ...

#endif

ตรวจสอบเครื่องมือนี้ เพื่อดูว่าเบราว์เซอร์ของคุณสามารถจัดการหน่วยพื้นผิวได้มากเพียงใด (ตัวเชเดอร์แฟรกเมนต์ > หน่วยภาพพื้นผิวสูงสุด)


การแก้ไขปัญหา ?

การสร้างและการลบไฟแบบไดนามิกนั้นไม่ดีเนื่องจากต้องใช้หน่วยความจำมาก (การจัดสรรแผนที่เงา...)

แต่อย่างที่ gaitat กล่าว คุณสามารถเปิดใช้เงาได้เฉพาะแสงที่ใกล้ที่สุด เพียงทำสิ่งต่อไปนี้ในลูปการเรนเดอร์ของคุณ:

  1. ปิดการใช้งานเงาทั้งหมด: light.castShadow = false;
  2. ค้นหาไฟที่ใกล้ที่สุด
  3. เปิดใช้งานเงาสำหรับไฟที่ใกล้ที่สุด N: light.castShadow = true;

การปรับปรุง

อัลกอริธึมโดดเดี่ยวนี้ไม่ดีเพราะมันจัดสรรหนึ่งแผนที่เงาต่อแสง นอกจากจะใช้หน่วยความจำแล้ว การเรนเดอร์จะหยุดเล็กน้อยทุกครั้งที่คุณข้ามแสงใหม่ที่ไม่ได้จัดสรรแผนที่เงา...

ดังนั้น แนวคิดก็คือใช้ซ้ำแผนที่เงาเดิมสำหรับแสงที่ใกล้ที่สุด คุณสามารถจัดการกับแผนที่เงาได้ดังนี้:

// create a new shadow map
var shadowMapCamera = new THREE.PerspectiveCamera(90, 1, 0.5, 500);
var shadow = new THREE.LightShadow(shadowMapCamera);

// use the shadow map on a light
light.shadow = shadow;
shadow.camera.position.copy(light.position);
light.castShadow = true;

คุณสามารถรับจำนวนหน่วยพื้นผิวสูงสุดได้ด้วย renderer.capabilities.maxTextures ดังนั้นคุณจึงสามารถคำนวณจำนวนแผนที่เงาเพื่อสร้างตามแผนที่นั้นได้ แต่อย่าลืมเหลือบางส่วนไว้สำหรับแผนที่ทั่วไป เช่น diffuseMap, NormalMap...

ป้อนคำอธิบายรูปภาพที่นี่

ลองดูซอนี้เพื่อการใช้งานเต็มรูปแบบ (ใช้แผนที่เงาเพียง 4 รายการเท่านั้น)

person neeh    schedule 19.03.2017
comment
ขอบคุณสำหรับคำตอบครับ ทำให้ผมมีความเข้าใจมากขึ้น น่าเสียดาย มันไม่ได้แก้ปัญหา การเพิ่ม numLightRows ทำให้เกิดข้อผิดพลาดอีกครั้ง ดูเหมือนแปลก คุณสามารถเพิ่มไฟได้มากเท่าที่คุณต้องการโดยไม่มีปัญหา แต่การเพิ่มแสงเงาเพียงดวงเดียวก็จะทำให้ไฟดับได้ - person arc; 19.03.2017
comment
น่าแปลก ฉันไม่มีข้อผิดพลาดนี้ แม้ว่าจะมีไฟ 100 ดวงและ maxTextures = 32 ก็ตาม จริงๆ แล้วฉันได้รับข้อผิดพลาดอีกอย่างหนึ่ง แต่นั่นเป็นเพราะฉันมีแสงมากเกินไปในฉากของฉัน คุณแน่ใจหรือว่าเป็นข้อผิดพลาดเดียวกันทุกประการ - person neeh; 19.03.2017
comment
ใช่ เกิดข้อผิดพลาดเดียวกันกับไฟสูงสุด 29 ดวง หลังจากที่ฉันได้รับข้อผิดพลาดเดียวกันกับในโน้ตบุ๊กของฉัน: Varyings over maximum register limit แต่ฉันรู้ว่านี่เป็นขีดจำกัดของฮาร์ดแวร์ ดังนั้นจึงไม่ต้องทำอะไรที่นั่น ฉันมี EVGA 970 เป็นเรื่องที่น่ากังวล - person arc; 19.03.2017
comment
ดังนั้นข้อผิดพลาด Fragment shader sampler count exceeds MAX_TEXTURE_IMAGE_UNITS (16) ยังคงเริ่มต้นด้วยไฟ 16 ดวงใช่ไหม นั่นก็หมายความว่ามีแผนต่อแสงอีกแห่งอยู่ที่ไหนสักแห่ง - person neeh; 19.03.2017
comment
ฉันคิดว่าฉันมีแนวคิดอื่นที่อาจใช้ได้กับไฟจำนวนเท่าใดก็ได้ หากฉันทำสำเร็จฉันจะเขียนคำตอบใหม่ - person neeh; 19.03.2017
comment
อาจจะเป็น. โดยพื้นฐานแล้ว ฉันสามารถเพิ่มไฟได้มากเท่าที่ต้องการ แต่พอมีเงาก็พัง แน่นอน ดีใจที่ได้รับความช่วยเหลือที่นี่ - person arc; 19.03.2017
comment
ใช่ เพิ่งรู้ว่า... ฉันไม่เข้าใจว่าทำไม - person neeh; 19.03.2017
comment
โอ้ฉันเข้าใจแล้ว! เพียงดูโค้ดที่ฉันวาง renderer.shadowMap.enabled = true; เปิดใช้งาน USE_SHADOWMAP จากนั้นเราจะจัดสรร NUM_POINTS_LIGHTS ที่แตกต่างกัน! ดังนั้นเอ็นจิ้นไม่สนใจด้วยซ้ำว่าเราจะส่งแผนที่เงาไปกี่แผนที่... อย่างไรก็ตาม ฉันมีความคิดใหม่ และฉันจะลบคำตอบนี้ออกไป - person neeh; 19.03.2017