LibGDX 3D提高性能

4
我正在开发一款3D游戏。
这个游戏需要约100个动态立方体。
我不太清楚这样的游戏需要多少性能,但我正在使用平板电脑测试,它配备了Mali-400 MP2 GPU、1GB RAM和1.5GHz双核处理器。我知道将所有立方体渲染到一个网格中,但这样就无法单独移动它们。
这种设置使我的fps非常波动,在20到50之间跳动,大多数情况下低于30。(在模拟器中为10-15)
当游戏开始时,我会构建一个ModelInstances的数组列表,它们都使用相同的模型。
model = new ModelBuilder().createBox(1f, 1f, 1f, new Material(ColorAttribute.createDiffuse(Color.GREEN)), Usage.Position | Usage.Normal);

// width,height,length = 5, creating a total of 125 cubes

for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
        for (int z = 0; z < length; z++) {
            if (this.map[x][y][z] > 0) {
                this.modelInstances.add(instance = new ModelInstance(model));
                instance.transform.translate(x, -(y * 1.5f), -z);
            }
        }
    }
}

渲染:

Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
mb.begin(camera3D);
mb.render(this.modelInstances);
mb.end();

相机初始化中:
camera3D = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
camera3D.position.set(0f, 8f, 5f);
camera3D.lookAt(0, 0, 0);
camera3D.near = 1f;
camera3D.far = 300f;
camera3D.update();
  • 我该做什么来提高性能?
  • 这个平板电脑对于这样的游戏来说太弱了吗?还是问题出在代码上?

编辑:

我也用WebGL进行了一些测试,同样的平板电脑,使用Chrome浏览器,渲染125个方块:稳定40-50帧每秒。


宽度、高度、长度试图等于什么? - Hayden
它们是5 5 5,共渲染了125个立方体。 - Dávid Szabó
@noone 我有一些代码行可以将fps写入屏幕,但这不是问题所在。如果我注释掉“mb.render(this.modelInstances)”,fps就会稳定在55-60。 - Dávid Szabó
@JustasSakalauskas =( 很遗憾这里和badlogic论坛都没有答案。 - Dávid Szabó
@Springrbua 视锥体裁剪是另一个故事(我稍后会实现它),你说你正在渲染100*100的地图,而我只渲染了125个立方体,因为我把所有的模型都分开了,试图找出如何合并它们并在之后进行编辑而不出问题。 - Dávid Szabó
显示剩余15条评论
1个回答

5
您可以按照以下方式将所有的立方体批处理为单个模型和模型实例:

您可以通过以下方式将所有立方体批量处理为单个模型和模型实例:

int width = 5;
int height = 5;
int length = 5;
int numCubes = width*height*length;

ModelBuilder mb = new ModelBuilder();
mb.begin();
MeshPartBuilder mpb = mb.part("cubes", GL20.GL_TRIANGLES, (Usage.Position | Usage.Normal), new Material(ColorAttribute.createDiffuse(Color.GREEN)));
for (int i=0; i<numCubes; i++){
    mpb.box(1, 1, 1); 
}
Model model = mb.end();
mBatchedCubesModelInstance = new ModelInstance(model);

但是最棘手的部分是能够将每个立方体移动到不同的位置并能够独立操作它们。

下面是一个Cube类,可以操作上述模型中的单个立方体。理论上说,这应该适用于您创建的任何使用24个唯一顶点的网格,因此您可以添加纹理坐标等。

此外,这也依赖于位置和法线始终是网格的前两个Usage属性,因此希望在libGDX 的Mesh类中保持这一点。

基本上,这是通过跟踪其在基础网格中的索引号(即它是第几个立方体)来运作的,以便它可以挑选出需要在顶点数组中更新的顶点。每帧都需要将顶点复制到网格中。

public class Cube {

    private int index;
    int vertexFloatSize;
    int posOffset;
    int norOffset;
    boolean hasColor;
    int colOffset;
    private Vector3 position = new Vector3();
    private Matrix4 rotationTransform = new Matrix4().idt();
    private Color color = new Color();
    public float halfWidth, halfHeight, halfDepth;
    private boolean transformDirty = false;
    private boolean colorDirty = false;

    static final Vector3 CORNER000 = new Vector3();
    static final Vector3 CORNER010 = new Vector3();
    static final Vector3 CORNER100 = new Vector3();
    static final Vector3 CORNER110 = new Vector3();
    static final Vector3 CORNER001 = new Vector3();
    static final Vector3 CORNER011 = new Vector3();
    static final Vector3 CORNER101 = new Vector3();
    static final Vector3 CORNER111 = new Vector3();

    static final Vector3[] FACE0 = {CORNER000, CORNER100, CORNER110, CORNER010};
    static final Vector3[] FACE1 = {CORNER101, CORNER001, CORNER011, CORNER111};
    static final Vector3[] FACE2 = {CORNER000, CORNER010, CORNER011, CORNER001};
    static final Vector3[] FACE3 = {CORNER101, CORNER111, CORNER110, CORNER100};
    static final Vector3[] FACE4 = {CORNER101, CORNER100, CORNER000, CORNER001};
    static final Vector3[] FACE5 = {CORNER110, CORNER111, CORNER011, CORNER010};
    static final Vector3[][] FACES = {FACE0, FACE1, FACE2, FACE3, FACE4, FACE5};

    static final Vector3 NORMAL0 = new Vector3();
    static final Vector3 NORMAL1 = new Vector3();
    static final Vector3 NORMAL2 = new Vector3();
    static final Vector3 NORMAL3 = new Vector3();
    static final Vector3 NORMAL4 = new Vector3();
    static final Vector3 NORMAL5 = new Vector3();
    static final Vector3[] NORMALS = {NORMAL0, NORMAL1, NORMAL2, NORMAL3, NORMAL4, NORMAL5};

    public Cube(float x, float y, float z, float width, float height, float depth, int index, 
            VertexAttributes vertexAttributes, float[] meshVertices){
        position.set(x,y,z);
        this.halfWidth = width/2;
        this.halfHeight = height/2;
        this.halfDepth = depth/2;
        this.index = index;


        vertexFloatSize = vertexAttributes.vertexSize/4; //4 bytes per float
        posOffset = getVertexAttribute(Usage.Position, vertexAttributes).offset/4;
        norOffset = getVertexAttribute(Usage.Normal, vertexAttributes).offset/4;

        VertexAttribute colorAttribute = getVertexAttribute(Usage.Color, vertexAttributes);
        hasColor = colorAttribute!=null;
        if (hasColor){
            colOffset = colorAttribute.offset/4;
            this.setColor(Color.WHITE, meshVertices);
        }
        transformDirty = true;
    }

    public void setIndex(int index){
        this.index = index;
        transformDirty = true;
        colorDirty = true;
    }

    /**
     * Call this after moving and/or rotating.
     */
    public void update(float[] meshVertices){
        if (colorDirty && hasColor){
            for (int faceIndex= 0; faceIndex<6; faceIndex++){
                int baseVertexIndex = (index*24 + faceIndex*4)*vertexFloatSize;//24 unique vertices per cube, 4 unique vertices per face
                for (int cornerIndex=0; cornerIndex<4; cornerIndex++){
                    int vertexIndex = baseVertexIndex + cornerIndex*vertexFloatSize + colOffset;
                    meshVertices[vertexIndex] = color.r;
                    meshVertices[++vertexIndex] = color.g;
                    meshVertices[++vertexIndex] = color.b;
                    meshVertices[++vertexIndex] = color.a;
                }
            }
            colorDirty = false;
        }


        if (!transformDirty){
            return;
        }
        transformDirty = false;

        CORNER000.set(-halfWidth,-halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER010.set(-halfWidth,halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER100.set(halfWidth,-halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER110.set(halfWidth,halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER001.set(-halfWidth,-halfHeight,halfDepth).rot(rotationTransform).add(position);
        CORNER011.set(-halfWidth,halfHeight,halfDepth).rot(rotationTransform).add(position);
        CORNER101.set(halfWidth,-halfHeight,halfDepth).rot(rotationTransform).add(position);
        CORNER111.set(halfWidth,halfHeight,halfDepth).rot(rotationTransform).add(position);

        NORMAL0.set(0,0,-1).rot(rotationTransform);
        NORMAL1.set(0,0,1).rot(rotationTransform);
        NORMAL2.set(-1,0,0).rot(rotationTransform);
        NORMAL3.set(1,0,0).rot(rotationTransform);
        NORMAL4.set(0,-1,0).rot(rotationTransform);
        NORMAL5.set(0,1,0).rot(rotationTransform);

        for (int faceIndex= 0; faceIndex<6; faceIndex++){
            int baseVertexIndex = (index*24 + faceIndex*4)*vertexFloatSize;//24 unique vertices per cube, 4 unique vertices per face
            for (int cornerIndex=0; cornerIndex<4; cornerIndex++){
                int vertexIndex = baseVertexIndex + cornerIndex*vertexFloatSize + posOffset;
                meshVertices[vertexIndex] = FACES[faceIndex][cornerIndex].x;
                meshVertices[++vertexIndex] = FACES[faceIndex][cornerIndex].y;
                meshVertices[++vertexIndex] = FACES[faceIndex][cornerIndex].z;

                vertexIndex = baseVertexIndex + cornerIndex*vertexFloatSize + norOffset;
                meshVertices[vertexIndex] = NORMALS[faceIndex].x;
                meshVertices[++vertexIndex] = NORMALS[faceIndex].y;
                meshVertices[++vertexIndex] = NORMALS[faceIndex].z;
            }
        }
    }

    public Cube setColor(Color color){
        if (hasColor){
            this.color.set(color);
            colorDirty = true;
        }
        return this;
    }

    public Cube translate(float x, float y, float z){
        position.add(x,y,z);
        transformDirty = true;
        return this;
    }

    public Cube translateTo(float x, float y, float z){
        position.set(x,y,z);
        transformDirty = true;
        return this;
    }

    public Cube rotate(float axisX, float axisY, float axisZ, float degrees){
        rotationTransform.rotate(axisX, axisY, axisZ, degrees);
        transformDirty = true;
        return this;
    }

    public Cube rotateTo(float axisX, float axisY, float axisZ, float degrees){
        rotationTransform.idt();
        rotationTransform.rotate(axisX, axisY, axisZ, degrees);
        transformDirty = true;
        return this;
    }

    public VertexAttribute getVertexAttribute (int usage, VertexAttributes attributes) {
        int len = attributes.size();
        for (int i = 0; i < len; i++)
            if (attributes.get(i).usage == usage) return attributes.get(i);

        return null;
    }
}

要使用这个功能,首先需要获取网格参考并创建立方体:

    mBatchedCubesMesh = model.meshes.get(0);
    VertexAttributes vertexAttributes = mBatchedCubesMesh.getVertexAttributes();
    int vertexFloatSize = vertexAttributes .vertexSize / 4; //4 bytes per float
    mBatchedCubesVertices = new float[numCubes * 24 * vertexFloatSize]; //24 unique vertices per cube
    mBatchedCubesMesh.getVertices(mBatchedCubesVertices);

    mBatchedCubes = new Array<Cube>(numCubes);
    int cubeNum = 0;
    for (int x = 0; x < width; x++) {
        for (int y = 0; y < height; y++) {
            for (int z = 0; z < length; z++) {
                mBatchedCubes.add(new Cube((x-(width/2f))*1.5f, -((y-(height/2f)) * 1.5f), -(z-(length/2f))*1.5f, 1,1,1, cubeNum++, vertexAttributes, mBatchedCubesVertices ));
            }
        }
    }

然后在你的render方法中:
mBatchedCubes.get(0).rotate(1, 1, 1, 180*delta); //example manipulation of a single cube

for (Cube cube : mBatchedCubes){ //must update any changed cubes. 
    cube.update(mBatchedCubesVertices);
}
mBatchedCubesMesh.setVertices(mBatchedCubesVertices); //apply changes to mesh

...

modelBatch.begin(camera);
modelBatch.render(mBatchedCubesModelInstance);
modelBatch.end();

现在,CPU顶点操作不如着色器顶点操作高效,如果您每帧移动所有方块,则可能会变成CPU限制。如果您不经常旋转它们,那么将为旋转创建一个单独的“脏”变量,并在更新方法中仅在必要时进行旋转可能会有所帮助。

编辑:来自此问题的更新

如果您想要透明度,则必须对立方体进行排序,以便可以根据从远到近的顺序进行绘制。它们的index值必须更新为新顺序,因为这是它们进入网格的顺序。这是一个支持排序的Cube类(颜色现在必须独立跟踪,因为立方体可能被移动到网格的其他部分)。

public class Cube implements Comparable<Cube>{

    private int index;
    int vertexFloatSize;
    int posOffset;
    int norOffset;
    boolean hasColor;
    int colOffset;
    private Vector3 position = new Vector3();
    private Matrix4 rotationTransform = new Matrix4().idt();
    public float halfWidth, halfHeight, halfDepth;
    private boolean transformDirty = false;
    private boolean colorDirty = false;
    private Color color = new Color();
    float camDistSquared;

    static final Vector3 CORNER000 = new Vector3();
    static final Vector3 CORNER010 = new Vector3();
    static final Vector3 CORNER100 = new Vector3();
    static final Vector3 CORNER110 = new Vector3();
    static final Vector3 CORNER001 = new Vector3();
    static final Vector3 CORNER011 = new Vector3();
    static final Vector3 CORNER101 = new Vector3();
    static final Vector3 CORNER111 = new Vector3();

    static final Vector3[] FACE0 = {CORNER000, CORNER100, CORNER110, CORNER010};
    static final Vector3[] FACE1 = {CORNER101, CORNER001, CORNER011, CORNER111};
    static final Vector3[] FACE2 = {CORNER000, CORNER010, CORNER011, CORNER001};
    static final Vector3[] FACE3 = {CORNER101, CORNER111, CORNER110, CORNER100};
    static final Vector3[] FACE4 = {CORNER101, CORNER100, CORNER000, CORNER001};
    static final Vector3[] FACE5 = {CORNER110, CORNER111, CORNER011, CORNER010};
    static final Vector3[][] FACES = {FACE0, FACE1, FACE2, FACE3, FACE4, FACE5};

    static final Vector3 NORMAL0 = new Vector3();
    static final Vector3 NORMAL1 = new Vector3();
    static final Vector3 NORMAL2 = new Vector3();
    static final Vector3 NORMAL3 = new Vector3();
    static final Vector3 NORMAL4 = new Vector3();
    static final Vector3 NORMAL5 = new Vector3();
    static final Vector3[] NORMALS = {NORMAL0, NORMAL1, NORMAL2, NORMAL3, NORMAL4, NORMAL5};

    public Cube(float x, float y, float z, float width, float height, float depth, int index, 
        VertexAttributes vertexAttributes, float[] meshVertices){
    position.set(x,y,z);
    this.halfWidth = width/2;
    this.halfHeight = height/2;
    this.halfDepth = depth/2;
    this.index = index;


    vertexFloatSize = vertexAttributes.vertexSize/4; //4 bytes per float
    posOffset = getVertexAttribute(Usage.Position, vertexAttributes).offset/4;
    norOffset = getVertexAttribute(Usage.Normal, vertexAttributes).offset/4;

    VertexAttribute colorAttribute = getVertexAttribute(Usage.Color, vertexAttributes);
    hasColor = colorAttribute!=null;
    if (hasColor){
        colOffset = colorAttribute.offset/4;
        this.setColor(Color.WHITE, meshVertices);
    }
    transformDirty = true;
    }

    public void updateCameraDistance(Camera cam){
    camDistSquared = cam.position.dst2(position);
    }

    /**
     * Call this after moving and/or rotating.
     */
    public void update(float[] meshVertices){

    if (transformDirty){
        transformDirty = false;

        CORNER000.set(-halfWidth,-halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER010.set(-halfWidth,halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER100.set(halfWidth,-halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER110.set(halfWidth,halfHeight,-halfDepth).rot(rotationTransform).add(position);
        CORNER001.set(-halfWidth,-halfHeight,halfDepth).rot(rotationTransform).add(position);
        CORNER011.set(-halfWidth,halfHeight,halfDepth).rot(rotationTransform).add(position);
        CORNER101.set(halfWidth,-halfHeight,halfDepth).rot(rotationTransform).add(position);
        CORNER111.set(halfWidth,halfHeight,halfDepth).rot(rotationTransform).add(position);

        NORMAL0.set(0,0,-1).rot(rotationTransform);
        NORMAL1.set(0,0,1).rot(rotationTransform);
        NORMAL2.set(-1,0,0).rot(rotationTransform);
        NORMAL3.set(1,0,0).rot(rotationTransform);
        NORMAL4.set(0,-1,0).rot(rotationTransform);
        NORMAL5.set(0,1,0).rot(rotationTransform);

        for (int faceIndex= 0; faceIndex<6; faceIndex++){
        int baseVertexIndex = (index*24 + faceIndex*4)*vertexFloatSize;//24 unique vertices per cube, 4 unique vertices per face
        for (int cornerIndex=0; cornerIndex<4; cornerIndex++){
            int vertexIndex = baseVertexIndex + cornerIndex*vertexFloatSize + posOffset;
            meshVertices[vertexIndex] = FACES[faceIndex][cornerIndex].x;
            meshVertices[++vertexIndex] = FACES[faceIndex][cornerIndex].y;
            meshVertices[++vertexIndex] = FACES[faceIndex][cornerIndex].z;

            vertexIndex = baseVertexIndex + cornerIndex*vertexFloatSize + norOffset;
            meshVertices[vertexIndex] = NORMALS[faceIndex].x;
            meshVertices[++vertexIndex] = NORMALS[faceIndex].y;
            meshVertices[++vertexIndex] = NORMALS[faceIndex].z;
        }
        }
    }

    if (colorDirty){
        colorDirty = false;

        for (int faceIndex= 0; faceIndex<6; faceIndex++){
        int baseVertexIndex = (index*24 + faceIndex*4)*vertexFloatSize;//24 unique vertices per cube, 4 unique vertices per face
        for (int cornerIndex=0; cornerIndex<4; cornerIndex++){
            int vertexIndex = baseVertexIndex + cornerIndex*vertexFloatSize + colOffset;
            meshVertices[vertexIndex] = color.r;
            meshVertices[++vertexIndex] = color.g;
            meshVertices[++vertexIndex] = color.b;
            meshVertices[++vertexIndex] = color.a;
        }
        }
    }
    }

    public Cube setColor(Color color, float[] meshVertices){
    if (hasColor){
        this.color.set(color);
        colorDirty = true;

    }
    return this;
    }

    public void setIndex(int index){
    if (this.index != index){
        transformDirty = true;
        colorDirty = true;
        this.index = index;
    }
    }

    public Cube translate(float x, float y, float z){
    position.add(x,y,z);
    transformDirty = true;
    return this;
    }

    public Cube translateTo(float x, float y, float z){
    position.set(x,y,z);
    transformDirty = true;
    return this;
    }

    public Cube rotate(float axisX, float axisY, float axisZ, float degrees){
    rotationTransform.rotate(axisX, axisY, axisZ, degrees);
    transformDirty = true;
    return this;
    }

    public Cube rotateTo(float axisX, float axisY, float axisZ, float degrees){
    rotationTransform.idt();
    rotationTransform.rotate(axisX, axisY, axisZ, degrees);
    transformDirty = true;
    return this;
    }

    public VertexAttribute getVertexAttribute (int usage, VertexAttributes attributes) {
    int len = attributes.size();
    for (int i = 0; i < len; i++)
        if (attributes.get(i).usage == usage) return attributes.get(i);

    return null;
    }

    @Override
    public int compareTo(Cube other) {
    //This is a simple sort based on center point distance to camera. A more 
    //sophisticated sorting method might be required if the cubes are not all the same 
    //size (such as calculating which of the 8 vertices is closest to the camera
    //and using that instead of the center point).
    if (camDistSquared>other.camDistSquared)
        return -1;
    return camDistSquared<other.camDistSquared ? 1 : 0;
    }
}

以下是如何对它们进行排序的方法:
for (Cube cube : mBatchedCubes){
    cube.updateCameraDistance(camera);
}
mBatchedCubes.sort();
int index = 0;
for (Cube cube : mBatchedCubes){
    cube.setIndex(index++);
}

1
我更新了它,支持为每个立方体设置颜色,并使其更加稳健,以应对各种顶点属性使用的组合。但是,如果您想支持 alpha 通道,则会变得更加复杂。每当它们移动时,您必须将所有立方体从远到近进行排序。通过排序,我的意思是更改它们的“索引”参数,使最远的一个为0,然后从那里开始逐步增加。更改索引参数时,还应将“dirty”标记为 true。 - Tenfour04
1
我为 index 添加了一个 setter,因为如果索引发生更改,它将始终需要更新颜色和变换。只要清楚,无论你将它们存储在什么数组中,它们的顺序都不重要。只有它们的 index 参数值对于对它们进行排序很重要。 - Tenfour04
1
我还没有在环境中测试过——OP使用的是纯色立方体,所以我只是复制了那个。所以是的,你需要改变用法才能让它起作用。你使用的是半透明颜色吗?如果是的话,这会变得非常复杂,因为你必须每帧按从后往前的顺序对立方体进行排序。同时确保启用了背面剔除,否则你还必须根据它们相对于相机视图的旋转方式对立方体的面进行排序!当你说有些面缺失时,这听起来可能是深度测试关闭了,所以立方体的背面绘制在了前面。 - Tenfour04
1
Gdx.gl.glEnable(GL20.GL_DEPTH_TEST); 应放在您的 Shader 子类的 begin 方法中。Gdx.gl.glCullFace(GL20.GL_BACK); 也应该放在您的 Shader 子类的 begin 方法中。 - Tenfour04
1
尝试使用Gdx.gl.glCullFace(GL20.GL_FRONT);代替。 - Tenfour04
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接