zoukankan      html  css  js  c++  java
  • Libgdx New 3D API 教程之 -- 加载3D场景的背后-第二部分

    http://bbs.9ria.com/thread-221699-1-1.html

    在本教程的第一部分,我们已经看过LibGDX 3D API中Model类的总体结构。在第2部分中,我们将会分析渲染管道,从加载模型开始,到真正的渲染模型。我们将不会在渲染管道的某个问题上进行深入探讨。我们只会介绍一些非常基本的内容,这是我觉得你使用3D API时,应该了解的。

      在这一部分,我们要分析渲染究竟做了什么。明白我们在渲染时所做的事很重要。在前一部分本教程,我们已经看到,一个Model是由很多个Node组成,而Node由NodePart组成。一个NodePart是组成模型最小的部分,包含了所有在渲染时所需要的信息。它包含一个MeshPart,描述要渲染什么(形状),它包含一个Material,描述应该如何渲染。阅读这一部分教程时,一定要记得这些概念。

      我们以Loading a scene with Libgdx的教程为基础(参考译文:使用Libgdx加载3D场景)。我们需要拆开代码,来看一看场景后面的实际情况,因此,你可以需要备份,或复制一份以进行新的工作,这里给出参考代码:

    1. public class SceneTest implements ApplicationListener {
    2. public PerspectiveCamera cam;
    3. public CameraInputController camController;
    4. public ModelBatch modelBatch;
    5. public AssetManager assets;
    6. public Array<ModelInstance> instances = new Array<ModelInstance>();
    7. public Lights lights;
    8. public boolean loading;
    9. public Array<ModelInstance> blocks = new Array<ModelInstance>();
    10. public Array<ModelInstance> invaders = new Array<ModelInstance>();
    11. public ModelInstance ship;
    12. public ModelInstance space;
    13. @Override
    14. public void create () {
    15. modelBatch = new ModelBatch();
    16. lights = new Lights();
    17. lights.ambientLight.set(0.4f, 0.4f, 0.4f, 1f);
    18. lights.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f));
    19. cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
    20. cam.position.set(0f, 7f, 10f);
    21. cam.lookAt(0,0,0);
    22. cam.near = 0.1f;
    23. cam.far = 300f;
    24. cam.update();
    25. camController = new CameraInputController(cam);
    26. Gdx.input.setInputProcessor(camController);
    27. assets = new AssetManager();
    28. assets.load("data/invaders.g3db", Model.class);
    29. loading = true;
    30. }
    31. private void doneLoading() {
    32. Model model = assets.get("data/invaders.g3db", Model.class);
    33. for (int i = 0; i < model.nodes.size; i++) {
    34. String id = model.nodes.get(i).id;
    35. ModelInstance instance = new ModelInstance(model, id);
    36. Node node = instance.getNode(id);
    37. instance.transform.set(node.globalTransform);
    38. node.translation.set(0,0,0);
    39. node.scale.set(1,1,1);
    40. node.rotation.idt();
    41. instance.calculateTransforms();
    42. if (id.equals("space")) {
    43. space = instance;
    44. continue;
    45. }
    46. instances.add(instance);
    47. if (id.equals("ship"))
    48. ship = instance;
    49. else if (id.startsWith("block"))
    50. blocks.add(instance);
    51. else if (id.startsWith("invader"))
    52. invaders.add(instance);
    53. }
    54. loading = false;
    55. }
    56. @Override
    57. public void render () {
    58. if (loading && assets.update())
    59. doneLoading();
    60. camController.update();
    61. Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
    62. Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
    63. modelBatch.begin(cam);
    64. for (ModelInstance instance : instances)
    65. modelBatch.render(instance, lights);
    66. if (space != null)
    67. modelBatch.render(space);
    68. modelBatch.end();
    69. }
    70. @Override
    71. public void dispose () {
    72. modelBatch.dispose();
    73. instances.clear();
    74. assets.dispose();
    75. }
    76. @Override public void resume () {}
    77. @Override public void resize (int width, int height) {}
    78. @Override public void pause () {}
    79. @Override public void dispose () {}
    80. }
    复制代码


      在代码中,我们通过AssetManager来加载Model,在大多数情况下,这都是最好的办法。但有时,你可能需要对加载过程有更多的控制。所以,这次我们把AssetMnager删掉。

    1. public class SceneTest implements ApplicationListener {
    2. public PerspectiveCamera cam;
    3. public CameraInputController camController;
    4. public ModelBatch modelBatch;
    5. public Model model;
    6. public Array<ModelInstance> instances = new Array<ModelInstance>();
    7. public Lights lights;
    8. ...
    9. @Override
    10. public void create () {
    11. ...
    12. Gdx.input.setInputProcessor(camController);
    13. ModelLoader modelLoader = new G3dModelLoader(new JsonReader());
    14. ModelData modelData = modelLoader.loadModelData(Gdx.files.internal("data/invaders.g3dj"));
    15. model = new Model(modelData, new TextureProvider.FileTextureProvider());
    16. doneLoading();
    17. }
    18. private void doneLoading() {
    19. for (int i = 0; i < model.nodes.size; i++) {
    20. ...
    21. }
    22. }
    23. @Override
    24. public void render () {
    25. camController.update();
    26. ...
    27. }
    28. @Override
    29. public void dispose () {
    30. modelBatch.dispose();
    31. instances.clear();
    32. model.dispose();
    33. }
    34. ...
    35. }
    复制代码


      我们删掉了AssetManager,并通过手动的方式来加载Model,所以,我们在Model加载完成后,调用了doneLoading()。我们这里还是调用了在之前教程中创建的invaders.g3dj,而不是invaders.g3db。所以,确保你把这个文件拷贝到了项目里的assets文件夹中。现在看一下加载部分代码:

    1. ModelLoader modelLoader = new G3dModelLoader(new JsonReader());
    2. ModelData modelData = modelLoader.loadModelData(Gdx.files.internal("data/invaders.g3dj"));
    3. model = new Model(modelData, new TextureProvider.FileTextureProvider());
    复制代码


      我们创建了一个ModelLoader,这个在之前的使用Libgdx加载3D场景一讲中已经用到过。但我们不再使用ObjLoader,而是创建G3dModelLoader。我们传一个JsonReader参数给构造函数,因为invader.g3dj就是一个json文件。如果是g3db,那你可以使用UBJsonReader。

      然后,加载ModelData。ModelData类中,包含了模型的原始数据,本质上,这个类与我们之前分析过的文件格式是一一对应的。它不含有任何源文件,如它有一个float数组来表示Mesh,用文件名来指定纹理,而不是包含文件本身。所以,现阶段,不管Model class,或其他资源文件如何,你都可以随意修改它。

      结尾,在create()方法的最后一行,我们通过刚刚得到的ModelData,创建了一个Model对象。我们还传进去一个TextureProvider参数,我们要用它来加载纹理文件。想要更多的控制加载过程,你可以自己实现TextureProvider接口。如果通过AssetManager来加载模型,那加载纹理也可以通过AssetManager。现在Model和它的资源,如Meshes和Textures,都已经加载了。还有,Model也可用于最后的资源回收(disposing)。

      现在来看看Materials怎么玩:

    1. private void doneLoading() {
    2. Material blockMaterial = model.getNode("block1").parts.get(0).material;
    3. ColorAttribute colorAttribute = (ColorAttribute)blockMaterial.get(ColorAttribute.Diffuse);
    4. colorAttribute.color.set(Color.YELLOW);
    5. for (int i = 0; i < model.nodes.size; i++) {
    6. ...
    7. }
    8. }
    复制代码


      第一行,我们得到了模型的block1节点(Node),这是我们已知的。得到它的第一个node-part,与它的material。我们在前几章看到过,这个material其实就是block_default1的一个引用,而它会被所有的block节点共享使用。所以,改变这个值,所有的block就都跟着变了。第二行,我们拿到material的Diffuse ColorAttribute,也是我们一早知道的。最后设置成黄色。

    <ignore_js_op>behindthescenes1-300x236.png

    2013-6-27 18:08:34 上传
    下载附件 (128.7 KB)
     


      这看起来需要对模型文件有很详细的了解,让我们看看另一种方法:

    1. private void doneLoading() {
    2. Material blockMaterial = model.getMaterial("block_default1");
    3. blockMaterial.set(ColorAttribute.createDiffuse(Color.YELLOW));
    4. for (int i = 0; i < model.nodes.size; i++) {
    5. ...
    6. }
    7. }
    复制代码


      同样的结果,但我们通过ID直接得到了material。并且,我们没有去得到当前的diffuse color值,而只是设置。这样,如果这个材质里没有这个属性,就添加上去,如果有,就覆盖。

      改变模型材质,会影响到改变后创建的ModelInstance。你可以为每一个instance做改变:

    1. private void doneLoading() {
    2. for (int i = 0; i < model.nodes.size; i++) {
    3. ...
    4. }
    5. for (ModelInstance block : blocks) {
    6. float r = 0.5f + 0.5f * (float)Math.random();
    7. float g = 0.5f + 0.5f * (float)Math.random();
    8. float b = 0.5f + 0.5f * (float)Math.random();
    9. block.materials.get(0).set(ColorAttribute.createDiffuse(r, g, b, 1));
    10. }
    11. }
    复制代码


      没有通过节点,也没有通过ID,我们只是拿到第一个材质,因为ModelInstance也需要指定一个Material:

    <ignore_js_op>behindthescenes2-300x236.png

    2013-6-27 18:08:36 上传
    下载附件 (128.36 KB)
     


      之前,通过查看G3DJ文件,我们看过Model的结构了,现在来看看ModelInstance class.

    1. public class ModelInstance implements RenderableProvider {
    2. public final Array<Material> materials = new Array<Material>();
    3. public final Array<Node> nodes = new Array<Node>();
    4. public final Array<Animation> animations = new Array<Animation>();
    5. public final Model model;
    6. public Matrix4 transform;
    7. public Object userData;
    8. ...
    9. }
    复制代码


      和Model差不多,它有Material,Node,和Animation的数组各一个。这些是在构建ModelInstance对象时,从Model对象中复制过来的,这样,你在改变ModelInstance的时候,不会影响到Model对象。若你在创建ModelInstances的时候,指定了Node ID,将仅有指定的material和animation,被复制到ModelInstance中,也仅作用于这一个ModelInstance对象。因此,好像我们已经创建的Block ModelInstance对象一样,我们知道,第一个material,只会对指定的block node有影响。

      注意,与Model类不同的地方,ModelInstance不包括Mesh和MeshPart数组。这些没被复制过来,取而代之的是指定Node(NodePart)的引用。所以,Meshes中的信息是被多个Model Instances共享的。对于材质中可能包含的纹理也是这样。

      在ModelInstance类中的Model,是在创建ModelInstance时建立的指向Model的一个引用。transform代表了这个ModelInstance的position(位置), rotation(旋转), 和scale(缩放)信息。这些知识,在之前加载场景的那一篇教程中都看过了。值得一提的是,这个值不是final的,所以,需要的话,你可以指定一个Matrix4引用。最后,userData是一个用户可以自定义的值,设置任何你想要的数据在这里。比如,你可以为你的shader放一些instructions.(我对shader不了解,不会译咯: supply extra instructions to your shader)。

      ModelInstance是由RenderableProvider接口实现而来。当我们调用modelBatch.render(instance, lights)时,ModelBatch看的是这是不是一个RenderableProvider对象,而不是ModelInstance。任何一个继承于RenderableProvider的类,都可以提供可渲染的对象给ModelBatch。看一看 Renderable 类:

    1. public class Renderable {
    2. /** the model transform **/
    3. public final Matrix4 worldTransform = new Matrix4();
    4. /** the mesh to render **/
    5. public Mesh mesh;
    6. /** the offset into the mesh's indices **/
    7. public int meshPartOffset;
    8. /** the number of indices/vertices to use **/
    9. public int meshPartSize;
    10. /** the primitive type, encoded as an OpenGL constant, like {@link GL20#GL_TRIANGLES} **/
    11. public int primitiveType;
    12. /** the material to be applied to the mesh **/
    13. public Material material;
    14. /** the bones transformations used for skinning, or null if not applicable */
    15. public Matrix4 bones[];
    16. /** the lights to be used to render this Renderable, may be null **/
    17. public Lights lights;
    18. /** the Shader to be used to render this Renderable, may be null **/
    19. public Shader shader;
    20. /** user definable value. */
    21. public Object userData;
    22. }
    复制代码


      对比以前看到的NodePart,它是是一个模型最小的单位,描述了应该怎么渲染这个模型,模型含有一个MeshPart和一个Material。而Renderable对象,也有这三个值,同时,还定义了transform, lights, shader和userData。所以,当你调用ModelBatch.render(ModelInstance)时,这个ModelInstance中所有的node parts都会被转换成Renderable实例,并传送给ModelBatch. 我们手动实现一下:

    1. public class RenderableTest implements ApplicationListener {
    2. public PerspectiveCamera cam;
    3. public CameraInputController camController;
    4. public ModelBatch modelBatch;
    5. public Model model;
    6. public Lights lights;
    7. public Renderable renderable;
    8. @Override
    9. public void create () {
    10. ...
    11. cam.position.set(2f, 2f, 2f);
    12. ...
    13. model = new Model(modelData, new TextureProvider.FileTextureProvider());
    14. NodePart blockPart = model.getNode("ship").parts.get(0);
    15. renderable = new Renderable();
    16. renderable.mesh = blockPart.meshPart.mesh;
    17. renderable.meshPartOffset = blockPart.meshPart.indexOffset;
    18. renderable.meshPartSize = blockPart.meshPart.numVertices;
    19. renderable.primitiveType = blockPart.meshPart.primitiveType;
    20. renderable.material = blockPart.material;
    21. renderable.lights = lights;
    22. renderable.worldTransform.idt();
    23. }
    24. @Override
    25. public void render () {
    26. camController.update();
    27. Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
    28. Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
    29. modelBatch.begin(cam);
    30. modelBatch.render(renderable);
    31. modelBatch.end();
    32. }
    33. @Override
    34. public void dispose () {
    35. modelBatch.dispose();
    36. model.dispose();
    37. }
    38. ...
    39. }
    复制代码


      这里,我们像以前一样,加载了,invaders.g3dj,但我们要取得ship节点的第一个NodePart。通过它,我们创建了一个Renderable对象,设定了一系列相应的值。同时我们设定了light,将worldTransform的位移设定回了原点。没有旋转,和缩放。我们移除了ModelInstances,取而代之的是,在render方法中,我们只将renderable对象,传给了ModelBatch。我把镜头向原点拉近了一些,可以看得清楚些。

    <ignore_js_op>behindthescenes3-300x236.png

    2013-6-27 18:08:34 上传
    下载附件 (20.34 KB)
     


      一个ModelInstance限制了它所包含的renderable对象,而ModelBatch负责渲染这些renderable对象。事实上,渲染还不是ModelBatch做的,它仅仅是将Renderable排个序,最优化Renderable的渲染顺序,然后将他们传给可渲染的shader。如果没指定,或者指定了不合适的shader,那ModelBatch会帮你创建一个。通过调用ShaderProvider来获得一个shader。这里我们暂时还不深入,但是你可以记下,你可以在创建ModelBatch时,指定你自己的ShaderProvider。

      所以,我们知道了,Shader才是负责渲染Renderable对象的。它会负责一切渲染的工作,来呈现你的Renderable对象。叫法可能不同,建议称之为OpenGL ES 1.x shader。对于OpenGL ES 2.0来说,它还封装了一个ShaderProgram,并且对于不同的Renderable对象,可以设计相应的uniforms和attributes。

    1. public class RenderableTest implements ApplicationListener {
    2. public PerspectiveCamera cam;
    3. public CameraInputController camController;
    4. public Shader shader;
    5. public RenderContext renderContext;
    6. public Model model;
    7. public Lights lights;
    8. public Renderable renderable;
    9. @Override
    10. public void create () {
    11. lights = new Lights();
    12. ...
    13. model = new Model(modelData, new TextureProvider.FileTextureProvider());
    14. NodePart blockPart = model.getNode("ship").parts.get(0);
    15. renderable = new Renderable();
    16. renderable.mesh = blockPart.meshPart.mesh;
    17. renderable.meshPartOffset = blockPart.meshPart.indexOffset;
    18. renderable.meshPartSize = blockPart.meshPart.numVertices;
    19. renderable.primitiveType = blockPart.meshPart.primitiveType;
    20. renderable.material = blockPart.material;
    21. renderable.lights = lights;
    22. renderable.worldTransform.idt();
    23. renderContext = new RenderContext(new DefaultTextureBinder(DefaultTextureBinder.WEIGHTED, 1));
    24. shader = new DefaultShader(renderable.material,
    25. renderable.mesh.getVertexAttributes(),
    26. true, false, 1, 0, 0, 0);
    27. shader.init();
    28. }
    29. @Override
    30. public void render () {
    31. camController.update();
    32. Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
    33. Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
    34. renderContext.begin();
    35. shader.begin(cam, renderContext);
    36. shader.render(renderable);
    37. shader.end();
    38. renderContext.end();
    39. }
    40. @Override
    41. public void dispose () {
    42. shader.dispose();
    43. model.dispose();
    44. }
    45. ...
    46. }
    复制代码


      上面的代码中,我们删掉了ModelBatch, 并且添加了一个RenderContext和Shader。RenderContext保存了OpenGL的状态信息,从而避免了shader切换时的状态改变。例如,已经绑定了一个Texture,不用再次绑定了,我们使用一个包含了纹理绑定信息的DefaultTextureBinder来构造一个RenderContext,从而避免了再次绑定纹理。然后, 我们通过一些参数,如灯光什么的,新建一个shader作为DefaultShader。注意,DefaultShader是OpenGL ES 2.0的,所以你要启用你的GLES20,才能让这个有效。
    在Render方法中,我们调用了renderContext.begin(),这可以保证context是在初始化的状态,然后,我们调用了shader.begin()来告诉shader,工作开始了,你需要做好渲染对象的准备。这会设置一些全局的uniforms,比如透视矩阵什么的。之后,使用shader来渲染renderable对象。最后调用shader.end()和renderContext.end()来结束渲染。

    总结:

    • ModelInstance包含了:一组nodes的复本、模型的材质。但比如Meshes和Textures,这些属性都只是那些资源的引用。
    • ModelInstance会为自己包含的每一个NodePart创建Renderable实例。
    • Renderable,是传给渲染管道的最小的渲染单位。
    • ModelBatch保存了渲染每一个Renderable对象的Shader,将renderable对象排序,是为了最优化渲染顺序。
    • Shader负责渲染Renderable对象。每多应用中,都要使用到多个shader,每个shader都负责一个唯一的ShaderProgram(GLSL program)。
    • RenderContext是用来保存OpenGL 上下文的,比如纹理的绑定状态。



      原文地址:http://blog.csdn.net/q26335804/article/details/9162461

  • 相关阅读:
    我渴望自由和成功(愿与君共勉)
    gdb使用 和core dump文件调试
    谷歌浏览器快捷键大全
    Android适屏
    BestCoder Round #33
    NAT&amp;Port Forwarding&amp;Port Triggering
    程序员应该学习的书
    java代码调用rtx发送提醒消息
    js实现excel导出
    一个跨界程序员:不务正业的这几年,我如何让自己的收入翻了十倍(转)
  • 原文地址:https://www.cnblogs.com/tonny-li/p/4147025.html
Copyright © 2011-2022 走看看