我正在开发一款 3D 游戏。
游戏需要大约 100 个立方体才能运行,所有立方体都是动态的。
我真的不知道像这样的游戏需要多少性能,但我正在测试 tablet与 Mali-400 MP2 GPU,1 GB 内存,1.5 GHz 双核。我知道如何在一个网格中渲染所有立方体,但我无法单独移动所有立方体。
这个设置给了我一个非常不稳定的 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 fps
最佳答案
您可以像这样将所有多维数据集批处理到单个模型和模型实例中:
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 个唯一顶点的任何网格,因此您可以添加纹理坐标,例如。
这也依赖于位置,然后法线始终是网格的前两个使用属性,因此希望这在 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 限制。如果您不经常旋转它们,创建一个单独的“脏”变量用于旋转可能会有所帮助,并且仅在更新方法中需要时才旋转。
编辑:更新自 this question
如果要有透明度,那么立方体必须是可排序的,这样才能从远到近的顺序进行绘制。它们的 index
值必须更新为新顺序,因为这是它们在网格中的排序方式。这是一个支持排序的立方体类(现在必须独立跟踪颜色,因为立方体可能会移动到网格的不同部分)。
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++);
}
关于java - LibGDX 3D 提高性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24098977/