Trouble batching cubes in libgdx

試著忘記壹切 提交于 2019-12-19 12:00:09

问题


I am trying to develop a game where I am rendering up to 300 cubes on screen. The performance of modelBatch when creating new modelInstance for each cube is terrible. There is no 3d batch that batches all the cubes to one draw call as far as I know. So I am desperately trying to batch them somehow.

This question is directly related to this one: LibGDX 3D increase perfomance

The answer posted batches all the cubes successfully but when environment is added to get some lighting it appears that the cubes have missing sides or something else is wrong with them.

Heres a picture:

Here is my cube class (pretty much copied from answer above)

public class Cube {

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

  float[] meshVertices;

  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;
      this.meshVertices = meshVertices;

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

      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);
      }
      transformDirty = true;

  }

  public void setDimensions(float x, float y , float z){

      this.halfWidth = x/2;
      this.halfHeight = y/2;
      this.halfDepth = z/2;

  }

  public void setIndex(int index){

      this.index = index;
      transformDirty = true;
      colorDirty = true;

  }

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

      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 void setAlpha(float alpha) {

       if (hasColor){

           this.color.set(this.color.r, this.color.g, this.color.b, alpha);
           colorDirty = true;

       }
  }

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

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

public Cube setPosition(Vector3 position1) {

       position.set(position1);
       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;
  }



  public Vector3 getPosition() {

      return this.position;
  }

}

And here is a test case that i created to test the cubes.

public class TestCase {

    ModelInstance mBatchedCubesModelInstance;
    Mesh mBatchedCubesMesh;
    float[] mBatchedCubesVertices;
    Array<Cube> mBatchedCubes;

    TestCase(){

            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 | Usage.Color), new Material());
            for (int i=0; i<numCubes; i++){
                mpb.box(1, 1, 1);
            }
            Model model = mb.end();
            mBatchedCubesModelInstance = new ModelInstance(model);

            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 ).setColor(Colors.getMixedColor()));
                            }
                    }
            }

    }

    public void render(ModelBatch batch, Environment environment){

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

            if(environment!=null) batch.render(mBatchedCubesModelInstance,environment);
            else batch.render(this.mBatchedCubesModelInstance);

    }

}

The cubes don't even move in my game so I don't even need the transformation. I just need to set the colors (including alpha channel) every frame. The batched mesh should work with libgdx shadow mapping also (I hope it automatically works when you have a properly batched modelInstance).

Could anyone tell me what is wrong with my code and why some faces don't show up. I understand I might be asking much so I will put a bounty on this question (50 points) after two days.

EDIT: After the answer from Tenfour04 things got a lot better. Alpha channel seems to work, strange face problem seems to have disappeared. However When I applied the changes to my real game I noticed that sometimes flour draws on top of in game terrain. I updated the test to illustrate the problem by adding big plane in the middle of cubes. Made a video: https://www.youtube.com/watch?v=LQhSMJfuyZY.

I would also like to clarify that I am not using any custom shader. Using only ModelBatch.begin() and .end() methods with no additional openGl calls.


回答1:


I see a couple problems:

  1. You have blending off and/or your shader isn't setting the alpha value

  2. You aren't culling back faces.

So when you create your new material, instead of just using new Material(), use:

new Material(
    IntAttribute.createCullFace(GL20.GL_FRONT),//For some reason, libgdx ModelBuilder makes boxes with faces wound in reverse, so cull FRONT
    new BlendingAttribute(1f), //opaque since multiplied by vertex color
    new DepthTestAttribute(false), //don't want depth mask or rear cubes might not show through
    ColorAttribute.createDiffuse(Color.WHITE) //white since multiplied by vertex color
    );

You will also need to sort the cubes by distance from camera to get their alphas to layer correctly. Here's an updated Cube class that supports sorting. It has to be able to track color independently from the vertices array in case its index changes:

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) {
    //the cube has a lower index than a cube that is closer to the camera
    if (camDistSquared>other.camDistSquared)
        return -1;
    return camDistSquared<other.camDistSquared ? 1 : 0;
    }
}

And you would sort it like this:

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


来源:https://stackoverflow.com/questions/24724359/trouble-batching-cubes-in-libgdx

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!