เร่งการสร้างพื้นผิวขั้นตอน


14

เมื่อไม่นานมานี้ฉันเริ่มทำงานกับเกมที่เกิดขึ้นในระบบสุริยจักรวาล หลังจากเส้นโค้งการเรียนรู้เล็กน้อย (โดยที่ไม่เคยทำงานกับ Scala, OpenGL 2 ES หรือ Libgdx มาก่อน) ฉันมีการสาธิตเทคโนโลยีพื้นฐานที่คุณหมุนรอบดาวเคราะห์ที่มีพื้นผิวเดียว:

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

ปัญหาที่ฉันพบคือประสิทธิภาพของการสร้างพื้นผิว ภาพรวมอย่างรวดเร็วของสิ่งที่ฉันทำ: ดาวเคราะห์เป็นลูกบาศก์ที่ได้รับการเปลี่ยนรูปทรงกลม ในแต่ละด้านจะมีการใช้พื้นผิว anxn (เช่น 256 x 256) ซึ่งรวมอยู่ในพื้นผิว 8n xn หนึ่งอันที่ถูกส่งไปยังส่วนย่อย ไม่ได้ใช้ช่องว่างสองอันสุดท้ายพวกเขาอยู่ที่นั่นเพื่อให้แน่ใจว่าความกว้างเท่ากับกำลังงาน 2 พื้นผิวถูกสร้างขึ้นบน CPU ในปัจจุบันโดยใช้อัลกอริธึมเสียงรบกวน simplex เวอร์ชั่น 2012 ที่เชื่อมโยงกับกระดาษ'Simplex เสียง demystified '. ฉากที่ฉันใช้ทดสอบอัลกอริธึมมีสองทรงกลม: ดาวเคราะห์และพื้นหลัง ทั้งสองใช้พื้นผิวแบบ greyscale ซึ่งประกอบด้วยเสียงหกมิติแบบหกเสี้ยวง่ายเช่นถ้าเราเลือก 128x128 เป็นขนาดพื้นผิวมีขนาด 128 x 128 x 6 x 2 x 6 = ประมาณ 1.2 ล้านการโทรไปยังฟังก์ชั่นเสียงรบกวน

สิ่งที่ใกล้เคียงที่สุดที่คุณจะไปถึงดาวเคราะห์คือเกี่ยวกับสิ่งที่แสดงในภาพหน้าจอและเนื่องจากความละเอียดเป้าหมายของเกมคือ 1280x720 ซึ่งหมายความว่าฉันต้องการใช้พื้นผิว 512x512 รวมกับความจริงที่ว่าพื้นผิวจริงจะมีความซับซ้อนมากกว่าเสียงรบกวนขั้นพื้นฐาน (จะมีทั้งกลางวันและกลางคืนพื้นผิวผสมใน shader ส่วนตามแสงแดดและหน้ากาก specular ฉันต้องการเสียงสำหรับทวีปการแปรผันของสีภูมิประเทศ เมฆแสงไฟของเมือง ฯลฯ ) และเรากำลังมองหาบางสิ่งบางอย่างเช่น 512 x 512 x 6 x 3 x 15 = 70 ล้านเสียงเรียกร้องให้ดาวเคราะห์เพียงอย่างเดียว ในเกมสุดท้ายจะมีกิจกรรมเมื่อเดินทางระหว่างดาวเคราะห์ดังนั้นการรอประมาณ 5 หรือ 10 วินาทีอาจเป็น 20 จะเป็นที่ยอมรับได้เนื่องจากฉันสามารถคำนวณพื้นผิวในพื้นหลังขณะเดินทางแม้ว่าจะเห็นได้ชัดว่าดีกว่าเร็วกว่า

กลับไปสู่ฉากทดสอบของเราประสิทธิภาพบนพีซีของฉันไม่น่ากลัวเกินไป แต่ก็ยังช้าเกินไปเมื่อพิจารณาผลสุดท้ายจะแย่กว่าประมาณ 60 เท่า:

128x128 : 0.1s
256x256 : 0.4s
512x512 : 1.7s

นี่เป็นหลังจากที่ฉันย้ายโค้ดที่มีประสิทธิภาพที่สำคัญทั้งหมดไปยัง Java เนื่องจากการพยายามทำเช่นนั้นใน Scala นั้นแย่กว่ามาก อย่างไรก็ตามการใช้งานบนโทรศัพท์ของฉัน (Samsung Galaxy S3) ทำให้เกิดปัญหามากขึ้น:

128x128 :  2s
256x256 :  7s
512x512 : 29s

ยาวเกินไปแล้วและนั่นก็ไม่ได้คำนึงถึงความจริงที่ว่ามันจะเป็นนาทีแทนที่จะเป็นวินาทีในรุ่นสุดท้าย เห็นได้ชัดว่าสิ่งที่ต้องทำ โดยส่วนตัวแล้วฉันเห็นหนทางที่เป็นไปได้สองสามอย่างถึงแม้ว่าฉันจะไม่กระตือรือร้นกับพวกเขามากนัก:

  • อย่าคำนวณพื้นผิวล่วงหน้า แต่ให้ shader ส่วนคำนวณทุกอย่าง อาจเป็นไปไม่ได้เพราะ ณ จุดหนึ่งฉันมีพื้นหลังเป็นรูปสี่เหลี่ยมเต็มหน้าจอที่มีตัวปรับพิกเซลและฉันได้รับประมาณ 1 เฟรมต่อวินาทีในโทรศัพท์ของฉัน
  • ใช้ GPU เพื่อแสดงพื้นผิวหนึ่งครั้งเก็บและใช้พื้นผิวที่เก็บไว้จากนั้นเป็นต้นไป Upside: อาจเร็วกว่าทำบน CPU เนื่องจาก GPU ควรจะเร็วกว่าในการคำนวณจุดลอย ข้อเสีย: เอฟเฟกต์ที่ไม่สามารถแสดงได้อย่างง่ายดาย (เช่นง่าย ๆ ) ของเสียงรบกวนแบบง่าย ๆ (เช่น vortices ดาวเคราะห์ก๊าซหลุมอุกกาบาตดวงจันทร์ ฯลฯ ) นั้นยากที่จะเขียนโค้ดใน GLSL ได้มากกว่าใน Scala / Java
  • คำนวณพื้นผิวของเสียงรบกวนจำนวนมากและส่งไปพร้อมกับแอปพลิเคชัน ฉันต้องการหลีกเลี่ยงสิ่งนี้หากเป็นไปได้
  • ลดความละเอียดลง ซื้อการเพิ่มประสิทธิภาพ 4x ซึ่งไม่เพียงพอจริง ๆ บวกกับการสูญเสียคุณภาพจำนวนมาก
  • ค้นหาอัลกอริธึมด้านเสียงที่เร็วขึ้น ถ้าใครมีผมคนเดียวฉันก็หู แต่ simplex น่าจะเร็วกว่า perlin แล้ว
  • ใช้รูปแบบพิกเซลเพื่อลดความละเอียดของพื้นผิวและลดระดับเสียงรบกวน ในขณะที่ฉันจินตนาการถึงเกมในรูปแบบนี้ในตอนแรกฉันก็ชอบวิธีการที่สมจริง
  • ฉันกำลังทำอะไรผิดพลาดและการแสดงควรมีขนาดหนึ่งหรือสองคำสั่งที่ดีกว่า หากเป็นกรณีนี้โปรดแจ้งให้เราทราบ

หากใครมีข้อเสนอแนะเคล็ดลับการแก้ปัญหาหรือความคิดเห็นอื่น ๆ เกี่ยวกับปัญหานี้ฉันชอบที่จะได้ยินพวกเขา

ในการตอบสนองต่อ Layoric นี่คือรหัสที่ฉันใช้:

//The function that generates the simplex noise texture
public static Texture simplex(int size) {
    byte[] data = new byte[size * size * columns * 4];
    int offset = 0;
    for (int y = 0; y < size; y++) {
        for (int s = 0; s < columns; s++) {
            for (int x = 0; x < size; x++) {
                //Scale x and y to [-1,1] range
                double tx = ((double)x / (size - 1)) * 2 - 1;
                double ty = 1 - ((double)y / (size - 1)) * 2;

                //Determine point on cube in worldspace
                double cx = 0, cy = 0, cz = 0;
                if      (s == 0) { cx =   1; cy =  tx; cz =  ty; }
                else if (s == 1) { cx = -tx; cy =   1; cz =  ty; }
                else if (s == 2) { cx = - 1; cy = -tx; cz =  ty; }
                else if (s == 3) { cx =  tx; cy = - 1; cz =  ty; }
                else if (s == 4) { cx = -ty; cy =  tx; cz =   1; }
                else if (s == 5) { cx =  ty; cy =  tx; cz = - 1; }

                //Determine point on sphere in worldspace
                double sx = cx * Math.sqrt(1 - cy*cy/2 - cz*cz/2 + cy*cy*cz*cz/3);
                double sy = cy * Math.sqrt(1 - cz*cz/2 - cx*cx/2 + cz*cz*cx*cx/3);
                double sz = cz * Math.sqrt(1 - cx*cx/2 - cy*cy/2 + cx*cx*cy*cy/3);

                //Generate 6 octaves of noise
                float gray = (float)(SimplexNoise.fbm(6, sx, sy, sz, 8) / 2 + 0.5);

                //Set components of the current pixel
                data[offset    ] = (byte)(gray * 255);
                data[offset + 1] = (byte)(gray * 255);
                data[offset + 2] = (byte)(gray * 255);
                data[offset + 3] = (byte)(255);

                //Move to the next pixel
                offset += 4;
            }
        }
    }

    Pixmap pixmap = new Pixmap(columns * size, size, Pixmap.Format.RGBA8888);
    pixmap.getPixels().put(data).position(0);

    Texture texture = new Texture(pixmap, true);
    texture.setFilter(TextureFilter.Linear, TextureFilter.Linear);
    return texture;
}

//SimplexNoise.fbm
//The noise function is the same one found in http://webstaff.itn.liu.se/~stegu/simplexnoise/SimplexNoise.java
//the only modification being that I replaced the 32 in the last line with 16 in order to end up with
//noise in the range [-0.5, 0.5] instead of [-1,1]
public static double fbm(int octaves, double x, double y, double z, double frequency) {
    double value = 0;
    double f = frequency;
    double amp = 1;
    for (int i = 0; i < octaves; i++) {
        value += noise(x*f, y*f, z*f) * amp;
        f *= 2;
        amp /= 2;
    }
    return value; 
}

คุณช่วยโพสต์สิ่งที่คุณมีใน Java สำหรับฟังก์ชั่นเสียงรบกวนได้หรือไม่? ไม่ได้บอกว่ามีการเพิ่มประสิทธิภาพใด ๆ ที่จะได้รับจากมัน แต่บางคนอาจเห็นบางสิ่งบางอย่างเพื่อให้คุณได้รับการส่งเสริม
Darren Reid

ฉันได้เพิ่มรหัสที่ฉันใช้ในโพสต์ต้นฉบับ
FalconNL

ไม่เกี่ยวข้องกับ Q ต่อ se ของคุณ แต่คุณควรเรียก dispose () บน pixmap ของคุณหลังจากที่คุณทำเสร็จแล้ว
junkdog

คำตอบ:


10

คุณสามารถรวมวิธี (2) และ (3) ดังนี้:

  • ขั้นแรกให้ใช้ GPU เพื่อสร้างพื้นผิวของเสียงรบกวนและบันทึกไว้ นี่จะเป็น "แคชเสียง" ของคุณ คุณสามารถทำได้เพียงครั้งเดียวในการเรียกใช้ครั้งแรก
  • ในการสร้างพื้นผิวในเกมให้รวมพื้นผิวบางส่วนจากแคช - ซึ่งควรจะเร็วจริง ๆ จากนั้นหากจำเป็นให้เพิ่มเอฟเฟกต์พิเศษเช่น vortices ไว้ด้านบน
  • หรือคุณสามารถสร้างพื้นผิว "เอฟเฟ็กต์พิเศษ" ไว้ล่วงหน้าและผสมผสานให้เข้ากับผลลัพธ์สุดท้าย

+1 ฉันคิดว่าการสร้างพื้นผิวจำนวนมากและบรรจุไว้ในเกมเพื่อรวมหรือใช้งานง่าย ๆ จะเป็นวิธีที่ดีที่สุดในการทำ
TheNickmaster21

2

การสร้างพื้นผิวกระบวนงานเป็น ab * *ของ mofo ในแง่ของเวลาในการคำนวณ มันเป็นสิ่งที่มันเป็น.

การดำเนินงานที่ดีที่สุดของ Simplex เสียงรบกวนฉันได้พบเป็นสเตฟาน Gustavson

นอกเหนือจากการปรับปรุงเวลาประมวลผลจริง (เป็นเรื่องยากที่จะผ่านความจริงที่ว่าคุณเพียงแค่ขอคอมพิวเตอร์จำนวนมากเมื่อคุณคำนวณพื้นผิวตามขั้นตอน 1024x1024) หนึ่งในวิธีที่ดีที่สุดในการลดเวลารอที่รับรู้คือ แอปของคุณทำงานเธรดพื้นหลังให้มากที่สุดเท่าที่จะทำได้

ดังนั้นเริ่มสร้างพื้นผิวที่เปิดตัวเกมบนเธรดพื้นหลังในขณะที่ผู้ใช้ยังเล่นซอกับตัวเลือกและเมนูหรือดูตัวอย่างการเริ่มต้นระดับ

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.