Đẩy nhanh quá trình tạo kết cấu thủ tục


14

Gần đây tôi đã bắt đầu làm việc với một trò chơi diễn ra trong một hệ mặt trời được tạo theo thủ tục. Sau một chút thời gian học tập (chưa từng làm việc với Scala, OpenGL 2 ES hoặc Libgdx trước đó), tôi có một bản demo công nghệ cơ bản nơi bạn quay quanh một hành tinh có kết cấu thủ tục duy nhất:

nhập mô tả hình ảnh ở đây

Vấn đề tôi gặp phải là hiệu suất của việc tạo kết cấu. Tổng quan nhanh về những gì tôi đang làm: một hành tinh là một khối lập phương đã bị biến dạng thành hình cầu. Ở mỗi bên, kết cấu lo lắng (ví dụ 256 x 256) được áp dụng, được gói trong một kết cấu 8n xn được gửi đến trình đổ bóng mảnh. Hai không gian cuối cùng không được sử dụng, chúng chỉ ở đó để đảm bảo chiều rộng là sức mạnh của 2. Kết cấu hiện được tạo trên CPU, sử dụng phiên bản cập nhật 2012 của thuật toán nhiễu đơn giản được liên kết trong bài báo 'Simplex tiếng ồn làm sáng tỏ '. Cảnh tôi đang sử dụng để kiểm tra thuật toán chứa hai hình cầu: hành tinh và hậu cảnh. Cả hai đều sử dụng kết cấu thang độ xám bao gồm sáu quãng tám nhiễu 3D đơn giản, vì vậy, nếu chúng ta chọn 128x128 làm kích thước kết cấu thì có 128 x 128 x 6 x 2 x 6 = khoảng 1,2 triệu cuộc gọi đến chức năng nhiễu.

Gần nhất bạn sẽ đến hành tinh này là về những gì được hiển thị trong ảnh chụp màn hình và vì độ phân giải mục tiêu của trò chơi là 1280x720, điều đó có nghĩa là tôi thích sử dụng họa tiết 512x512. Kết hợp với thực tế, kết cấu thực tế tất nhiên sẽ phức tạp hơn tiếng ồn cơ bản (Sẽ có kết cấu ngày và đêm, được pha trộn trong shader mảnh dựa trên ánh sáng mặt trời và mặt nạ đặc biệt. Tôi cần tiếng ồn cho các lục địa, biến đổi màu địa hình , mây, ánh đèn thành phố, v.v.) và chúng ta đang xem xét một cái gì đó như 512 x 512 x 6 x 3 x 15 = 70 triệu tiếng ồn gọi riêng cho hành tinh này. Trong trò chơi cuối cùng, sẽ có các hoạt động khi di chuyển giữa các hành tinh, do đó, chờ 5 hoặc 10 giây, có thể là 20, sẽ được chấp nhận vì tôi có thể tính toán kết cấu trong nền khi đi du lịch, mặc dù rõ ràng càng nhanh càng tốt.

Quay trở lại cảnh thử nghiệm của chúng tôi, hiệu suất trên PC của tôi không quá khủng khiếp, mặc dù vẫn quá chậm khi xem xét kết quả cuối cùng sẽ tồi tệ hơn khoảng 60 lần:

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

Đây là sau khi tôi chuyển tất cả mã quan trọng về hiệu năng sang Java, vì cố gắng làm như vậy trong Scala còn tệ hơn rất nhiều. Tuy nhiên, việc chạy này trên điện thoại của tôi (Samsung Galaxy S3) tạo ra một kết quả có vấn đề hơn:

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

Đã quá lâu và điều đó thậm chí còn không bao gồm trong thực tế rằng nó sẽ là vài phút thay vì vài giây trong phiên bản cuối cùng. Rõ ràng một cái gì đó cần phải được thực hiện. Cá nhân, tôi thấy một vài con đường tiềm năng, mặc dù tôi chưa đặc biệt quan tâm đến bất kỳ ai trong số họ:

  • Đừng tính toán trước kết cấu, nhưng hãy để shader mảnh tính toán mọi thứ. Có lẽ là không khả thi, bởi vì tại một thời điểm, tôi có nền là một quad toàn màn hình với một pixel shader và tôi nhận được khoảng 1 khung hình / giây trên điện thoại của mình.
  • Sử dụng GPU để kết xuất kết cấu một lần, lưu trữ và sử dụng kết cấu được lưu trữ từ đó về sau. Ưu điểm: có thể nhanh hơn so với thực hiện trên CPU vì GPU được cho là nhanh hơn khi tính toán dấu phẩy động. Nhược điểm: các hiệu ứng không thể (dễ dàng) được thể hiện dưới dạng các hàm của nhiễu đơn giản (ví dụ như các xoáy hành tinh khí, miệng hố mặt trăng, v.v.) khó mã hóa trong GLSL hơn rất nhiều so với Scala / Java.
  • Tính toán một lượng lớn kết cấu nhiễu và gửi chúng cùng với ứng dụng. Tôi muốn tránh điều này nếu có thể.
  • Hạ nghị quyết. Mua cho tôi mức tăng hiệu suất gấp 4 lần, điều đó không thực sự đủ cộng với tôi mất rất nhiều chất lượng.
  • Tìm một thuật toán tiếng ồn nhanh hơn. Nếu bất cứ ai có một tôi đều là tai, nhưng Simplex được cho là nhanh hơn perlin.
  • Áp dụng phong cách nghệ thuật pixel, cho phép kết cấu có độ phân giải thấp hơn và quãng tám nhiễu ít hơn. Trong khi ban đầu tôi hình dung trò chơi theo phong cách này, tôi đã thích cách tiếp cận thực tế hơn.
  • Tôi đang làm gì đó sai và hiệu suất sẽ tốt hơn một hoặc hai bậc. Nếu đây là trường hợp, xin vui lòng cho tôi biết.

Nếu bất cứ ai có bất kỳ đề xuất, lời khuyên, cách giải quyết hoặc ý kiến ​​khác liên quan đến vấn đề này, tôi rất muốn nghe họ.

Đáp lại Layoric, đây là mã tôi đang sử dụng:

//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; 
}

Bạn có thể đăng lên những gì bạn hiện có trong Java cho hàm tiếng ồn của mình không? Không nói rằng có tồn tại bất kỳ lợi ích hiệu suất nào có được từ nó, nhưng một số người có thể phát hiện ra điều gì đó để tăng sức mạnh cho bạn.
Darren Reid

Tôi đã thêm mã tôi đang sử dụng vào bài viết gốc.
FalconNL

Không liên quan đến Q per se của bạn, nhưng bạn nên gọi dispose () trên pixmap của bạn sau khi bạn hoàn thành nó.
rác rưởi

Câu trả lời:


10

Bạn có thể kết hợp các cách tiếp cận (2) và (3) như thế này:

  • Đầu tiên, sử dụng GPU để tạo ra một số kết cấu nhiễu và lưu chúng. Đây sẽ là "bộ đệm tiếng ồn" của bạn; bạn chỉ có thể làm điều đó một lần trong lần chạy đầu tiên.
  • Để tạo một kết cấu trong trò chơi, hãy kết hợp một vài kết cấu từ bộ đệm - điều này sẽ rất nhanh. Sau đó, nếu cần, thêm các hiệu ứng đặc biệt như xoáy trên đầu trang.
  • Ngoài ra, bạn cũng có thể tạo trước một số kết cấu "hiệu ứng đặc biệt" và chỉ cần trộn chúng để có kết quả cuối cùng.

+1 Tôi nghĩ rằng việc tạo ra một loạt các kết cấu và đóng gói chúng với trò chơi để kết hợp hoặc áp dụng các ảnh hưởng đơn giản sẽ là cách tốt nhất để làm điều đó.
TheNickmaster21

2

Tạo kết cấu thủ tục là ab * * của một mofo về mặt thời gian tính toán. Đó là những gì nó được.

Cách thực hiện tốt nhất của Simplex noise mà tôi đã tìm thấy là của Stefan Gustavson .

Ngoài việc cải thiện thời gian tính toán thực tế (thực sự khá khó khăn để vượt qua thực tế là bạn chỉ cần yêu cầu rất nhiều máy tính khi bạn tính toán kết cấu thủ tục 1024x1024), một trong những cách tốt nhất để giảm thời gian chờ nhận thức là ứng dụng của bạn làm càng nhiều chủ đề nền càng tốt.

Vì vậy, bắt đầu tạo kết cấu khi khởi chạy trò chơi trên luồng nền , trong khi người dùng vẫn đang loay hoay với các tùy chọn và menu hoặc xem đoạn giới thiệu bắt đầu cấp.

Một điều khác cần xem xét là, chỉ cần lưu trữ vài trăm kết cấu được tạo vào đĩa và chọn ngẫu nhiên một trong số các kết cấu này khi tải. Nhiều đĩa hơn, nhưng thời gian tải ít hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.