LibGDX 3D 提高性能

2022-01-12 00:00:00 3d opengl-es android java libgdx

我正在开发一款 3D 游戏.

I'm working on a 3D game.

游戏需要大约 100 个立方体才能工作,所有立方体都是动态的.

The game requires around 100 cubes to work, all cube have be dynamic.

我真的不知道这样的游戏需要多少性能,但我正在使用 平板电脑 与 Mali-400 MP2 GPU,1 GB 内存,1.5 GHz 双核.我知道在一个网格中渲染所有立方体,但是我不能单独移动它们.

I don't really know how much perfomance is required for a game like this, but i'm testing with a tablet with Mali-400 MP2 GPU, 1 GB ram, 1.5 GHz dual core. I know about rendering all of the cubes in one mesh, but then i can't move all of them separately.

这个设置给了我一个非常摇摆不定的 fps.在 20 到 50 之间跳跃,大多在 30 岁以下.(在模拟器 10-15 中)

This setup gives me a very vacillating fps. Jumping between 20 and 50, mostly under 30. (In emulator 10-15)

游戏开始时,我构建了一个 ModelInstances 的数组列表,它们都使用相同的模型.

When the game starts, i build an arraylist of ModelInstances, all of them is using the same model.

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 fps

    Did some test with webGL too, same tablet, using chrome, rendering 125 cubes: stable 40-50 fps

    推荐答案

    您可以像这样将所有多维数据集批处理到单个 Model 和 ModelInstance 中:

    You can batch all your cubes into a single Model and ModelInstance like this:

    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);
    

    但棘手的部分是能够将这些立方体中的每一个移动到不同的位置并能够独立地操纵它们.

    But the tricky part is being able to move each of those cubes to a different location and be able to manipulate them independently.

    这是一个 Cube 类,它可以操作上述模型中的各个立方体.我认为理论上它应该适用于您创建的每个立方体使用 24 个唯一顶点的任何网格,因此您可以添加纹理坐标.

    Here's a Cube class that can manipulate the individual cubes from the above model. I think theoretically it should work with any Mesh you create that uses 24 unique vertices per cube, so you could add texture coordinates for example.

    这也依赖于位置,然后法线始终是网格的前两个 Usage 属性,因此希望这在 libGDX 的 Mesh 类中成立.

    This also relies on position and then normal always being the first two Usage attributes of the mesh, so hopefully that holds true in libGDX's Mesh class.

    这基本上是通过跟踪它在基础网格中的索引号(它是哪个立方体号)来工作的,这样它就可以在顶点数组中挑选出它需要更新的顶点.每一帧都需要将顶点复制到网格中.

    This works basically by keeping track of it's index number in the base mesh (which cube number it is) so it can pick out the vertices that it needs to update in the vertices array. The vertices need to be copied into the mesh each frame.

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

    要使用它,首先获取网格参考并创建立方体:

    To use this, first get a mesh reference and create the cubes:

        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 方法中:

    Then in your render method:

    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 限制.如果您不经常旋转它们,那么创建一个单独的脏"变量进行旋转可能会有所帮助,并且仅在必要时在更新方法中旋转.

    Now CPU vertex manipulation is not as efficient as shader vertex manipulation, so this may become CPU bound if you're moving all your cubes around every frame. If you aren't rotating them much, it would probably help to create a separate "dirty" variable for rotation and only rotate if necessary in the update method.

    更新自 这个问题

    如果你想有透明度,那么立方体必须是可排序的,所以它们可以从远到近排序以进行绘制.它们的 index 值必须更新为新顺序,因为这是它们在网格中的排序方式.这是一个支持排序的 Cube 类(现在必须独立跟踪颜色,因为立方体可能会移动到网格的不同部分).

    If you want to have transparency, then the cubes must be sortable, so they can be ordered from far to near for drawing. Their index values must be updated to the new order since that is how they are ordered into the mesh. Here is a Cube class that supports sorting (the color has to be tracked independently now since the cube might be moved to a different part of the mesh).

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

    以下是您对它们的排序方式:

    Here is how you would sort them:

    for (Cube cube : mBatchedCubes){
        cube.updateCameraDistance(camera);
    }
    mBatchedCubes.sort();
    int index = 0;
    for (Cube cube : mBatchedCubes){
        cube.setIndex(index++);
    }
    

相关文章