我正在尝试开发一款游戏,我在屏幕上渲染多达300个立方体。在为每个多维数据集创建新的modelInstance时,modelBatch的性能非常糟糕。据我所知,没有3D批处理将所有立方体批量打包到一个绘图调用。所以我拼命地尝试以某种方式批量处理它们。
此问题与此问题直接相关:LibGDX 3D increase perfomance
发布的答案成功批量处理所有立方体,但是当添加环境以获得某些照明时,看起来立方体缺少侧面或其他东西是错误的。
下图:
这是我的立方体类(几乎从上面的答案复制)
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;
}
}
这是我为测试多维数据集而创建的测试用例。
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);
}
}
立方体甚至不会在我的游戏中移动,所以我甚至不需要转换。我只需要每帧设置颜色(包括alpha通道)。批处理网格也应该与libgdx阴影贴图一起使用(我希望当你有一个正确批处理的modelInstance时它会自动运行)。
有人能告诉我我的代码有什么问题以及为什么有些面孔不会出现。我知道我可能会问得太多,所以我会在两天后对这个问题(50分)给予赏金。
编辑:在Tenfour04的回答之后,事情变得更好了。 Alpha通道似乎工作,奇怪的脸问题似乎已经消失。然而,当我将更改应用到我的真实游戏中时,我注意到有时面粉会在游戏地形中显示出来。我更新了测试,通过在立方体中间添加大平面来说明问题。制作视频:https://www.youtube.com/watch?v=LQhSMJfuyZY。
我还想澄清一点,我没有使用任何自定义着色器。仅使用ModelBatch.begin()和.end()方法,无需额外的openGl调用。
答案 0 :(得分:1)
我发现了几个问题:
您已关闭混合和/或着色器未设置Alpha值
你没有剔除背面。
因此,当您创建新材料时,请使用:
,而不是仅使用new Material()
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
);
您还需要按距离相机的距离对多维数据集进行排序,以使其alpha值正确分层。这是一个支持排序的更新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) {
//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;
}
}
你会这样排序:
for (Cube cube : mBatchedCubes){
cube.updateCameraDistance(camera);
}
mBatchedCubes.sort();
int index = 0;
for (Cube cube : mBatchedCubes){
cube.setIndex(index++);
}