加快程序纹理的生成


14

最近,我开始研究在程序生成的太阳系中进行的游戏。经过一番学习之后(之前都没有使用Scala,OpenGL 2 ES或Libgdx进行过学习),我进行了一个基本的技术演示,您将围绕一个程序化纹理的行星旋转:

在此处输入图片说明

我遇到的问题是纹理生成的性能。我正在做的事情的简要概述:行星是已变形为球体的立方体。每一面都应用anxn(例如256 x 256)纹理,该纹理捆绑在一个8n xn纹理中,并发送到片段着色器。最后两个空格未使用,它们仅用于确保宽度为2的幂。使用在'Simplex'论文中链接的2012年更新的单纯形噪声算法,当前正在CPU上生成纹理。噪音消失了。我用来测试算法的场景包含两个球体:行星和背景。两者都使用由六个八度的3D单工噪声组成的灰度纹理,因此,例如,如果我们选择128x128作为纹理大小,则有128 x 128 x 6 x 2 x 6 =约120万个噪声函数调用。

您最接近地球的是屏幕快照中显示的内容,并且由于游戏的目标分辨率为1280x720,这意味着我希望使用512x512纹理。结合这样的事实,实际的纹理当然比基本噪声要复杂(白天和黑夜的纹理,将基于阳光和镜面反射的片段着色器混合在一起。我需要大洲的噪声,地形颜色变化,云层,城市的灯光等),而我们正在寻找的是512 x 512 x 6 x 3 x 15 =仅此星球就需要7,000万个噪声的东西。在最终游戏中,行星之间旅行时会有活动,所以等待5或10秒(可能是20秒)是可以接受的,因为我可以在旅行时计算背景纹理,尽管显然速度越快越好。

回到我们的测试场景,我的PC上的性能并不是太糟糕,尽管考虑到最终结果将比原来差60倍,但仍然太慢了:

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

这是在我将所有对性能至关重要的代码移至Java之后的事情,因为在Scala中尝试这样做要差得多。但是,在我的手机(三星Galaxy S3)上运行它会产生更成问题的结果:

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

已经太长了,这甚至没有考虑到最终版本将是分钟而不是秒的事实。显然需要做些事情。就个人而言,我看到了一些潜在的途径,尽管我并不特别热衷于其中任何一种:

  • 不要预先计算纹理,而是让片段着色器计算所有内容。可能不可行,因为有一次我将背景作为具有像素着色器的全屏四边形,并且手机上的速度约为1 fps。
  • 使用GPU渲染一次纹理,然后将其存储并使用存储的纹理。上行空间:可能比在CPU上执行速度更快,因为在浮点计算中应该认为GPU会更快。缺点:在GLSL中,比起Scala / Java,难以(简单地)表达为单纯噪声功能(例如,气体行星涡,月球坑等)的效果要困难得多。
  • 计算大量的噪声纹理并将其随应用程序一起提供。我想尽可能避免这种情况。
  • 降低分辨率。给我买了4倍的性能提升,这还远远不够,而且我失去了很多质量。
  • 查找更快的噪声算法。如果有人有一个,我将不知所措,但是单纯形应该已经比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中当前具有的功能吗?并不是说可以从中获得任何性能上的提升,但是有些人可能会发现某些东西可以给您带来提升。
达伦·里德

我已经将正在使用的代码添加到原始帖子中。
FalconNL 2012年

与您的Q本身无关,但是使用完之后,应该在像素图上调用dispose()。
垃圾狗2012年

Answers:


10

您可以像这样组合方法(2)和(3):

  • 首先,使用GPU 生成许多噪声纹理并将其保存。这将是您的“噪音缓存”;您只能在第一次运行时执行一次。
  • 要在游戏中生成纹理,请从缓存中组合一些纹理-这应该非常快。然后,如果需要,在此之上添加特殊效果,例如涡旋。
  • 另外,您也可以预先生成一些“特殊效果”纹理,然后将它们混合以获得最终结果。

+1我认为生成一堆纹理并将其与游戏包装在一起以组合或应用简单的效果将是最好的方法。
TheNickmaster13年

2

程序纹理生成是AB * *在计算时间上一个美富的。就是这样。

我发现的最好的Simplex Noise实现是Stefan Gustavson的

除了改善实际的计算时间(实际上很难克服您实际上在计算1024x1024程序纹理时只是要求大量计算机使用这一事实),减少感知等待时间的最佳方法之一就是拥有您的应用会尽可能多地执行后台线程工作。

因此,在用户启动游戏时,在后台线程上开始生成纹理,而用户仍在摆弄选项和菜单,或者观看关卡预告片。

要考虑的另一件事是,只需将数百个生成的纹理缓存到磁盘,然后在加载时随机选择其中一个。更多的磁盘,但更少的加载时间。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.