วัตถุจำนวนมากในสาม js มีคุณสมบัติที่ต้องการเพิ่มและพวกเขาไม่ค่อยเขียนในเอกสาร (แต่มีเอกสารไม่มากนักในสาม js และปัญหามากมายยังคงต้องพึ่งพาปัญหาเกี่ยวกับ GitHub) พวกเขาไม่ทราบวิธีการเขียนสิ่งนี้ในบทช่วยสอนต่าง ๆ ออนไลน์เพราะสำหรับโปรแกรมเบื้องต้นที่เรียบง่ายไม่สามารถใช้คุณสมบัตินี้ได้
คุณลักษณะนี้ใช้ทำอะไร? สรุปฉันบอกผู้แสดงผลว่าฉันควรอัปเดตแคชในเฟรมนี้ แม้ว่ามันจะใช้งานง่ายมากเป็นบิตตั้งค่าสถานะเพราะคุณจำเป็นต้องรู้ว่าทำไมคุณต้องอัปเดตแคชและแคชที่จะอัปเดต แต่ก็ยังจำเป็นต้องเข้าใจอย่างรอบคอบ
ทำไมต้องใช้ประโยชน์ก่อนอื่นมาดูกันว่าทำไมต้องใช้แคช การมีอยู่ของแคชโดยทั่วไปคือการลดจำนวนเวลาการส่งข้อมูลซึ่งจะช่วยลดเวลาที่ใช้ในการส่งข้อมูล ที่นี่มันเป็นความจริงที่ว่ามันไม่ใช่เรื่องง่ายสำหรับวัตถุ (ตาข่าย) ที่จะแสดงบนหน้าจอได้สำเร็จในที่สุด มันจะต้องถูกโอนไปยังสนามรบสามครั้ง
สิ่งแรกคือการอ่านข้อมูลจุดสุดยอดและข้อมูลพื้นผิวทั้งหมดจากดิสก์ท้องถิ่นลงในหน่วยความจำผ่านโปรแกรม
จากนั้นหลังจากโปรแกรมได้ทำการประมวลผลที่เหมาะสมในหน่วยความจำมันจำเป็นต้องถ่ายโอนข้อมูลจุดสุดยอดและข้อมูลพื้นผิวของวัตถุที่จำเป็นต้องถูกดึงไปยังหน้าจอไปยังหน่วยความจำวิดีโอ
ในที่สุดเมื่อแสดงผลแต่ละเฟรมข้อมูลจุดสุดยอดและข้อมูลพื้นผิวในหน่วยความจำวิดีโอจะถูกล้างเข้าไปใน GPU สำหรับการประกอบและการวาดภาพ
ตามรูปแบบการส่งข้อมูลคล้ายปิรามิดขั้นตอนแรกนั้นช้าที่สุด หากมีการส่งผ่านเครือข่ายในสภาพแวดล้อมเช่น WebGL มันจะช้าลง ประการที่สองคือเวลาจากหน่วยความจำไปจนถึงหน่วยความจำวิดีโอซึ่งจะเป็นการทดสอบข้อมูลอย่างง่ายในภายหลัง
จากนั้นมีความถี่ในการใช้งานสามขั้นตอนเหล่านี้ สำหรับสถานการณ์ขนาดเล็กขั้นตอนแรกคือครั้งเดียวนั่นคือทุกครั้งที่โปรแกรมเริ่มต้นข้อมูลทั้งหมดของสถานการณ์จะถูกโหลดลงในหน่วยความจำ สำหรับสถานการณ์ขนาดใหญ่การโหลดแบบอะซิงโครนัสบางอย่างอาจทำได้ แต่ในปัจจุบันยังไม่เป็นปัญหาที่เรากำลังพิจารณา ความถี่ของขั้นตอนที่สองควรเป็นสิ่งที่สำคัญที่สุดที่จะพูดคุยเกี่ยวกับเวลานี้ ก่อนอื่นให้เขียนโปรแกรมง่าย ๆ เพื่อทดสอบการบริโภคที่เกิดจากการทำขั้นตอนการส่งนี้
var canvas = document.createElement ('Canvas');
var _gl = canvas.getContext ('Experimental-Webgl');
var vertices = [];
สำหรับ (var i = 0; i <1,000*3; i ++) {
Vertices.push (i * math.random ());
-
var buffer = _gl.createBuffer ();
console.profile ('buffer_test');
BindBuffer ();
console.profileend ('buffer_test');
ฟังก์ชั่น bindbuffer () {
สำหรับ (var i = 0; i <1000; i ++) {
_gl.bindbuffer (_gl.array_buffer, บัฟเฟอร์);
_gl.bufferdata (_gl.array_buffer, Float32Array ใหม่ (จุดยอด), _gl.static_draw);
-
-
มาอธิบายโปรแกรมนี้ก่อน จุดยอดเป็นอาร์เรย์ที่บันทึกจุดยอด ที่นี่มีการสร้างจุดยอด 1,000 จุด เนื่องจากจุดสุดยอดแต่ละอันมีพิกัดสามพิกัด x, y และ z จึงจำเป็นต้องมีอาร์เรย์ขนาด 3000 คำสั่ง _gl.createBuffer เปิดแคชสำหรับการจัดเก็บข้อมูลจุดสุดยอดในหน่วยความจำวิดีโอจากนั้นใช้ _gl.bufferdata เพื่อถ่ายโอนข้อมูลจุดสุดยอดที่สร้างขึ้นจากหน่วยความจำไปยังหน่วยความจำวิดีโอ ที่นี่เราคิดว่ามี 1,000 วัตถุที่มี 1,000 จุดยอดในฉากแต่ละจุดยอดคือ 3 32 บิต 4 ไบต์ของข้อมูลลอยตัว คำนวณข้อมูลเกือบ 1,000 x 1,000 x 12 = 11m โปรไฟล์ใช้เวลาประมาณ 15ms ที่นี่เราอาจเห็นว่า 15ms เป็นเพียงเวลาเพียงเล็กน้อย อย่างไรก็ตามสำหรับโปรแกรมแบบเรียลไทม์หากคุณต้องการให้แน่ใจว่าอัตราเฟรม 30fps เวลาที่ต้องใช้สำหรับแต่ละเฟรมจะต้องควบคุมที่ประมาณ 30ms จะใช้เวลาครึ่งหนึ่งในการส่งข้อมูลได้อย่างไร คุณควรรู้ว่าหัวใหญ่ควรเป็นการดำเนินการวาดภาพใน GPU และการประมวลผลต่าง ๆ ใน CPU และคุณควรจะตระหนี่กับทุกขั้นตอนของการดำเนินการในกระบวนการแสดงผลทั้งหมด
ดังนั้นจำนวนการส่งสัญญาณในขั้นตอนนี้ควรลดลง ในความเป็นจริงมันสามารถใช้ในการถ่ายโอนข้อมูลจุดสุดยอดและข้อมูลพื้นผิวทั้งหมดจากหน่วยความจำไปยังหน่วยความจำวิดีโอเมื่อโหลด นี่คือสิ่งที่ Three.js ทำในขณะนี้ ข้อมูลจุดสุดยอดของวัตถุที่จำเป็นต้องถูกถ่ายโอนไปยังหน่วยความจำวิดีโอเป็นครั้งแรกและแคชบัฟเฟอร์เป็นเรขาคณิต .__ WebGlvertexbuffer หลังจากนั้นทุกครั้งที่คุณวาดคุณจะตัดสินทรัพย์สินของ VerticesNeedUpdate ของเรขาคณิต หากคุณไม่จำเป็นต้องอัปเดตให้ใช้แคชปัจจุบันโดยตรง หากคุณเห็นว่า VerticesNeedUpate เป็นจริงข้อมูลจุดสุดยอดในรูปทรงเรขาคณิตจะถูกถ่ายโอนไปยังเรขาคณิต .__ WebGlvertexBuffer โดยทั่วไปเราไม่ต้องการขั้นตอนนี้สำหรับวัตถุคงที่ อย่างไรก็ตามหากเราพบวัตถุที่เปลี่ยนแปลงบ่อยครั้งเช่นการใช้จุดยอดเป็นระบบอนุภาคและตาข่ายที่ใช้ภาพเคลื่อนไหวโครงกระดูกวัตถุเหล่านี้จะเปลี่ยนจุดยอดในแต่ละเฟรมดังนั้นแต่ละเฟรมจะต้องตั้งค่าคุณสมบัติจุดยอด
ในความเป็นจริงในโปรแกรม WebGL จะมีการเปลี่ยนแปลงตำแหน่งจุดสุดยอดมากขึ้นใน Vertex Shader เพื่อให้ได้เอฟเฟกต์อนุภาคและภาพเคลื่อนไหวโครงกระดูก แม้ว่ามันจะง่ายกว่าที่จะขยายถ้าวางอยู่ทางด้าน CPU เพื่อคำนวณเนื่องจากข้อ จำกัด ของกำลังการคำนวณของ JavaScript การดำเนินการคำนวณเหล่านี้จะถูกวางไว้ที่ด้าน GPU มากขึ้น ในกรณีนี้ไม่จำเป็นต้องส่งข้อมูลจุดสุดยอดใหม่ดังนั้นกรณีข้างต้นจึงไม่ได้ใช้มากในโปรแกรมจริงและเป็นเพิ่มเติมเกี่ยวกับการอัปเดตพื้นผิวและแคชวัสดุ
กรณีข้างต้นส่วนใหญ่จะอธิบายสถานการณ์ที่ส่งข้อมูลจุดสุดยอด นอกเหนือจากข้อมูลจุดสุดยอดแล้วยังมีหัวใหญ่ที่เป็นพื้นผิว พื้นผิวรูปแบบ R8G8B8A8 ขนาด 1024*1024 ต้องใช้ขนาดหน่วยความจำสูงสุด 4M ดังนั้นดูตัวอย่างต่อไปนี้
var canvas = document.createElement ('Canvas');
var _gl = canvas.getContext ('Experimental-Webgl');
var texture = _gl.createTexture ();
var img = ภาพใหม่;
img.onload = function () {
console.profile ('การทดสอบพื้นผิว');
BindTexture ();
console.profileend ('การทดสอบพื้นผิว');
-
img.src = 'test_tex.jpg';
ฟังก์ชั่น BindTexture () {
_gl.bindTexture (_gl.texture_2d, พื้นผิว);
_gl.teximage2d (_gl.texture_2d, 0, _gl.rgba, _gl.rgba, _gl.unsigned_byte, img);
-
ไม่จำเป็นต้องทำซ้ำ 1,000 ครั้งในทางที่ผิด ใช้เวลา 30 มม. ในการส่งพื้นผิวของ 10241024 ในแต่ละครั้งและภาพ 256256 เกือบ 2ms ดังนั้นในสาม js พื้นผิวควรถูกส่งเพียงครั้งเดียวที่จุดเริ่มต้นเท่านั้น หลังจากนั้นถ้า texture.eedsupdate คุณสมบัติไม่ได้ตั้งค่าเป็นจริงด้วยตนเองพื้นผิวที่ถูกถ่ายโอนไปยังหน่วยความจำวิดีโอจะถูกใช้โดยตรง
ต้องอัปเดตแคชอะไรข้างต้นอธิบายว่าทำไมสาม.jsต้องเพิ่มแอตทริบิวต์ความต้องการดังกล่าวผ่านสองกรณี ถัดไปแสดงรายการหลายสถานการณ์เพื่อทราบภายใต้สถานการณ์ที่คุณต้องอัปเดตแคชเหล่านี้ด้วยตนเอง
การโหลดพื้นผิวแบบอะซิงโครนัสนี่เป็นหลุมเล็ก ๆ เพราะภาพส่วนหน้าถูกโหลดแบบอะซิงโครนัส หากคุณเขียน texture.needsupdate = TRUE โดยตรงหลังจากสร้าง IMG ตัวเรนเดอร์สาม.JSจะใช้ _gl.teximage2d เพื่อถ่ายโอนข้อมูลพื้นผิวที่ว่างเปล่าไปยังหน่วยความจำวิดีโอในเฟรมนี้แล้วตั้งค่าสถานะนี้เป็นเท็จ จากนั้นเมื่อมีการโหลดภาพข้อมูลหน่วยความจำวิดีโอจะไม่ได้รับการอัปเดต ดังนั้นคุณต้องรอให้โหลดภาพทั้งหมดในเหตุการณ์ OnLoad ก่อนที่จะเขียน sexture.eedsUpdate = จริง
พื้นผิววิดีโอพื้นผิวส่วนใหญ่เป็นเหมือนกรณีด้านบนเพื่อโหลดและส่งรูปภาพโดยตรง แต่ไม่ใช่สำหรับพื้นผิววิดีโอเนื่องจากวิดีโอเป็นสตรีมรูปภาพและรูปภาพที่จะแสดงในแต่ละเฟรมนั้นแตกต่างกันดังนั้นคุณต้องตั้งค่าความต้องการให้เป็นจริงสำหรับแต่ละเฟรมเพื่ออัปเดตข้อมูลพื้นผิวในการ์ดกราฟิก
ใช้บัฟเฟอร์เรนเดอร์บัฟเฟอร์เรนเดอร์เป็นวัตถุที่ค่อนข้างพิเศษ โดยทั่วไปโปรแกรมจะล้างหน้าจอโดยตรงหลังจากวาดฉากทั้งหมด อย่างไรก็ตามหากมีการประมวลผลโพสต์เพิ่มเติมหรือ XXX บนหน้าจอ (เช่นการเกิดขึ้นโดยรอบหน้าจอ) ฉากจะต้องถูกวาดขึ้นก่อนในบัฟเฟอร์เรนเดอร์ บัฟเฟอร์นี้เป็นพื้นผิวจริง ๆ แต่มันถูกสร้างขึ้นโดยภาพวาดก่อนหน้าไม่โหลดจากดิสก์ มีวัตถุพื้นผิวพิเศษ webGlRenderTarget ในสาม.jsเพื่อเริ่มต้นและบันทึก RenderBuffer พื้นผิวนี้ยังต้องตั้งค่าเป็นจริงในแต่ละเฟรม
ความต้องการของวัสดุวัสดุที่อธิบายไว้ในสาม js ถึงสามวัสดุ ในความเป็นจริงวัสดุไม่ได้มีการส่งข้อมูลมากนัก แต่ทำไมคุณต้องสร้างความต้องการ ที่นี่ฉันจะพูดถึง Shader Shader ได้รับการแปลเป็น shader ซึ่งให้ความเป็นไปได้ของการเขียนโปรแกรมจุดยอดและพิกเซลใน GPU มีคำศัพท์แรเงาในการวาดภาพเพื่อแสดงถึงวิธีการวาดภาพแสงและมืด การแรเงาใน GPU นั้นคล้ายคลึงกัน แสงและความมืดของแสงถูกคำนวณโดยโปรแกรมเพื่อแสดงวัสดุของวัตถุ ตกลงเนื่องจาก Shader เป็นโปรแกรมที่ทำงานบน GPU เช่นเดียวกับโปรแกรมทั้งหมดจึงจำเป็นต้องทำการรวบรวมและเชื่อมโยงการดำเนินการ ใน WebGL โปรแกรม Shader ถูกรวบรวมเมื่อรันไทม์ซึ่งแน่นอนว่าต้องใช้เวลาดังนั้นจึงเป็นการดีที่สุดที่จะรวบรวมและทำงานจนกว่าจะสิ้นสุดโปรแกรม ดังนั้นเมื่อวัสดุเริ่มต้นในสาม .Js โปรแกรม shader จะถูกรวบรวมและเชื่อมโยงและวัตถุโปรแกรมที่ได้รับหลังจากการรวบรวมลิงค์ถูกแคช โดยทั่วไปวัสดุไม่จำเป็นต้องคอมไพล์ใหม่ทั้งหมดอีกต่อไป ในการปรับวัสดุคุณจะต้องปรับเปลี่ยนพารามิเตอร์ที่สม่ำเสมอของ shader อย่างไรก็ตามหากคุณแทนที่วัสดุทั้งหมดเช่นการแทนที่ pong shader ดั้งเดิมด้วย Lambert Shader คุณต้องตั้งค่าวัสดุจำเป็นต้องใช้เป็นจริงเพื่อคอมไพล์อีกครั้ง อย่างไรก็ตามสถานการณ์นี้หายากและสถานการณ์ที่พบบ่อยคือสถานการณ์ที่กล่าวถึงด้านล่าง
เพิ่มและถอดไฟสิ่งนี้ควรเป็นเรื่องธรรมดามากขึ้นในฉาก บางทีหลายคนที่เพิ่งเริ่มใช้สาม js จะตกอยู่ในหลุมนี้ หลังจากเพิ่มแสงไปยังฉากแบบไดนามิกพวกเขาพบว่าแสงไม่ทำงาน อย่างไรก็ตามเมื่อใช้ shader ที่สร้างขึ้นใน Three.js ตัวอย่างเช่น Phong, Lambert, ดูซอร์สโค้ดใน Renderer คุณจะพบว่า Three.js ใช้ #Define ในรหัส shader ในตัวเพื่อตั้งค่าไฟในฉาก ค่าของ #define นี้ได้มาจากการประกบของสตริงทุกครั้งที่มีการอัปเดตวัสดุ รหัสมีดังนี้
"#define max_dir_lights" + parameters.maxdirlights,
"#define max_point_lights" + parameters.maxpointlights,
"#define max_spot_lights" + parameters.maxspotlights,
"#define max_hemi_lights" + พารามิเตอร์. maxhemilights,
อันที่จริงวิธีการเขียนนี้สามารถลดการใช้การลงทะเบียน GPU ได้อย่างมีประสิทธิภาพ หากมีเพียงแสงเดียวคุณสามารถประกาศเฉพาะตัวแปรเครื่องแบบที่จำเป็นสำหรับแสงเดียว อย่างไรก็ตามเมื่อจำนวนไฟเปลี่ยนแปลงโดยเฉพาะอย่างยิ่งเมื่อเพิ่มคุณจะต้องติดตั้งและรวบรวมและเชื่อมโยง shader อีกครั้ง ในเวลานี้คุณต้องตั้งค่าวัสดุจำเป็นต้องใช้วัสดุทั้งหมดเป็นจริง
เปลี่ยนพื้นผิวการเปลี่ยนแปลงพื้นผิวที่นี่ไม่ได้หมายถึงการอัปเดตข้อมูลพื้นผิว แต่วัสดุต้นฉบับใช้พื้นผิว แต่ไม่ได้ใช้ในภายหลังหรือวัสดุดั้งเดิมไม่ได้ใช้พื้นผิวแล้วเพิ่ม หากวัสดุไม่ได้รับการปรับปรุงด้วยตนเองเอฟเฟกต์สุดท้ายจะแตกต่างจากสิ่งที่คุณคิด เหตุผลสำหรับปัญหานี้คล้ายกับแสงที่กล่าวถึงข้างต้นและเป็นเพราะแมโครถูกเพิ่มเข้าไปใน shader เพื่อตรวจสอบว่ามีการใช้พื้นผิวหรือไม่
parameters.map? "#define use_map": "",
parameters.envmap? "#define use_envmap": "",
Parameters.lightMap? "#define use_lightmap": "",
parameters.bumpmap? "#define use_bumpmap": "",
พารามิเตอร์ NormalMap? "#define use_normalmap": "",
พารามิเตอร์ specularmap? "#define use_specularmap": "",
ดังนั้นทุกครั้งที่แผนที่ envmap หรือ lightmap เปลี่ยนค่าที่แท้จริงคุณต้องอัปเดตวัสดุ
การเปลี่ยนแปลงข้อมูลจุดสุดยอดอื่น ๆในความเป็นจริงการเปลี่ยนแปลงพื้นผิวข้างต้นจะสร้างปัญหา ส่วนใหญ่เป็นเพราะไม่มีพื้นผิวในระหว่างการเริ่มต้น อย่างไรก็ตามในสภาพแวดล้อมนี้ที่เพิ่มเข้ามาแบบไดนามิกมันไม่เพียงพอที่จะตั้งค่าวัสดุจำเป็นต้องใช้เป็นจริง นอกจากนี้ยังจำเป็นต้องตั้งค่าเรขาคณิต uvsneedsupdate เป็นจริง ทำไมถึงมีปัญหาเช่นนี้? เป็นเพราะการเพิ่มประสิทธิภาพของโปรแกรมโดยสาม js เมื่อเริ่มต้นรูปทรงเรขาคณิตและวัสดุเป็นครั้งแรกใน Renderer หากมีการตัดสินว่าไม่มีพื้นผิวแม้ว่าจะมีข้อมูล UV จุดสุดยอดในข้อมูลในหน่วยความจำสาม.JSจะไม่คัดลอกข้อมูลนี้ลงในหน่วยความจำวิดีโอ ความตั้งใจดั้งเดิมควรบันทึกพื้นที่หน่วยความจำวิดีโอที่มีค่า อย่างไรก็ตามหลังจากเพิ่มพื้นผิวเรขาคณิตจะไม่ถ่ายโอนข้อมูล UV เหล่านี้อย่างชาญฉลาดสำหรับการใช้พื้นผิว เราต้องตั้งค่า UVSNeedSupdate ด้วยตนเองเพื่อแจ้งว่าถึงเวลาอัปเดต UV คำถามนี้ทำให้ฉันโกงเป็นเวลานานในตอนแรก
สำหรับแอตทริบิวต์ NeedUpdate ของข้อมูลจุดสุดยอดหลายประเภทคุณสามารถดูปัญหานี้ได้
https://github.com/mrdoob/three.js/wiki/updates
ในที่สุดการเพิ่มประสิทธิภาพของ Three.js ทำงานได้ดี แต่มันนำมาซึ่งข้อผิดพลาดต่าง ๆ ที่อาจได้รับการปรับให้เหมาะสม วิธีที่ดีที่สุดในการทำเช่นนี้คือดูที่ซอร์สโค้ดหรือไปที่ GitHub เพื่อพูดถึงปัญหา