二、加载模型

本篇教程用到的模型资源点击 飞船模型资源下载 进行下载。

您可以使用自己喜欢的建模软件来创建一个模型。这里我使用了 libGDX 的 gdx-invaders 附带的飞船模型,你可以在这里找到。您可以将其解压缩到资源文件夹中。请注意,这里主要用到下面三个文件(ship.g3db是转换后的模型,这里可以先不管,本篇教程后面会详细说明),必须包含在同一文件夹中:

  • ship.obj: the wavefront model file we're going to load
  • ship.mtl: the wavefront material file the model uses
  • ship.png: the texture the material uses

image.png

本教程这个模型采用 OBJ 文件格式。但是在 libGDX 中 OBJ 格式支持不好,应避免使用。在本教程的后面,我们会把模型转换为更合适的文件格式。如果使用自己的(OBJ)模型,它可能会无法正确渲染。

我们在第一篇教程的基础上,更改其创建立方体的部分,将其换成导入我们的模型:

@Override
	public void create () {
		env = new Environment();
		env.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f));
		env.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f));
		
		modelBatch = new ModelBatch();
		
		cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		cam.position.set(1f, 1f, 1f);
		cam.lookAt(0,0,0);
		cam.near = 1f;
		cam.far = 300f;
		cam.update();
		
		camCtrol = new CameraInputController(cam);
		Gdx.input.setInputProcessor(camCtrol);
		
		ModelLoader loader = new ObjLoader();
		model = loader.loadModel(Gdx.files.internal("model/ship/ship.obj"));
		instance = new ModelInstance(model);
}

这里只有一些变化。首先,我把相机放得离原点更近,因为船模型很小。接下来,我们删除了ModelBuilder,然后创建了一个ModelLoader,让它加载飞船模型,效果如下。

image.png

在更大的应用程序中,要性能最优的话,需要使用AssetManager来管理模型。因此,让我们添加AssetManager:

public class MainGame implements ApplicationListener {
	private  CameraInputController camCtrol;
	private Environment env;
	public PerspectiveCamera cam;
	private Model model;
	private ModelInstance instance;
	private ModelBatch modelBatch;
	
	private AssetManager assets;
	private Array<ModelInstance> instances = new Array<>();
	
	private boolean loading;
	
	@Override
	public void create() {
		env = new Environment();
		env.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f));
		env.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f));
		
		modelBatch = new ModelBatch();
		
		cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		cam.position.set(1f, 1f, 1f);
		cam.lookAt(0,0,0);
		cam.near = 1f;
		cam.far = 300f;
		cam.update();
		
		camCtrol = new CameraInputController(cam);
		Gdx.input.setInputProcessor(camCtrol);
		
		assets = new AssetManager();
		assets.load("model/ship/ship.obj", Model.class);
		
		loading = true;
	}
	
	private void doneLoading() {
		Model ship = assets.get("model/ship/ship.obj", Model.class);
		ModelInstance shipInstance = new ModelInstance(ship); 
		instances.add(shipInstance);
		loading = false;
	}
	
	@Override
	public void resize (int width, int height) {
	}

	@Override
	public void render () {
		Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
		
		camCtrol.update();
		
		if (loading && assets.update()) {
			doneLoading();
		}
		camCtrol.update();
		
		Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
		Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);

		modelBatch.begin(cam);
		modelBatch.render(instances, env);
		modelBatch.end();
		
	}

	@Override
	public void pause () {
	}

	@Override
	public void resume () {
	}

	@Override
	public void dispose () {
		modelBatch.dispose();
		instances.clear();
		assets.dispose();
	}
}

我们删除了模型实例,并将其替换为AssetManager。我们将其替换为一组实例,而不是一个ModelInstance,这是一个更现实的场景,允许我们以后渲染更多的Instance。我们还添加了一个标志,表明我们是否仍在加载。

现在在Create方法中,我们创建资产管理器,并告诉它加载飞船模型。接下来我们设置加载标志,这样我们就知道需要更新assetmanager。在我们的渲染方法中,我们检查是否设置了加载标志,如果设置了,我们调用 assets.update()。如果是 assets.update() 返回true,我们知道它已经完成加载,所以我们调用了一个名为doneLoading()的新方法。此外,在render方法中,我们渲染所有实例,而不是仅渲染一个实例。如果 assets 尚未加载,这意味着数组为空。

新方法doneLoading()获取我们刚刚加载的船模型,创建一个名为shipInstance的实例,并将其添加到实例数组中,从而使其呈现。最后,我们需要将加载标志设置为false,这样就可以删除 assets。不再 assets.update() 方法。

由于我们现在支持多个模型实例,所以让我们再添加几个。

@Override
	public void create () {
		...
		cam.position.set(7f, 7f, 7f);
		...
	}

	private void doneLoading() {
		Model ship = assets.get("data/ship.obj", Model.class);
		for (float x = -5f; x <= 5f; x += 2f) {
			for (float z = -5f; z <= 5f; z += 2f) {
				ModelInstance shipInstance = new ModelInstance(ship);
				shipInstance.transform.setToTranslation(x, 0, z);
				instances.add(shipInstance);
			}
		}
		loading = false;
	}

在这里,我们把摄像机移到远离原点的地方,这样我们就能看到我们所有的船只。请注意,您也可以滚动鼠标进行放大或缩小。在doneLoad方法中,我们现在创建多个实例,并将它们放置在XZ平面上的网格中。

image.png

使用 obj 文件进行测试还可以。但它不适合在实际应用中使用,因为文件格式不包含足够的信息来渲染复杂模型。事实上,libGDX附带的ObjLoader仅用于测试,并没有实现您可能想要使用的所有功能。

因此我们需要使用 fbx-conv 工具,它将从建模软件导出的模型转换为适合使用libGDX 进行渲染的格式。与名称不同,fbx-conv 工具适合转换许多模型文件格式(包括obj),尽管fbx是首选的模型文件格式,因为几乎每个建模应用程序都支持这种格式。libGDX 支持两种文件格式:g3dj(为了便于调试,它是json文本格式)和g3db(在发布时应该使用二进制格式,因为它更小,加载速度更快)。

fbx-conv的使用教程可以点击这里阅读,我们转换后可以得到如下 ship.g3db 的模型:

image.png

我们在代码中就可以使用转换后的 g3db 模型了。

@Override
	public void create () {
		...
		assets.load("model/ship/ship.g3db", Model.class);
		...
	}

	private void doneLoading() {
		Model ship = assets.get("model/ship/ship.g3db", Model.class);
		...
	}