เกี่ยวกับ Java vs C ++ ฉันได้เขียนเอ็นจิ้น voxel ทั้งคู่ (เวอร์ชั่น C ++ แสดงไว้ด้านบน) ฉันเคยเขียนเครื่องยนต์ voxel มาตั้งแต่ปี 2004 (ตอนที่พวกเขาไม่ทันสมัย) :) ฉันสามารถพูดด้วยความลังเลเล็กน้อยว่าประสิทธิภาพของ C ++ นั้นเหนือกว่ามาก (แต่ก็ยากที่จะเขียนโค้ด) มันเกี่ยวกับความเร็วในการคำนวณน้อยกว่าและเกี่ยวกับการจัดการหน่วยความจำ เมื่อคุณจัดสรร / ยกเลิกการจัดสรรข้อมูลให้มากที่สุดในโลก voxel C (++) คือภาษาที่จะเอาชนะ อย่างไรก็ตามคุณควรคิดถึงเป้าหมายของคุณ หากประสิทธิภาพเป็นสิ่งที่สำคัญที่สุดของคุณให้ไปกับ C ++ หากคุณเพียงต้องการเขียนเกมที่ไม่มีประสิทธิภาพออกมาอย่างเด็ดขาด Java นั้นเป็นที่ยอมรับอย่างแน่นอน มีกรณีเล็ก ๆ น้อย ๆ / ขอบ แต่โดยทั่วไปคุณสามารถคาดหวังให้ Java ทำงานประมาณ 1.75-2.0 เท่าช้ากว่า (เขียนได้ดี) C ++ คุณสามารถดูการทำงานของเครื่องยนต์รุ่นเก่าที่ได้รับการปรับปรุงประสิทธิภาพได้ไม่ดีที่นี่ (แก้ไข: รุ่นที่ใหม่กว่าที่นี่ ) ในขณะที่การสร้างชิ้นข้อมูลอาจดูช้า แต่โปรดจำไว้ว่ามันกำลังสร้างไดอะแกรม 3D voronoi แบบปริมาตรการคำนวณหาค่าพื้นฐานของพื้นผิวแสง AO และเงาบน CPU ด้วยวิธีการบังคับแบบเดรัจฉาน ฉันได้ลองใช้เทคนิคที่หลากหลายและฉันสามารถสร้างก้อนที่เร็วขึ้นประมาณ 100 เท่าโดยใช้เทคนิคการแคชและการอินสแตนซ์ที่หลากหลาย
- เก็บเอาไว้. ไม่ว่าคุณจะทำได้ที่ไหนคุณควรคำนวณข้อมูลหนึ่งครั้ง ตัวอย่างเช่นฉันอบแสงไฟเข้าฉาก มันสามารถใช้แสงแบบไดนามิก (ในพื้นที่หน้าจอเป็นกระบวนการหลัง) แต่การอบในแสงหมายความว่าฉันไม่ต้องผ่านในบรรทัดฐานสำหรับรูปสามเหลี่ยมซึ่งหมายความว่า ....
ส่งข้อมูลน้อยไปยังการ์ดวิดีโอให้มากที่สุด สิ่งหนึ่งที่คนมักจะลืมคือยิ่งคุณส่งข้อมูลผ่าน GPU มากเท่าไหร่ก็ยิ่งมีเวลามากขึ้นเท่านั้น ฉันผ่านสีเดียวและตำแหน่งจุดสุดยอด ถ้าฉันต้องการที่จะทำรอบกลางวัน / กลางคืนฉันสามารถทำระดับสีหรือฉันสามารถคำนวณฉากที่ดวงอาทิตย์ค่อย ๆ เปลี่ยน
เนื่องจากการส่งข้อมูลไปยัง GPU นั้นมีราคาแพงมากจึงเป็นไปได้ที่จะเขียนเอ็นจิ้นในซอฟต์แวร์ซึ่งเร็วกว่าในบางประเด็น ข้อดีของซอฟต์แวร์คือสามารถเข้าถึงการจัดการข้อมูล / หน่วยความจำทุกชนิดที่ไม่สามารถทำได้บน GPU
เล่นกับขนาดแบทช์ หากคุณใช้ GPU ประสิทธิภาพอาจแตกต่างกันไปอย่างมากโดยขึ้นอยู่กับว่าแต่ละจุดยอดที่คุณผ่านนั้นมีขนาดใหญ่เพียงใด ดังนั้นเล่นกับขนาดของชิ้น (ถ้าคุณใช้ชิ้น) ฉันพบว่าชิ้น 64x64x64 ทำงานได้ดีทีเดียว ไม่ว่าอะไรก็ตามจงเก็บลูกบาศก์ของคุณให้เป็นลูกบาศก์ (ไม่มีปริซึมสี่เหลี่ยม) สิ่งนี้จะทำให้การเข้ารหัสและการดำเนินการต่าง ๆ (เช่นการเปลี่ยนแปลง) ง่ายขึ้นและในบางกรณีมีประสิทธิภาพมากขึ้น หากคุณเก็บค่าเดียวสำหรับความยาวของทุกมิติโปรดทราบว่าการลงทะเบียนน้อยกว่าสองครั้งที่สลับไปมาระหว่างการคำนวณ
พิจารณารายการที่แสดง (สำหรับ OpenGL) แม้ว่าจะเป็นวิธี "เก่า" แต่ก็สามารถเร็วขึ้นได้ คุณต้องอบรายการที่แสดงลงในตัวแปร ... ถ้าคุณเรียกการดำเนินการสร้างรายการที่แสดงในเรียลไทม์มันจะช้าอย่างไม่น่าเชื่อ รายการที่แสดงเป็นอย่างไรเร็วขึ้น? มันอัพเดตสถานะ, vs แอ็ตทริบิวต์ต่อจุดสุดยอดเท่านั้น นี่หมายความว่าฉันสามารถส่งผ่านใบหน้าได้สูงสุดหกใบหน้าจากนั้นหนึ่งสี (เทียบกับสีสำหรับแต่ละจุดยอดของ voxel) หากคุณใช้ GL_QUADS และ cubic voxels สิ่งนี้สามารถบันทึกได้มากถึง 20 ไบต์ (160 บิต) ต่อ voxel! (15 ไบต์โดยไม่มีตัวอักษรถึงแม้ว่าโดยปกติแล้วคุณต้องการให้สิ่งต่าง ๆ เรียงกันแบบ 4 ไบต์)
ฉันใช้วิธีการเดรัจฉานบังคับของการแสดงผล "ชิ้น" หรือหน้าข้อมูลซึ่งเป็นเทคนิคทั่วไป ซึ่งแตกต่างจาก octrees มันง่ายกว่า / เร็วกว่าในการอ่าน / ประมวลผลข้อมูลแม้ว่าจะเป็นมิตรกับหน่วยความจำน้อยกว่า (แต่ทุกวันนี้คุณสามารถรับหน่วยความจำ 64 กิกะไบต์สำหรับ $ 200- $ 300) ... ไม่ใช่ผู้ใช้ทั่วไป เห็นได้ชัดว่าคุณไม่สามารถจัดสรรอาเรย์ขนาดใหญ่สำหรับโลกทั้งใบได้ (ชุด 1024x1024x1024 ของ voxels คือหน่วยความจำ 4 กิกะไบต์โดยสมมติว่าใช้ 32-bit int ต่อ voxel) ดังนั้นคุณจัดสรร / dealloc อาร์เรย์ขนาดเล็กจำนวนมากขึ้นอยู่กับความใกล้ชิดกับผู้ชม นอกจากนี้คุณยังสามารถจัดสรรข้อมูลรับรายการแสดงผลที่จำเป็นจากนั้นถ่ายโอนข้อมูลเพื่อบันทึกหน่วยความจำ ฉันคิดว่าคำสั่งผสมในอุดมคติอาจใช้วิธีไฮบริดของ octrees และ arrays - เก็บข้อมูลไว้ใน array เมื่อทำการสร้างโพรซีเดอร์ของโลก, แสง, ฯลฯ
แสดงผลใกล้ถึงไกล ... การตัดพิกเซลทำให้ประหยัดเวลา gpu จะโยนพิกเซลหากไม่ผ่านการทดสอบบัฟเฟอร์ความลึก
แสดงผลชิ้น / เพจในวิวพอร์ตเท่านั้น (อธิบายตนเอง) แม้ว่า GPU จะรู้วิธีคลิปโพลีออนนอกวิวพอร์ตการส่งผ่านข้อมูลนี้ยังคงใช้เวลา ฉันไม่ทราบว่าโครงสร้างที่มีประสิทธิภาพที่สุดสำหรับเรื่องนี้คืออะไร ("น่าละอาย" ฉันไม่เคยเขียนแผนภูมิ BSP) แต่ถึงแม้จะเป็น raycast แบบง่าย ๆ บนพื้นฐานต่อชิ้นอาจปรับปรุงประสิทธิภาพและการทดสอบกับ frustum ที่ดูจะชัดเจน ประหยัดเวลา.
ข้อมูลที่ชัดเจน แต่สำหรับมือใหม่: ลบทุกรูปหลายเหลี่ยมที่ไม่ได้อยู่บนพื้นผิว - เช่นถ้า voxel ประกอบด้วยใบหน้าหกหน้าให้ลบใบหน้าที่ไม่เคยแสดงผลออกมา (แตะ voxel อีกอัน)
ตามกฎทั่วไปของทุกสิ่งที่คุณทำในการเขียนโปรแกรม: CACHE LOCALITY! หากคุณสามารถเก็บสิ่งต่าง ๆ ไว้ในแคชในตัวเครื่อง (แม้จะมีเวลาเพียงเล็กน้อยก็ตามมันก็จะสร้างความแตกต่างอย่างมหาศาล) นั่นหมายถึงการรักษาข้อมูลของคุณให้สอดคล้องกัน (ในพื้นที่หน่วยความจำเดียวกัน) และไม่สลับพื้นที่หน่วยความจำ นึกคิดทำงานกับหนึ่งชิ้นต่อเธรดและเก็บหน่วยความจำนั้นไว้ที่เธรดซึ่งไม่ได้ใช้กับ CPU แคชเท่านั้นนึกถึงลำดับชั้นของแคชแบบนี้ (ช้าที่สุดไปเร็วที่สุด): เครือข่าย (คลาวด์ / ฐานข้อมูล / ฯลฯ ) -> ฮาร์ดไดรฟ์ (รับ SSD หากคุณยังไม่มี), ram (รับ tripple channel หรือ RAM มากกว่าถ้าคุณยังไม่มี), CPU Cache, รีจิสเตอร์พยายามเก็บข้อมูลของคุณไว้ ปลายหลังและไม่สลับมากกว่าที่คุณต้อง
Threading ทำมัน. โลก Voxel นั้นเหมาะสำหรับการทำเกลียวเนื่องจากแต่ละส่วนสามารถคำนวณได้ (ส่วนใหญ่) เป็นอิสระจากส่วนอื่น ๆ ... ฉันเห็นว่าการปรับปรุงใกล้เคียง 4x (บน 4 คอร์, 8 เธรดคอร์ i7) ในการสร้างโลกแบบขั้นตอนเมื่อฉันเขียน รูทีนสำหรับเธรด
อย่าใช้ชนิดข้อมูลถ่าน / ไบต์ หรือกางเกงขาสั้น ผู้บริโภคทั่วไปของคุณจะมีโปรเซสเซอร์ AMD หรือ Intel ที่ทันสมัย (เช่นเดียวกับคุณ) โปรเซสเซอร์เหล่านี้ไม่มีการลงทะเบียน 8 บิต พวกเขาคำนวณไบต์โดยใส่ลงในสล็อต 32 บิตแล้วแปลงกลับ (อาจ) ในหน่วยความจำ คอมไพเลอร์ของคุณอาจทำรายการวูดูทุกประเภท แต่การใช้หมายเลข 32 หรือ 64 บิตจะทำให้ได้ผลลัพธ์ที่คาดการณ์ได้มากที่สุด (และเร็วที่สุด) เช่นเดียวกันค่า "บูล" ไม่ใช้เวลา 1 บิต; คอมไพเลอร์มักจะใช้เต็ม 32 บิตสำหรับบูล อาจเป็นการดึงดูดให้ทำการบีบอัดข้อมูลบางประเภท ตัวอย่างเช่นคุณสามารถเก็บ 8 voxels เป็นตัวเลขเดียว (2 ^ 8 = 256 ชุด) หากพวกเขาเป็นประเภท / สีเดียวกันทั้งหมด อย่างไรก็ตามคุณต้องคิดเกี่ยวกับการแยกแยะเรื่องนี้ - มันอาจช่วยประหยัดหน่วยความจำได้มาก แต่มันยังสามารถขัดขวางประสิทธิภาพการทำงานแม้จะมีเวลาในการบีบอัดขนาดเล็กก็ตามเพราะแม้แต่ช่วงเวลาพิเศษจำนวนเล็กน้อยนั้นก็ปรับขนาดตามขนาดโลกของคุณ ลองนึกภาพการคำนวณ raycast; สำหรับทุกขั้นตอนของ raycast คุณจะต้องเรียกใช้อัลกอริธึมการคลายการบีบอัด (เว้นแต่คุณจะได้วิธีการคำนวณทั่วไปสำหรับ 8 voxels ในขั้นตอนเดียว)
ตามที่ Jose Chavez กล่าวถึงรูปแบบการออกแบบฟลายเวทจะมีประโยชน์ เช่นเดียวกับที่คุณใช้บิตแมปเพื่อเป็นตัวแทนของเกมเรียงซ้อนในเกม 2D คุณสามารถสร้างโลกของคุณจากรูปแบบ 3 มิติหลายแบบ (หรือบล็อก) ข้อเสียของเรื่องนี้คือการทำซ้ำของพื้นผิว แต่คุณสามารถแก้ไขได้โดยใช้พื้นผิวที่แปรปรวน โดยทั่วไปแล้วคุณต้องการใช้การสร้างความสมดุลในทุกที่ที่ทำได้
หลีกเลี่ยงการประมวลผลจุดยอดและพิกเซลใน shader เมื่อแสดงผลเรขาคณิต ในเอ็นจิ้น voxel คุณจะมีสามเหลี่ยมหลายอันอย่างหลีกเลี่ยงไม่ได้ดังนั้นแม้แต่ pixel shader ที่เรียบง่ายก็สามารถลดเวลาเรนเดอร์ของคุณได้อย่างมาก มันจะดีกว่าในการเรนเดอร์ให้กับบัฟเฟอร์จากนั้นคุณจะใช้ Pixel Shader เป็นโพสต์โปรเซส หากคุณไม่สามารถทำได้ให้ลองทำการคำนวณในจุดสุดยอดของคุณ การคำนวณอื่น ๆ ควรถูกอบลงในข้อมูลจุดสุดยอดที่เป็นไปได้ บัตรผ่านเพิ่มเติมมีราคาแพงมากหากคุณต้องแสดงผลเรขาคณิตทั้งหมดอีกครั้ง (เช่นการจับคู่เงาหรือการจับคู่สภาพแวดล้อม) บางครั้งมันจะเป็นการดีกว่าถ้าจะให้มีฉากแบบไดนามิกเพื่อให้ได้รายละเอียดที่สมบูรณ์ยิ่งขึ้น หากเกมของคุณมีฉากที่สามารถแก้ไขได้ (เช่นภูมิประเทศที่สามารถทำลายได้) คุณสามารถคำนวณฉากใหม่ได้เมื่อสิ่งต่าง ๆ ถูกทำลาย การคอมไพล์ใหม่ไม่แพงและควรใช้เวลาไม่กี่วินาที
ผ่อนคลายลูปของคุณและรักษาอาร์เรย์ให้เรียบ! อย่าทำสิ่งนี้:
for (i = 0; i < chunkLength; i++) {
for (j = 0; j < chunkLength; j++) {
for (k = 0; k < chunkLength; k++) {
MyData[i][j][k] = newVal;
}
}
}
//Instead, do this:
for (i = 0; i < chunkLengthCubed; i++) {
//figure out x, y, z index of chunk using modulus and div operators on i
//myData should have chunkLengthCubed number of indices, obviously
myData[i] = newVal;
}
แก้ไข: จากการทดสอบที่ครอบคลุมมากขึ้นฉันพบว่าสิ่งนี้อาจผิด ใช้กรณีที่ได้ผลดีที่สุดสำหรับสถานการณ์ของคุณ โดยทั่วไปแล้วอาร์เรย์ควรจะแบน แต่การใช้ลูปแบบหลายดัชนีมักจะเร็วกว่าขึ้นกับกรณี